Gradle tips & tricks to survive the zombie apocalypse

Rick Grimes can’t help you out, so let’s get it together!

Gradle is here to stay. Although existing before Android Studio, it became the official build tool for android development and was the turning point on making it popular. But, are we taking full advantage of this great build automation system?

Project and build specific global variables

With gradle, a BuildConfig class is automatically generated and we have the ability to generate additional fields into it. This can be useful for such things as configuring server URLs and easily toggling features on and off.

defaultConfig {
buildConfigField "String", "TWITTER_TOKEN", '"SDASJHDKAJSK"'
}
buildTypes {
debug {
buildConfigField "String", "API_URL", '"http://api.dev.com/"'
buildConfigField "boolean", "REPORT_CRASHES", "true"
}
release {
buildConfigField "String", "API_URL", '"http://api.prod.com/"'
buildConfigField "boolean", "REPORT_CRASHES", "false"
}
}

Accessible from the BuildConfig final class, BuildConfig.TWITTER_TOKEN, BuildConfig.REPORT_CRASHES and BuildConfig.API_URL (the last two will be different according to the type of build they’re in).

Different name, version name and app id per buildtype

This allows you to have both release and debug versions of the app installed at the same time (remember that android doesn’t let you install different apps with the same package name)!

You can filter issues/crashes by the different version names in your crash reporting tool.

Easily spot which one you are currently running by looking at the app name!

android {
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
resValue "string", "app_name", "CoolApp (debug)"
signingConfig signingConfigs.debug
}
release {
resValue "string", "app_name", "CoolApp"
signingConfig signingConfigs.release
}
}

Private Information

Android requires that all apps be digitally signed with a certificate before they can be installed. Android uses this certificate to identify the author of an app, although there are some sensible information that should not be available to others.

You should NEVER check-in this kind of information into your source control.

Some people argue that you should have a local config file or even a global ~/.gradle/build.gradle with this values, but if you’re doing Continuous Integration/Deployment and specially if you don’t own your CI server, you should’t have any kind of file with the credentials in plain-text laying around in your CVS.

signingConfigs {
release {
storeFile "${System.env.COOL_APP_PRIVATE_KEY}"
keyAlias "${System.env.COOL_APP_ALIAS}"
storePassword "${System.env.COOL_APP_STORE_PW}"
keyPassword "${System.env.COOL_APP_PW}"
}
}

This way I can provide sensible information through Environment Variables to my CI and not worry about checking-in anything “dangerous” to my company.

Auto generated versionName and versionCode

Break up your version into its logical components and manage them separately. Stop wondering if you’ve bumped the version code correctly or updated the version name properly.

def versionMajor = 1
def versionMinor = 0
def versionPatch = 0
android {
defaultConfig {
versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
}
}

Add git hash and build time to your BuildConfig

def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
def buildTime = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"))
android {
defaultConfig {
buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
buildConfigField "String", "BUILD_TIME", "\"${buildTime}\""
}
}

Now you have two available variables, BuildConfig.GIT_SHA and BuildConfig.BUILD_TIME, which are awesome for binding the commits/build time to your logs among other things!

Fastening your seatbelts

For faster deploys just create a flavour called dev and set the minSdkVersion to 21. Note that you sacrifice getting proper linting against your real minSdk by doing this, and this should obviously just be used for day to day work and not releases. This allows the Android gradle plugin to pre-dex each module and produce an APK that can be tested on Android Lollipop and above without time consuming dex merging processes.

android {
productFlavors {
dev {
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the application.
minSdkVersion 14
}
}
}

Unit tests output directly to the console

A small neat trick so we can see android unit tests logging results as they happen.

android {
...

testOptions.unitTests.all {
testLogging {
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
outputs.upToDateWhen { false }
showStandardStreams = true
}
}
}

Now when you run your tests they will output something like this:

unit testing logging output

Gradle, tell me I’m pretty

An organised way to use it all:

android {
...
buildTypes {
def BOOLEAN = "boolean"
def TRUE = "true"
def FALSE = "false"
def LOG_HTTP_REQUESTS = "LOG_HTTP_REQUESTS"
def REPORT_CRASHES = "REPORT_CRASHES"
def DEBUG_IMAGES = "DEBUG_IMAGES"

debug {
...
buildConfigField BOOLEAN, LOG_HTTP_REQUESTS, TRUE
buildConfigField BOOLEAN, REPORT_CRASHES, FALSE
buildConfigField BOOLEAN, DEBUG_IMAGES, TRUE
}

release {
...
buildConfigField BOOLEAN, LOG_HTTP_REQUESTS, FALSE
buildConfigField BOOLEAN, REPORT_CRASHES, TRUE
buildConfigField BOOLEAN, DEBUG_IMAGES, FALSE
}
}
}

All good now!

If you have any questions hit me up on @cesarmcferreira