Building your own Android library
Overview
Building your own Android library enables other developers to take advantage of code that you’ve written. You can share existing activities, services, images, drawables, resource strings, and layout files that enable other people to leverage your work such as those documented in the must have libraries guide. Also, if your code base begins to take longer times to compile and/or run, creating a library also enables you to iterate faster by working on a smaller component.
If you plan to share only standard Java code, you can distribute them packaged as Java Archive Resources (.jar) files. However, if you intend to include resources such as layouts, drawables, or string resources, or even an additional AndroidManifest.xml file, you must create an Android Archive (.aar) file instead. An .aar file is a ZIP file whose only mandatory entry is /AndroidManifest.xml; it can also include the following types of files:
- /AndroidManifest.xml (mandatory)
- /classes.jar (optional)
- /res/ (optional)
- /R.txt (optional)
- /public.txt (optional)
- /assets/ (optional)
- /libs/*.jar (optional)
- /jni//*.so (optional)
- /proguard.txt (optional)
- /lint.jar (optional)
Creating a new Android Library
When you create a new Android project, a new application is always created. You can use this application to test your library. After creating the project, go to New -> New Module:
Select Android Library. There is the option to choose Java library, but there is a major difference in that an Android library will include not only the Java classes but the resource files, image files, and Android manifest file normally associated with Android.
Next, you will be prompted to provide a name and the module name. The name will simply be used to label the application in the Android Manifest file, while the module name will correspond to the directory to be created:
When you click Next, a directory with the module name will be generated along with other files including a resource and Java folder:
In addition, a build.gradle file will be created. One major difference is that Android applications use the com.android.application plugin. Android libraries will use the com.android.library plugin. This statement at the top signals to the Android Gradle plug-in to generate an .aar file instead of an .apk file normally installed on Android devices.
// Android library
apply plugin: 'com.android.library'
Compiling a Library
Android applications usually have a build and debug variation. The buildTypes parameter designates the settings for each type of configuration.
android {
buildTypes {
release {
}
debug {
}
}
}
You can compile the library with Android Studio, or type ./gradlew build at the command line. The output will be stored under the library’s subdirectory under build/outputs/aar. Which variants of the library get published is controlled through the Android Gradle Plugin’s publishing { } DSL, as described in the official AGP guide on configuring publication variants.
If you wish to build multiple variations, the legacy publishNonDefault true flag (shown below) has been deprecated and no longer has any effect in modern versions of the Android Gradle Plugin. AGP 7.1+ instead exposes a publishing { } DSL inside the android { } block with singleVariant() and multipleVariants() builders. See the official AGP guide on configuring publication variants for current syntax.
// Legacy syntax — deprecated and ignored in modern AGP versions
android {
publishNonDefault true
}
When this legacy flag was in effect, different .aar packages were generated for each build type specified. To reference them once they are published, see this section.
If you wish to reference the library from your demo application within the same Android project, add a project dependency to your app/build.gradle. Since Android Gradle Plugin 3.0, variant-aware dependency matching automatically matches the app’s variant to the library’s (the app’s debug variant consumes the library’s debug variant, and so on), so there is no need for the legacy per-build-type project(path: ..., configuration: ...) syntax:
dependencies {
// colon needs to prefixed with the library path
implementation project(path: ':mylibrary')
}
Using with ButterKnife
If you intend use the library with ButterKnife, in the past it did not work with Android libraries and you had to convert your code back to findViewById calls. You should upgrade to at least v8.2.0 and follow this section to enable your libraries to use it.
Publishing
To publish your library, you can either make it available to a public or private repository. Note: JFrog sunset Bintray on May 1, 2021 and permanently shut down JCenter on August 15, 2024 (JCenter requests now redirect to Maven Central). Maven Central (via Sonatype Central Portal) is now the de-facto public repository for Android libraries, and the recommended Gradle plugin for publishing is the official maven-publish plugin.
To publish your library straight from GitHub you can use JitPack. Once you create a GitHub release JitPack will build your library from source and will publish it automatically.
Setting up through jCenter (historical / no longer functional)
Warning: The Bintray service was shut down on May 1, 2021, and JCenter was permanently retired on August 15, 2024. New Bintray accounts cannot be created, the
com.jfrog.bintrayGradle plugin no longer works, and thecom.github.dcendents.android-mavenplugin referenced below has been abandoned (its functionality is now provided by Android Gradle Plugin’s built-inmaven-publishsupport). The steps below are preserved for historical context only — publish to Maven Central via Sonatype Central Portal instead.
First, signup for a BinTray account. You will want to create a GPG signing key:
and go to Edit Profile to add this private/public key pair.
gpg --gen-key
Find the public key ID generated by finding the 8-digit hex after “pub 2048/XXXXXXXX”. Note that the pool.sks-keyservers.net pool was decommissioned in June 2021; use a maintained keyserver such as keys.openpgp.org or keyserver.ubuntu.com instead:
gpg --list-keys
gpg --keyserver hkps://keys.openpgp.org --send-keys [PUBLIC_KEY_ID]
Export your keys. You will want to copy/paste these sections into the GPG Signing section:
gpg -a --export yourmail@email.com > public_key_sender.asc
gpg -a --export-secret-key yourmail@email.com > private_key_sender.asc
Click on the API Key section when editing your profile. You will need to provide your username and API Key by setting it locally in your gradle.properties file:
bintrayUser=user
bintrayApiKey=key
Take a look at the examples provided by BinTray here. In particular, you should follow the android-maven-example.
Next, edit your root build.gradle file. Add the android-maven-gradle-plugin, which will be used to generate the Maven-compatible archive to be shared, as well as the JFrog plugin:
buildscript {
repositories {
jcenter()
}
dependencies {
// used to generate a POM file
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
}
}
// Plugin used to upload authenticated files to BinTray through Gradle
plugins {
id "com.jfrog.bintray" version "1.7.3"
}
Inside your library/build.gradle file, you will want to apply the Android Maven Gradle and JFrog plugin:
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
Next, you will need to define constants that will be used to generate the XML files used by Maven to understand information about the package. Gradle dependency statements usually follow the form of GROUP_ID:ARTIFACT_ID:VERSION, such as ‘com.squareup.picasso:picasso:2.5.2’, so we should always make sure these values are set.
// If your directory matches the name, you do not need to set archivesBaseName.
archivesBaseName = "android-oauth-handler"
install {
repositories.mavenInstaller {
pom.project {
groupId "com.codepath.libraries"
artifactId "android-oauth-handler"
version "1.0.0"
}
}
}
The remaining section should be added for authenticating uploads to BinTray. Note that the configurations option alerts the plugin to upload the final packages generated.
bintray {
user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
// jFrog plugin must be declared for this line to work
configurations = ['archives']
// Package info for BinTray
pkg {
repo = 'maven'
name = 'android-oauth-handler'
userOrg = user
licenses = ['Apache-2.0']
vcsUrl = 'https://github.com/bintray/gradle-bintray-plugin.git'
version {
name = '0.1'
desc = 'Gradle Bintray Plugin 1.0 final'
vcsTag = '0.1'
}
}
}
If you want to test to see if the package works locally, type:
./gradlew install
The package will be installed in your ~/.m2/repository. If you wish to try the library out, you can add this private Maven repository to the root build.gradle config of the application that will be using the library:
allprojects {
repositories {
// add first
maven { url "${System.env.HOME}/.m2/repository" }
jcenter()
}
}
To upload your package, just type:
# Set your Bintray user ID below
export BINTRAY_USER="codepath"
# Set your Bintray API key below
export BINTRAY_API_KEY="YOUR_BINTRAY_API_KEY_HERE"
./gradlew bintrayUpload
Setting up a private Amazon S3 Maven repository
Another approach is to setup a private Maven repository, which also be done through Amazon’s Web Services (AWS) and the Simple Storage Service (S3). Gradle supports the ability to access private S3 repositories with a secret access key and ID used to authenticate with Amazon:
Adding the private Maven repository
To add the S3 repository to the list, you will need to add the credentials to access the S3 bucket to your root build.gradle file:
allprojects {
repositories {
google()
mavenCentral()
maven {
url "s3://yourmavenrepo-bucket/android/snapshots"
credentials(AwsCredentials) {
accessKey AWS_ACCESS_KEY
secretKey AWS_SECRET_KEY
}
}
}
}
Instead of adding the keys directly, it is recommended that you add it to your local.properties to your local machine:
AWS_ACCESS_KEY=<my_aws_access_key>
AWS_SECRET_KEY=<my_super_secret_key>
In order to publish the plugin, we need to create a separate Gradle file that can be use in our library configuration. Create a file called gradle/gradle-mvn-push.gradle, which will apply the Gradle-Maven plugin and specify the location of the S3 bucket when using the ./gradlew publish command:
// Inspired from https://gist.github.com/adrianbk/c4982e5ebacc6b6ed902
apply plugin: 'maven-publish'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getOutputDir() {
if (isReleaseBuild()) {
return "${project.buildDir}/releases"
} else {
return "${project.buildDir}/snapshots"
}
}
def getDestUrl() {
if (isReleaseBuild()) {
return "s3://my-bucket/releases"
} else {
return "s3://my-bucket/snapshots"
}
}
def getArtifactFilePath() {
if (isReleaseBuild()) {
return "$buildDir/outputs/aar/${POM_ARTIFACT_ID}-release.aar"
} else {
return "$buildDir/outputs/aar/${POM_ARTIFACT_ID}-debug.aar"
}
}
publishing {
publications {
myPublication (MavenPublication) {
groupId GROUP
artifactId POM_ARTIFACT_ID
version VERSION_NAME
artifact getArtifactFilePath()
}
}
repositories {
maven {
url getDestUrl()
credentials(AwsCredentials) {
accessKey = "key"
secretKey = "password"
}
}
}
}
We will then apply the statements in this file by applying it within the library’s Gradle file (i.e. mylibrary/build.gradle):
apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
Edit the root project’s gradle.properties too:
VERSION_NAME=0.4-SNAPSHOT
VERSION_CODE=0.4
GROUP=com.codepath
Finally, we need to setup the metadata necessary for publishing. Edit the library’s gradle.properties and set the values:
POM_NAME=My Library
POM_ARTIFACT_ID=library
POM_PACKAGING=aar
Support for Amazon IAM Roles
Gradle also supports authenticating to S3 with Identity and Access Management (IAM) roles instead of access keys. Declare an awsIm(AwsImAuthentication) authentication scheme on the repository (in place of the credentials(AwsCredentials) block) and Gradle will load credentials from the EC2 instance role or from environment variables, as documented in the Gradle supported repository protocols guide:
repositories {
maven {
url = "s3://myCompanyBucket/maven2"
authentication {
awsIm(AwsImAuthentication) // load from EC2 role or env var
}
}
}
Note that a top-level repositories { } block like this one configures resolving dependencies from the S3 repository. To publish with IAM authentication, apply the same change inside the publishing { repositories { ... } } block of the gradle-mvn-push.gradle script shown earlier: replace its credentials(AwsCredentials) block with the authentication { awsIm(AwsImAuthentication) } block. Publishing repositories use the same Maven repository type and accept the same authentication configuration, per the Gradle Maven Publish plugin guide.
Add the Gradle dependency
Once the private S3 repository has been added to the list, you can simply add this line to the Gradle dependency list. The Android Gradle plug-in will search through all possible repositories searching for a match. Add this line to your app/build.gradle file:
dependencies {
implementation 'com.codepath:mylibrary:0.4-SNAPSHOT'
}
Historical reference (legacy publishNonDefault flow): When the removed publishNonDefault flag published a separate .aar per build type as described here, consumers had to specify the build type (i.e. release or debug) as an artifact classifier. Gradle assumed a .jar file when a classifier was given, so @aar also had to be appended, and because that syntax does not follow the dependencies declared in the package, transitive=true had to be included as well, as described in this Google discussion. Modern AGP publishes multiple variants through the publishing { } DSL instead, attaching variant attributes that let the consumer’s variant-aware matching select the right artifact from plain GROUP:ARTIFACT:VERSION coordinates like the snippet above.
// Legacy classifier-based consumption — do not use with modern AGP publishing
dependencies {
releaseImplementation('com.codepath:mylibrary:0.4:release@aar') {
transitive=true
}
debugImplementation('com.codepath:mylibrary:0.4-SNAPSHOT:debug@aar') {
transitive=true
}
}
Prevent caching
If you are making constant changes to your snapshot versions and wish to have the latest updates pulled each time, you can mark the dependency as a changing module in Gradle:
dependencies {
implementation('com.codepath:mylibrary:0.4-SNAPSHOT') {
changing = true
}
}
Gradle will normally cache the module for 24 hours for those marked as changing, but you can lower this setting:
configurations.all {
// check for updates every build
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
Issues with JDK 8u60 (historical: Gradle 2.x)
On Gradle versions older than 2.8, accessing a private Amazon S3 repository could fail with an AWS authentication requires a valid Date or x-amz-date header error when building with JDK 8u60. It was a known Gradle issue that was fixed by upgrading to Gradle v2.8 via the gradle/wrapper/gradle-wrapper.properties file:
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
Current Gradle versions are not affected.
Using with ProGuard
If you intend to export your release, you should also include any configurations in case ProGuard/R8 is applied to the app consuming your library. If you specify consumerProguardFiles inside the defaultConfig block of your library’s Gradle config, the rules in that file will be bundled into the .aar and applied automatically when a consuming app is shrunk, as described in the optimization guidance for library authors. (Note that minifyEnabled is a buildTypes property that shrinks the library itself — it does not belong in defaultConfig.)
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
}
Make sure to create a consumer-proguard-rules.pro file. See Configuring ProGuard for more details.
Keep the consumer rules as narrow as possible: Google’s guidance for library authors warns that a library “must not include broad keep rules” (such as package-wide -keep class com.mylibrary.** { *; } rules or keeping every public class) and says to never use global options like -dontobfuscate in consumer rules, since they degrade optimization for every app that depends on your library. Only keep the specific classes and members that are accessed via reflection or JNI.
See the ProGuard usage documentation for more information about the syntax of this file.
Resource Merging
If your Android library defines an AndroidManifest.xml or any other resource files (i.e. strings.xml, colors.xml), these resource will be automatically merged with your application. In this way, you do not have to redeclare permissions that are needed in your library in your main application. However, if your library declares color styles that may conflict with the appearance in your main application, you may need to rename these styles.
If you do wish to understand how the final AndroidManifest.xml is generated, you can decode the final .apk file using a third-party tool called apktool. Instructions to install are located here. If you are upgrading the apktool version, you may need to delete the $HOME/apktool/framework/1.apk file.
Once you have the tool installed, you simply need to type this line:
apktool decode <.apk file>
The tool should decode your .apk file and allow you to better understand how the final resource files are generated.
References
- http://ryanharter.com/blog/2015/06/18/hosting-a-private-maven-repo-on-amazon-s3/
- http://chris.banes.me/2013/08/27/pushing-aars-to-maven-central/
- https://gist.github.com/adrianbk/c4982e5ebacc6b6ed902/
- https://github.com/gradle/gradle/blob/master/design-docs/finding-and-using-credentials.md#story-an-s3-repository-can-be-configured-to-authenticate-using-awss-ec2-instance-metadata/