Publishing Android Library to Bintray with Gradle + Buddy.Works

If you’re developing an Android Library you will most likely come to the point where you’ll want to distribute your package. There are several ways to accomplish that; in this article I’ll show you my favorite set of tools and methods. We’ll use Gradle to create the package and Bintray to host and distribute.

The last part of this system is automating the build process with a continuous integration tool like Buddy.Works. In Buddy you can setup your build environment to run Gradle commands on every push to your repository.

The tools covered in this article were implemented in the BlurKit project, you can find the code in this repository: https://github.com/CameraKit/blurkit-android. It might be helpful to see a complete example as you’re working on your own project!

Gradle

Image result for gradle

If you’ve even just got a taste for Android development you should be familiar with Gradle. Gradle is a powerful builds system that builds library files from Java source code.

In its default Android setup, Gradle builds your project into an APK to run on an android device. However if you’re creating a library you need to create .pom and .aar files to publish to Bintray.

Let’s Get To It.

First we’ll create a new file called deploy.gradle that we will call from the project build.gradle. The end goal from the deployment is to build, then publish to Bintray. Let’s first define our build task.

Before building a new project we need to clean the previous deployment. Do that with the following function.

task cleanForDeployment {
doLast {
logger.lifecycle('BlurKit: Deployment: Cleaning...')
logger.lifecycle('Deleting: ' + project.buildDir)
delete project.buildDir
}
}

The build files are contained at the path project.buildDir. Deleting that folder will effectively clean the previous build.

Now, build our new package.

task buildForDeployment {
logger.lifecycle('BlurKit: Deployment: Building...')
shouldRunAfter(cleanForDeployment)
finalizedBy assemble

doFirst {
android.variantFilter { variant ->
if (variant.buildType.name == 'debug') {
variant.setIgnore(true)
}
}
}
}

The buildForDeployment task depends on cleanForDeployment and finalized by a gradle command called assemble. In the buildForDeployment command do a quick check of the buildType to determine if we are running in debug mode.

The last thing Gradle has to do is deploy the package to Bintray; let’s take a quick detour to Bintray.

The Land of Bintray.

JFrog Bintray is a cloud platform that gives you full control over how you publish, store, promote, and distribute software.

The proper name is JFrog Bintray but for this article I’ll simply refer to the service as Bintray. Like the quote from their documentation says, Bintray is a service to host and distribute software. Bintray has become the standard for most Android projects due to its reliability and ease of use.

If you don’t have an account, navigate to bintray.com and create one now.

Bintray is organized with the following hierarchy. Account -> Repository -> Package -> Version.

Once you’ve created an account, add a new repository. If you are not using JCenter (we’ll touch on that in a bit) the name will show up when the user is implementing your package, so pick a name that reflects the project. For this example I’ll use “other”. Then select type as maven, and click create.

From the repository page add a new package. This package will be the name of the public facing library that users will include in their projects. I’ve used “blurkit-android”.

Now we’ll go back to Gradle code.

Gradle Town

Welcome back to Gradle. Add the following code to the top of the deploy.gradle file.

apply plugin: 'com.jfrog.bintray'
bintray {
user = getBintrayUser()
key = getBintrayKey()
configurations = ['archives']
pkg {
repo = 'other'
name = 'blurkit-android'
userOrg = 'camerakit'
vcsUrl = 'https://github.com/CameraKit/blurkit-android.git'
licenses = ['MIT']

version {
name = project.version
released = new Date()
}
}
}

Here we create a bintray object and configure it with our Bintray account information we just set up. The repo, name and userOrg come from Bintray. vcsUrl, and licenses are not required but reference your version control url and the license associated with it. version is the version that the user will include in the implementation of your library.

For example with a package name of “blurkit-android” and a version of ‘1.0.0’ the line to include the library from Android would be:

implementation ‘io.alterac.blurkit:blurkit:1.0.0’

I’ve setup project.version from my build.gradle file which we will cover in the next section.

The two parameters I haven’t mentioned, user and key are private variables and should not be included directly in the code. Let’s create two functions to get the user and key parameters from environment variables or a configuration file.

def getBintrayUser() {
if (System.getenv('BINTRAY_USER')) {
logger.lifecycle(System.getenv('BINTRAY_USER'))
return System.getenv('BINTRAY_USER')
} else if (rootProject.file('local.properties').exists()) {
Properties properties = new Properties()
properties.load(rootProject.file('local.properties').newDataInputStream())
return properties.getProperty('bintray.user')
} else {
logger.lifecycle("Warning: Could not find BINTRAY_USER in environment or local.properties")
}
}

def getBintrayKey() {
if (System.getenv('BINTRAY_KEY')) {
logger.lifecycle(System.getenv('BINTRAY_KEY'))
return System.getenv('BINTRAY_KEY')
} else if (rootProject.file('local.properties').exists()) {
Properties properties = new Properties()
properties.load(rootProject.file('local.properties').newDataInputStream())
return properties.getProperty('bintray.key')
} else {
logger.lifecycle("Warning: Could not find BINTRAY_KEY in environment or local.properties")
}
}

The above block first checks the system environment variables for BINTRAY_USER and BINTRAY_KEY. If they do not exist it then checks the local.properties file as a backup. You can store the variable wherever is convenient. I chose to use environment variables.

Now our Bintray object is setup with the information from our online Bintray account but it’s not yet doing anything useful. Let’s add a deploy command to deploy this package to Bintray.

task deployRelease {
logger.lifecycle('BlurKit - Starting Release Deployment')
shouldRunAfter(buildForDeployment)

dependsOn cleanForDeployment
dependsOn buildForDeployment
finalizedBy bintrayUpload

doLast {
logger.lifecycle(bintrayUpload.getVersionName())
bintrayUpload.setVersionName(bintrayUpload.getVersionName())
bintrayUpload.setUserOrg('camerakit')
bintrayUpload.setRepoName('other')

logger.lifecycle('Deploying version ' + bintrayUpload.getVersionName() + ' in ' + bintrayUpload.getRepoName())
}
}

This deployRelease command builds on what we created in buildRelease and is finished by the gradle command bintrayUpload. Before the deployment command runs, we have to set the versionName, userOrg, and repoName again.

Integrating With The Rest Of Your Gradle Scripts.

The last Gradle step is integrating deploy.gradle with the rest of our gradle scripts. Two files need modification.

First the top level build.gradle.

buildscript {
...
    dependencies {
...
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.1'
}
}

allprojects { project ->
group = 'io.alterac.blurkit'
version = '1.0.0'

repositories {
google()
jcenter()
}
}

The additions are:

  1. Bintray dependency.
  2. allprojects line — Here we define the version number that is referenced by Bintray for deployment

Next we’ll head to the project level build.gradle where we’ll make a few more additions.

plugins {
...
id 'com.github.dcendents.android-maven' version '1.5'
}
install {
repositories.mavenInstaller {
pom.project {
groupId project.group
artifactId 'blurkit'
packaging 'aar'
}
}
}

apply from : 'deploy.gradle'
  1. The android-maven dependency.
  2. install block where the groupId, artifactId and packaging are defined. We’ll package with .aar for use in Android projects.
  3. apply from : 'deploy.gradle' to link the deploy.gradle file.

To run the command we just created type ./gradlew deployRelease in the top level directory of the project.

Finishing Up

Then navigate to your package on Bintray’s website and you should see a notification that a new version has been published. Confirm that version and Bintray will finalize the upload. You should see something like this.

The default way that your Library is included in an Android project is with the line shown in the bottom left of the package page.

compile ‘io.alterac.blurkit:blurkit:1.0.0’

When using default Bintray you need to add a repositories section with the name of the repository your package is hosted in the build.gradle of the Android project.

repositories {
maven {
url "https://camerakit.bintray.com/blurkit"
}
}
dependencies {
implementation 'io.alterac.blurkit:blurkit:1.0.0'
}

On the bottom right of the package page there is an option to link to JCenter. JCenter is included in Android projects by default and doesn’t require you do add the specific url of your repository.

Icing On The Cake

This setup is fast and reliable but doesn’t solve our problem of automation. There are plenty of automation tools out there but my preferred method is Buddy.Works.

Buddy.Works is a platform to create deployment pipelines for your projects of any type, from Android to Web development. You can configure all kinds of different commands and environments to test and deploy.

Visit https://buddy.works and create an account. You can get started with Google, GitHub or Bitbucket.

Create a new project, allow Buddy access to your Github profile and select the GitHub repository from your account. This repository is the one Buddy.Works will watch for changes on.

Next smash the “Add a new pipeline” button, give it a name and select the trigger mode and branch you want to work from. I have mine set to On Push to the master branch.

From here you’ll see a list of possible actions you can add to your pipeline.

There are a lot of possible actions in different categories. You can publish to Google Play, tell the pipeline to wait for approval, zip files and run commands from all types of languages and frameworks.

For this tutorial we’ll keep it simple and just create a Gradle command.

Gradle in Buddy.Works

Select the Gradle action and you’ll be taken to a terminal window with some more options above it. In the command window we’ll add our deployment task.

./gradlew deployRelease

./gradlew is the gradle wrapper command and installs missing gradle dependencies rather than depending on gradle itself. Any other command, gradle or otherwise can be added here.

If you run the pipeline in its current state (go ahead and try it now) it won’t work. You’ll most likely get an error about Java sdk/ndk locations not found. Good news for you, I’ve already set up a development environment on Docker for Android projects. The image is located here on DockerHub: https://hub.docker.com/r/emersoncloud/android-ndk-28/ but you can add it to Buddy.works by clicking on the environment tab and adding the docker image of “emersoncloud/android-ndk-28”

Lastly we need to add the Bintray user and key environment variables for the gradle script. Do that in the variables tab.

Save the pipeline and you’ll be ready to rumble! You can now push a change to your repository and watch it run through the pipeline.

Our pipeline is very simple with just a single action. You can add additional actions below the Gradle action or have them run on failure in the “Actions Run On Failure” section.

Conclusion

While this article covers our automation of BlurKit, we plan to integrate a similar automated system for the CameraKit project with support for beta and snapshot releases as well. We are huge advocates of automated devOps so expect many more posts about our other automated systems and future systems coming soon!

You can check out the project where we implement these Gradle scripts here: https://github.com/CameraKit/blurkit-android.

Thanks for reading and I hope you were able to follow along and publish your project successfully. If you have any questions drop a comment here on Medium and I’ll be sure to respond.