Publish Android library to BinTray (JCenter), AAR vs. JAR and optional dependency

Working on Android open-source I needed to publish two libraries (Android-Image-Cropper and Android-Fast-Image-Loader) to JCenter.
 
What I will touch in this post:

  • Publishing AAR
  • "Sources not found" issue with AAR
  • AAR vs. JAR
  • Publishing JAR
  • Handling optional dependency in Gradle

 
TL;DR see the release gradle script.
 
clip_image001.png

 

Publishing AAR

After not so quick Google search I finally found this blog post that did exactly what I needed, create a zip archive with everything required (aar, javadoc jar, sources jar and md5 hashes) to upload the library to BinTray.
The blog post does an excellent job so I won't repeat it, in the end what I needed to add to the library build.gradle file was the following snippet (note: it uses remote script).
 

apply plugin:  'com.android.library'      

ext {      
    PUBLISH_GROUP_ID = 'com.theartofdev.edmodo'      
    PUBLISH_ARTIFACT_ID =  'android-image-cropper'      
    PUBLISH_VERSION = '1.0.4'      
}      

android {      
    // flavor config etc.      
}      
dependencies {      
    // dependencies      
}      

apply from: 'https://raw.githubusercontent.com/ArthurHub/release-android-library/master/android-release-aar.gradle'      

 
The output "release-1.0.3.zip" file can be uploaded and extracted to BinTray, just make sure to check the "Explode this archive" checkbox. Then you can request publishing the library to JCenter so the user won't have to reference your private repository.
 

"Sources not found" issue

Publishing AAR package works perfect except when, as a user, you try to look at the library javadoc or source, you get "Sources not found" even though the JARs were uploaded in the package.
 
clip_image002.png
 
Apparently the issue here is with IDEA (Android Studio) and AAR format, IDEA downloads the sources JAR into the wrong location so it doesn't finds them. The issue is specific to AAR and doesn't occurs if JAR is used.
 
There are two ways to solve it, either deploy the library as JAR and not AAR or use AARLinkSources Plugin.
The first approach is on the library developer and not always possible as I will explain shortly while the second is on the user of the library so it creates more friction to use the library and many users won't bother.
 

AAR vs. JAR

The 'aar' bundle is the binary distribution of an Android Library Project.
AAR is simply a zip archive that includes the JAR (classes.jar) with additional Android resources including the manifest xml and R.txt.
So publishing Android library as JAR will work fine as long as it doesn't define any resources like strings, dimensions, drawables, layouts or custom attributes. If any of those are in use you must publish AAR for the library to work properly. Unfortunately publishing AAR will not allow the users easy access to the sources and documentation as a explained above.
 
clip_image003.png
 

Publishing JAR

To publish a JAR there is an additional task we need to add to the gradle script. This will create a JAR archive in addition to the AAR archive already created by the script and they both will be bundled and can be uploaded together, JAR have higher priority so it will be used even if AAR exists in the package.
 

android.libraryVariants.all  { variant ->      
    def name = variant.buildType.name      
    if (!name.equals("debug")) {      
        def task = project.tasks.create  "jar${name.capitalize()}", Jar      
        task.dependsOn variant.javaCompile      
        task.from  variant.javaCompile.destinationDir      
        artifacts.add('archives', task);      
    }      
}      

 
I created a new gradle script, based on the original, so all you need to do is use different URL in the "apply from" part (note the name is "android-release-jar .gradle"):

apply plugin:  'com.android.library'      

ext {      
    PUBLISH_GROUP_ID = 'com.theartofdev.edmodo'      
    PUBLISH_ARTIFACT_ID =  'android-image-cropper'      
    PUBLISH_VERSION = '1.0.4'      
}      

android {      
    // flavor config etc.      
}      
dependencies {      
    // dependencies      
}      

apply from: 'https://raw.githubusercontent.com/ArthurHub/release-android-library/master/android-release-jar.gradle'      

 

Handling optional dependency in Gradle

Not directly on the subject of publishing a library package, though the use of optional dependency is only relevant when publishing a library to be used by others.
The idea is to be able to use a dependency in library code but not forcing the users of the library to be dependent on the optional dependency. So if the user doesn't have this dependency the library code will handle it by falling back to alternative implementation.
In Android-Fast-Image-Loader I have an optional dependency on OkHttp network library used for image downloading, but if the user doesn't have that dependency I fallback to native Android URLConnection.
 
Maven has native support for this feature so all is needed is to have "<optiona>true</optional>" on the dependency in the generated POM file of the library, unfortunately Gradle doesn't support it so a little hack is required.
 
First we need to create "optional" keyword synonymous to "compile", then in " uploadArchives " " pom.withXml " we will add the optional element for all dependency that use the "optional" keyword:
 

configurations {      
    optional      
    compile.extendsFrom optional      
}      
uploadArchives {      
    repositories.mavenDeployer {      
        pom.groupId = groupId      
        pom.artifactId = artifactId      
        pom.version = version      

pom.withXml {      
            asNode().dependencies.dependency.findAll { xmlDep ->      
                // mark optional dependencies      
                if  (project.configurations.optional.allDependencies.findAll { dep ->      
                    xmlDep.groupId.text() ==  dep.group && xmlDep.artifactId.text() == dep.name      
                }) {      
                    def xmlOptional =  xmlDep.optional[0];      
                    if (!xmlOptional) {      
                        xmlOptional =  xmlDep.appendNode('optional')      
                    }      
                    xmlOptional.value = 'true';      
                }      
            }      
        }      

// Add other pom properties here if you  want (developer details / licenses)      
        repository(url: "file://${localReleaseDest}")      
    }      
}      

 
I have updated the script, the only difference is that not the "apply from" part needs to be before the "dependencies", otherwise it will not recognize the "optional" keyword:
 

apply plugin:  'com.android.library'      

ext {      
    PUBLISH_GROUP_ID = 'com.theartofdev.edmodo'      
    PUBLISH_ARTIFACT_ID =  'android-image-cropper'      
    PUBLISH_VERSION = '1.0.4'      
}      

android {      
    // flavor config etc.      
}      

apply from: 'https://raw.githubusercontent.com/ArthurHub/release-android-library/master/android-release-aar.gradle'      

dependencies {      
    // dependencies      
    optional 'com.something:something:1.0'      
}      

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s