Android: Using Gradle

On a couple of Android projects I'm working on, I've switched to using the new Gradle Build system. The idea of storing the entire build configuration in one place is great.

My early experience of Gradle seemed to cause more problems than it solved. However, this was probably due to lack of understanding as much as the early status of the tools. As well as taking the time to learn more about Gradle, recent releases of the toolkit also seem far more stable and integrated with the Android Studio/IDEA IDE.

This post is a summary of how I've solved various tasks with Gradle and what I've learned so far. Hopefully it will help guide you if you're getting started with Gradle, and serve as a handy reminder. These instructions are based on using Android Studio 0.4:

Including an external Jar

  1. Create a libs folder under your project's top-level folder.
  2. Copy the .jar file into the new libs folder, or a sub-folder.
  3. In your app's build.gradle, add the jar as a file dependency:
// /MyProject/my-app/build.gradle

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:0.8.+'
    }
}

apply plugin: 'android'
repositories {

    mavenCentral()

    flatDir {
        dirs 'libs'
    }
}

android {
  // ...
}

dependencies {
  compile files('libs/somelibrary.jar')
}

Including an external library module

// MyProject/settings.gradle

include ':my-library'
// /MyProject/MyApp/build.gradle

android {
// ...
}

dependencies {
compile project(':my-library')
}

Including Google Play Services

Google Play Services is increasingly becoming the only way to interact with Google's APIs (this is a good thing, as it will hopefully help to reduce platform fragmentation). The Google Play Services are now provided through a local repository.

// /MyProject/MyApp/build.gradle

android {
  // ...
}
dependencies {
  compile 'com.google.android.gms:play-services:3.2.+'
}

The same is also true of the Android Support Libraries which are provided through a separate local repository:

// /MyProject/MyApp/build.gradle
dependencies {
  compile 'com.android.support:support-v4:19.0.0'
  compile 'com.android.support:gridlayout-v7:19.0.0'
  compile 'com.android.support:appcompat-v7:19.0.0'
  // ...
}

Product Flavours and Build Types

In all the documentation, I couldn't find a explanation of the difference between a product flavour and a build type.

Put simply, a product flavour is a different edition your app that you might release on Google Play, the most common example being a free and paid edition of your app. I've not really used these much, but one thing you can do is configure a different package name for your app depending on which flavour you use, e.g:

When the app is compiled, it will use the appropriate package name according to the product flavour your have built.

A build type is an environment setting for your app. By default, a debug (development) and release (production) build type are set up. I usually add a third staging build type.

Build types let you set environment. Specific values using the build config object. This is great for setting things like ContentProvider authorities and API endpoints:

// /MyProject/MyApp/build.gradle
android {

  buildTypes {
    debug {
      buildConfigField "String", "PROVIDER_AUTHORITY", "\"com.example.app.debug.provider\""
      buildConfigField "String", "API_ENDPOINT", "\"http://development.localhost/\""
    }

    staging {
      buildConfigField "String", "PROVIDER_AUTHORITY", "\"com.example.app.beta.provider\""
      buildConfigField "String", "API_ENDPOINT", "\"http://beta.example.com/api/\""
    }

    release {
      buildConfigField "String", "PROVIDER_AUTHORITY", "\"com.example.app.provider\""
      buildConfigField "String", "API_ENDPOINT", "\"http://api.example.com/api/\""
    }
  }
}

With product flavours and build types, you could generate a debugFree apk of your app for testing, and a releasePaid apk for distribution on the Play Store.

Keeping release signing configuration out of source control

The keystore and passwords for signing your release jars is now also configured through the Gradle build script. Like any sensitive data, though, you should ensure this is never published to your code repository.

Instead, using the code from this gist lets you keep your signing data in an external file which Gradle loads at compile time. I add a signing.properties.example file to my repository to server as a reminder to set the correct values:

// /MyProject/MyApp/build.gradle

android {
  // ...
}
dependencies {
  // ...
}

def Properties props = new Properties()
def propFile = new File('signing.properties')

if (propFile.canRead()) {
    props.load(new FileInputStream(propFile))

    if (props != null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
            props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
        android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
        android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
        android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
        android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
    } else {
        println 'signing.properties found but some entries are missing'
        android.buildTypes.release.signingConfig = null
    }
} else {
    println 'signing.properties not found'
    android.buildTypes.release.signingConfig = null
}
# signing.properties.example

STORE_FILE=/path/to/your.keystore
STORE_PASSWORD=yourkeystorepass
KEY_ALIAS=projectkeyalias
KEY_PASSWORD=keyaliaspassword