Hybrid Global Inc.Hybrid Global Inc.


Managing Android-Build-Variant-Globals Using Gradle

25 Mar 2015

On enterprise projects, you are often required to build variants of your app such that each is configured to run against a particular test environment.

Executing these configurations into a build would typically involve one or more of the following:

  • a text file containing key-value pairs that dictate how the product will be compiled (i.e. the configuration file);
  • writing some sort of wrapper object that will parse and load the values in the configuration file;
  • adding some preprocessor directive, particularly when programming with C-based languages; OR
  • implementing some code paths dependent to the settings in the configuration file (which is likely in Java as it does not support preprocessor directives)
  • Debugging could be easily frustrating. Dealing with custom logic related to build variants into your debugging exercise will complicate things really REALLY fast.

It would be a lot easier if:

  • configuration files are located in one place; and
  • build configuration logic is decoupled from the main codeline.

Enter Gradle.

What is Gradle?

“Gradle is an advanced build toolkit that manages dependencies and allows you to define custom build logic.”

As a mobile developer, Gradle is that thing that allows me to easily manage our Android build configurations and build variants.

It’s also that thing that I found out recently that allows me to easily customize globals across the different variants without needing any sort of wrapper-code for configuration files (booyea!).

How then do I implement a global variable and set it across variants? Please read on.

Build Variants

Let’s first define a build variant. As stated before, you may need to build different versions of the same app so that you can run tests against different server environments. Let’s consider the URL of the server environment as our global.

You may be required to make a staging, test and production versions of the app. Let these be the product flavours.

You also might need to test signed and unsigned versions of these different flavors: debug and release. These are our build types.

The combination of the product flavor and the build type then yields a build variant. In my example above, the different variants are:

  • stagingDebug
  • stagingRelease
  • testDebug
  • testRelease
  • productionDebug
  • productionRelease

To create these variants, one simply needs to define the build types and product flavors in the main application module’s build.gradle file like so:

 1 apply plugin: 'com.android.application'
 3 android {
 4   compileSdkVersion 21
 5   buildToolsVersion "21.1.2"
 7   defaultConfig {
 8     applicationId "my.application.id"
 9     minSdkVersion 16
10     targetSdkVersion 21
11   }
13   buildTypes {
14     release {
15       minifyEnabled true
16       shrinkResources true
17       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
18     }
19   }
21   productFlavors {
22 	  staging {
23 	    applicationIdSuffix ".staging"
24 	  }
25 	  test {
26 	    applicationIdSuffix ".test"
27 	  }
28 	  production {
29 	    applicationIdSuffix ".production"
30 	  }
31 }

Notice that under buildType, we did not define debug because it is one of the default build types. Release is also a default build type. However, we have it declared above since we want to Gradle to build the release variants with proguard on as well as to shrink the final product’s size as much as possible.

Inside each product flavor above, we find the applicationIdSuffix. This tells Gradle to append the suffix to the declared applicationId under defaultConfig. Having different IDs between the 3 product flavors then allows me to install all in the same device/emulator.

Setting up the globals

Now that we have defined the product flavors and build types in the build.gradle file, we can now define our globals.

Let’s call the global that defines the server environment ENV.

Let’s also add a boolean global that dictates whether we show or hide an annotation in one of our views (to indicate to the user if they have text field that says STAGING, TEST or hidden for production builds). Let’s call this SHOW_VARIANT.

This is how I would define this in the build.gradle file.

 1 apply plugin: 'com.android.application'
 3 android {
 4   compileSdkVersion 21
 5   buildToolsVersion "21.1.2"
 7   defaultConfig {
 8     applicationId "my.application.id"
 9     minSdkVersion 16
10     targetSdkVersion 21
11     buildConfigField("boolean", "SHOW_VARIANT", "false")
12     buildConfigField("String", "ENV", "http://environment.com")
13   }
15   buildTypes {
16     release {
17       minifyEnabled true
18       shrinkResources true
19       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
20     }
21   }
23   productFlavors {
24     staging {
25       applicationIdSuffix ".staging"
26       buildConfigField("boolean", "SHOW_VARIANT", "true")
27       buildConfigField("String", "ENV", "http://staging.environment.com")
28     }
29     test {
30       applicationIdSuffix ".test"
31       buildConfigField("boolean", "SHOW_VARIANT", "true")
32       buildConfigField("String", "ENV", "http://test.environment.com")
33     }
34     production {
35       applicationIdSuffix ".production"
36     }
37   }
38 }

Note how we defined the fields in defaultConfig instead of declaring it inside the production-product flavor. Doing it this way ensures we always have default regardless of the build variant.

How do we use these globals in the code?

You simply reference the BuildConfig class and use dot notation to access the fields you have defined. See the usage examples below:

 1 public void getRequest(String url) {
 2   HttpClient httpClient = getHttpClient();
 3   try {
 4     // Create a GET request
 5     HttpGet httpGet = new HttpGet(BuildConfig.ENV);
 7     ... some code ...
 9   } catch (Exception e) {
11     ... some code ...
13   }
14 }
15 public void showHideAnnotation()
16   if (BuildConfig.SHOW_VARIANT) {
17     mAnnotationText.setVisibility(View.VISIBLE);
18   } else {
19     mAnnotationText.setVisibility(View.GONE);
20   }
21 }

Gradle: Live It, Love It, Learn It

As you can see, Gradle is a very powerful once you learn it.

All that wrapper code you have for configuration files – GONE!

Rummaging through multiple configuration files to figure out your build settings – NO NEED – everything is in the build.gradle file.

William of Ockham once said, “Keep things simple.” Gradle is certainly an Occam’s razor in Android development.

- JD Benito