When developing Android applications you often want to be able to build different versions of the same application from a single project. For example, you may want to build a version for the QA team that uses a different Google Analytics key, or a version for user acceptance testing that uses your staging backend but disables logging. This post will discuss how you can achieve this using Gradle build variants, as well as discuss a new alternative.

What are build variants?

Build variants are a combination of a product flavor and a build type. A product flavor is a different edition of your application, for example QA, UAT, staging and production, whereas a build type is an environment setting for your application, for example the default debug and release build types that are used in Gradle.

For each build type and product flavor, Gradle allows you to set build configuration fields which are compiled into static fields on a BuildConfig class. In your build script, this takes the form of:

with the resulting BuildConfig looking like:

What are the drawbacks to build variants?

The current system of managing build configurations is pretty good, especially if your configurations are pretty stable, however there are some issues I’ve identified.

Since these values are compiled into the application itself this means that if you want to create or modify a build configuration then you need to update the build script and recompile the application. This is particularly problematic if testers, project managers etc want to do this, as it means they need to rely on a developer to make this change for them, which in turn means that even a simple change can take a while.

This system also means that you will have multiple versions of your application which you need to keep in sync. This pain can be mostly managed by your CI system, but you are still responsible for ensuring that all versions installed on the device are up to date.

Finally, the arguments to buildConfigField are all strings. This is fine for the key, but defining the type and value as a string is clunky and error prone. This issue can be somewhat mitigated by defining constants for the types and keys, however the values will still need to be strings that are then cast to their correct type.

I ntroducing Caveman

Caveman is a tool recently open-sourced by Outware that aims to solve a lot of the aforementioned problems. Caveman comes in two parts, the first being an application that allows you to create and manage your build configurations, which is designed to run alongside your primary app, and the second being a library that pulls data from the Caveman application (via a content provider), which defaults to production values if no Caveman application is found.

Caveman Application

Configuring the Caveman application is simple. The Caveman application is designed so that you can add it as a module of your current project, either as a git submodule or by copying it in.

Once you’ve added the Caveman application to your project, you can configure what build configuration properties your project requires via res/raw/configurations.json. This takes the form of:

For every property you must have a key, a human readable name and a default value for new environments. Unlike buildConfigFields, this solution allows you to use the number, boolean and string JSON data types for your default values, which in turn sets the type on the resulting Java Object. The Caveman application also lets you pre-fill environments using res/raw/environments.json, which takes the form of:

The Caveman application then uses this information to generate a list of environments that is presented upon installation.