The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Advanced Gradle for Android Tips

There are lots of things you can do given a full scripting language as the basis for your build system. This chapter represents a collection of tips for things that you can do that go beyond stock capabilities provided by the Android Plugin for Gradle.

Prerequisites

Understanding this chapter requires that you have read the chapters that introduce Gradle and cover basic Gradle/Android integration, including both the legacy project structure and the new project structure. Having read the chapter on Gradle dependencies would also be a pretty good idea.

Gradle, DRY

Ideally, your build scripts do not repeat themselves any more than is logically necessary. For example, a project and sub-projects probably should use the same version of the build tools, yet by default, we define them in each build.gradle file. This section outlines some ways to consolidate this sort of configuration.

It’s build.gradle All The Way Down

If you have sub-projects, you can have build.gradle files at each level of your project hierarchy. Your top-level build.gradle file is also applied to the sub-projects when they are built.

In particular, you can “pass data” from the top-level build.gradle file to sub-projects by configuring the ext object via a closure. In the top-level build.gradle file, you would put common values to be used:


ext {
   compileSdkVersion=19
}

(note the use of the = sign here)

Sub-projects can then reference rootProject.ext to retrieve those values:


android {
    compileSdkVersion rootProject.ext.compileSdkVersion
}

By this means, you can ensure that whatever needs to be synchronized at build time is synchronized, by defining it once.

Another way that a top-level build.gradle file can configure subprojects is via the subprojects closure. This contains bits of configuration that will be applied to each of the subprojects as a part of their builds.

The HelloAIDL sample project demonstrates this. The build.gradle in the overall project root (outside the Client/ and Service/ sub-projects) has a subprojects closure to define the code-signing rules for these two applications and common values for the two sub-projects:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
    }
}

subprojects {
    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.0'
        }
    }

    apply plugin: 'com.android.application'

    android {
        compileSdkVersion 19
        buildToolsVersion "25.0.2"

        signingConfigs {
            release {
                storeFile file('HelloAIDL.keystore')
                keyAlias 'HelloConfig'
                storePassword 'laser.yams.heady.testy'
                keyPassword 'fw.stabs.steady.wool'
            }
        }

        buildTypes {
            release {
                signingConfig signingConfigs.release
            }
        }
    }
}

The subprojects closure contains its own reference to the android plugin for Gradle, in addition to android closure for configuring the signingConfigs and buildTypes. Because this code is written in the root project’s build.gradle file, file() references refer to the root project’s directory, which is why file('HelloAIDL.keystore') will find the keystore in the root project’s directory.

Note that subprojects applies to all sub-projects, which limits its utility. For example, a top-level project with one sub-project for an app and another sub-project for a library used by that app cannot readily use subprojects. That is because the library sub-project needs to configure the com.android.library plugin, while the application sub-project needs to configure the com.android.application plugin. The subprojects closure is only good for common configuration to apply to all sub-projects regardless of project type.

gradle.properties

Another approach would be to add a gradle.properties file to your project root directory. Those properties are automatically read in and would be available up and down your project hierarchy.

Per-developer properties can go in a gradle.properties file in the user’s Gradle home directory (e.g., ~/.gradle on Linux), where they will not be accidentally checked into version control.

So, to achieve the synchronized compileSdkVersion value, you could have a gradle.properties file with:


COMPILE_SDK_VERSION=19

Then, your projects’ build.gradle files could use:


android {
    compileSdkVersion COMPILE_SDK_VERSION
}

The Gradle/HelloProperties sample project illustrates this. It is a clone of the HelloAIDL sample application from earlier in this chapter, but one where we have a gradle.properties file in the root project’s directory:

BUILD_TOOLS_VERSION=21.1.2

Here, we are defining a build tools version for use with the buildToolsVersion property in the android closure. The sub-projects use the BUILD_TOOLS_VERSION property that we defined in gradle.properties in their own build.gradle files, courtesy of a subprojects closure defined in the top-level build.gradle file:

        buildToolsVersion BUILD_TOOLS_VERSION

Custom Properties Files

You are also welcome to use your own custom properties files. For example, perhaps you want to use gradle.properties for properties that you are willing to put in version control (e.g., BUILD_TOOLS_VERSION), but you would also like to use a properties file to keep your code-signing details outside of your build.gradle file and out of version control.

Loading in custom properties files is slightly clunky, as it does not appear to be built into Gradle itself. However, you can take advantage of the fact that Gradle is backed by Groovy and use some ordinary Groovy code to load the properties.

This can also be seen in the HelloProperties sample project, where the build.gradle in the root project’s directory uses a signing.properties file to isolate sensitive data:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
    }
}

subprojects {
    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.0'
        }
    }

    apply plugin: 'com.android.application'

    android {
        compileSdkVersion 19
        buildToolsVersion BUILD_TOOLS_VERSION

        def signingPropFile = rootProject.file('signing.properties')

        if (signingPropFile.canRead()) {
            def Properties signingProps = new Properties()

            signingProps.load(new FileInputStream(signingPropFile))

            signingConfigs {
                release {
                    storeFile rootProject.file('HelloAIDL.keystore')
                    keyAlias signingProps['KEY_ALIAS']
                    storePassword signingProps['STORE_PASSWORD']
                    keyPassword signingProps['KEY_PASSWORD']
                }
            }

            buildTypes {
                release {
                    signingConfig signingConfigs.release
                }
            }
        }
    }
}

Let’s look at the key lines, one at a time:


def signingPropFile = rootProject.file('signing.properties')

This statement grabs the signing.properties file from the root project and assigns it to the signingPropFile variable. Groovy, by default, is a dynamic language and does not use data types for its variables. Under the covers, signingPropFile is a java.io.File object, just like you are used to in ordinary Java/Android development.


if (signingPropFile.canRead()) {
}

Since signingPropFile is a File, we can call a canRead() method to confirm that the file exists and is readable.


def Properties signingProps = new Properties()

This creates an empty instance of a java.util.Properties object and assigns it to the signingProps variable.


signingProps.load(new FileInputStream(signingPropFile))

This creates a standard java.io.FileInputStream for the properties file, then passes it to the load() method on the Properties object, to read in the properties file.


keyAlias signingProps['KEY_ALIAS']
storePassword signingProps['STORE_PASSWORD']
keyPassword signingProps['KEY_PASSWORD']

These statements access properties from the Properties object, where Groovy has augmented Properties to support square-bracket syntax to access individual properties.

The author would like to thank Gabriele Mariotti for his blog post that, among other things, depicted this technique.

Environment Variables

Any environment variables with a prefix of ORG_GRADLE_PROJECT_ will show up as global variables in your Gradle script. So, for example, you can access an environment variable named ORG_GRADLE_PROJECT_foo by accessing a foo variable in build.gradle.

If you would prefer to use environment variables without that prefix, you can call System.getenv(), passing in the name of the environment variable, to retrieve its value.

Note, however, that you may or may not have access to the environment variables that you think you should. Android Studio, for example, does not expose environment variables to Gradle for its builds, and so an environment variable that you can access perfectly well from the command line may not be available in the same build.gradle script when run from Android Studio.

Automating APK Version Information

The preview of this section is off trying to sweet-talk the Khaleesi into providing us with a dragon.

Adding to BuildConfig

The preview of this section was abducted by space aliens.

Down and Dirty with the DSL

The preview of this section was accidentally identified as an Android 'tasty treat' by the Cookie Monster.