Gradle: Android Build Variables Done Right

Using Gradle to create build variables, avoid mistakes and improve continuous integration

Rafael Matias
6 min readMay 8, 2018
Maintaining a huge project is like to drive an elephant, every instruction is important. Photo by Andrew Rice from Unsplash.

Gradle helps teams build, automate and deliver better software, faster. From mobile apps to microservices, from small startups to big enterprises.

Proposal

Present how developers can use just Gradle system to set up, develop, deliver and integrate Android projects using build variables.

How Android building works

Gradle is the standard build tool for Android Studio. So, it can do a lot of heavy work like to build classes for constants and resources, compile project, export .apk, execute tests, etc.

And, not less important, Gradle can handle build types and a team can create an environment to automate tasks like deploy.

Developers develop, Gradle builds and users consume app :)

Build variables for build variants

Here is the most important part of setting a great environment with consistent variables. For every build type such as development, staging, release (I'll write something about build types) we can create specific configuration or a group of variables.

Common project constant variables:

#1 Base URLs (hostnames)

public static final String API_BASE_URL = "http://example.com/"

#2 File paths or filenames

public static final String DB_FILEPATH = "common/path/db.sqlite"

#3 Keys

public static final String API_KEY = "2FA43FADFaw432dfa"

All of these constants above could be defined as build variables. In this case, developers don't need to concern about these definitions. Also, a member of the team who doesn't have permissions can't change variables for release deploy — of course, if the team creates a secure CI environment.

For example, imagine your app needs to pre-populate a SQLite database and the file is on your root project. One day a developer decides to play with this database, add ‘test’ data and forget to remove it. Boom! The data is in production and your product has a little problem. Believe me, it happens more than we think. We can solve that using a database file on deploy server and defining a build variable in Gradle with its file path to load it.

Gradle properties

Before talking about how Gradle injects variables in our Android project, we need to understand where we can store values and use them as build variables.

Gradle properties file in an Android project structure view

By default, Gradle uses a file called gradle.properties in the root directory for Android Projects. This extension file is widely used in Java projects, so it is also used for Android projects. The file has a simple key — value data structure.

KEY_1=VALUE_1
KEY_2=VALUE_2

What most developers don't know is we can use gradle.properties to store constants and override them in an environment, and we can create a specific properties file for every Android module and override their parent.

Chain overriding for Gradle properties

By default, there area properties file in Gradle Home where Gradle system lives in your environment, they are loaded automatically by Gradle— if the filename is gradle.properties— we can use these variables inapp/build.gradle just writing their names.

Below a default Gradle properties file created by Android Studio:

# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

And the following is an example ofapp/gradle.properties:

# Gradle properties for module appAPI_BASE_URL="http://example.com"
DB_FILEPATH="/common/filepath/db"
API_KEY="2FA43FADFaw432dfa"

Gradle build variables

After understanding how to store values used by Gradle, we need to understand how to use and set build variables. By default, Gradle compiles and manipulates some Android project files to inject variables.

Below are common injection/loading variables:

1 — BuildConfig.java

Gradle can add a field to BuildConfig.java file which can be easily used in the project. This file is generated automatically by Gradle system.

buildTypes {
debug {
buildConfigField "String", "DB_FILEPATH", DB_FILEPATH_VALUE
}
...

How Gradle inject values through itsbuildConfigField method to Java:

Gradle convert buildConfigField to static field in BuildConfig.java file

So, if you need to access variables in Java classes as a build variable now you can do it using Gradle buildConfigField. Because it's an automatically generated file, we can see a lot of static fields there:

public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.example.app";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0.0";

// Fields from build type go here
public static final String DB_FILEPATH = "/common/filepath/db"
}

2 — AndroidManifest.xml

Another way to use build variables from Gradle is to inject or update a Manifest file in build time.

Imagine our app has an intent filter to open when a specific URLs is called by Android System. In this case, we can set a placeholder for the manifest file with specific hostname for each build type.

Defining a placeholder for the AndroidManifest.xml in app/build.gradle:

android {
buildTypes {
debug {
manifestPlaceholders = [ hostname: HOSTNAME ]
manifestPlaceholders = [ scheme: scheme]
....
}
}
}
//Remember: HOSTNAME and SCHEME can come from gradle.properties

And using placeholder in AndroidManifest.xml:

<intent-filter ... >
<data android:scheme="${scheme}" android:host="${hostName}".../>
...
</intent-filter>

3 — System env

Finally, the most important part of Gradle build is to get variables from system environment. It's a typical usage with CI — Continuous integration — for release deployment type.

Common usage of system variables in Gradle:

android {
buildTypes {
release {
manifestPlaceholders = [ hostname: System.getEnv("HOSTNAME")]
....
}
}
}

Note that System.getEnv("HOSTNAME") is used to load variables directly from system environment.

We need to define the variable using export (UNIX):

export HOSTNAME="http://release.com"

or add to .bash_profile:

vim ~./bash_profileexport HOSTNAME="http://release.com"

After that, your project is ready to use variables from system environment, yet this approach is always recommended to be used via command line or with a CI tool.

Conclusion

Gradle already has a lot of features that we can use to improve our Android project. Defining build variables may not be important for solving problems without a good environment to develop, deliver and maintain a project.

There are a lot of aspects we need to consider about building apps:

  • What is the deployment environment?
  • How secure is our deployment environment?
  • Who does decide what can be a build configuration?
  • Who does maintain private information?

Below I created an image of a simple structure of build configuration:

Mobile deployment environment structure for mobile apps

As we can see in the image, we have 4 build types:

  • Debug: for developers who don't need to know about build variables.
  • Internal: Testing or QA. The team also doesn't need to know about variables, but needs to deploy different build types using CI.
  • Staging: External testing or validation.
  • Release: Production/App Store. All variables for this build type should be secure in deployment environment.

Github

Check a boilerplate repository that I've created using build variables as a part of a study about development environment:

https://github.com/rafamatias/android-boilerplate

References

--

--