A mockup of various releases of our app installed and running side by side

A Better Android Release Pipeline

RDX
5 min readNov 30, 2015

--

Play Store already has Alpha/Beta/Production stages. But is that enough to manage your app’s releases?

Play Store Pipelines don’t scale

Play Store’s default pipelines

With the Play Store pipelines (Alpha/Beta/Production) — you can have only one version of an app installed at any point of time, since all of these share the same Application ID. If you are using the Alpha version, you can no longer use the Production one. Most Developers of an app are also users, so I really hate this.

Use Different Apps for Different Releases

I follow a technique where I have all these releases always installed on my phone. This is how my launcher looks like for a client app that I’m presently developing (morphed and app name not revealed as I don’t have permission from the client to do that yet):

I create three different apps, and make those two pre-production apps (alpha/beta) permanently stay in Alpha mode. Only the Production app gets an actual production release. This is how my play publisher account looks like (again icons morphed out):

Use Flavors to manage Runtime Configuration

A little known Android-Gradle trick: Each flavor can have its own set of files under src/<flavor-name> which will override the src/main folder.

So you can control runtime behavior of your app using product flavors! You should be leveraging this for a lot of things. In our case, we can set different configuration variables like API tokens, backend server URLs, etc for each flavor separately. For example, let’s take these flavors:

Now, you can store different Configuration for different releases in the following ways:

Using Java constants

// Default Config.java, suitable for local development:
// src/main/java/com/myapp/Config.java
class Config {
public static String SERVER_URL = "localhost/..."
public static String API_TOKEN = "..."
}
// And then Config.java for different flavors:
// src/alpha/java/com/myapp/Config.java
class Config {
public static String SERVER_URL = "dev.myapp.com/..."
public static String API_TOKEN = "..."
}
// src/beta/java/com/myapp/Config.java
// src/prod/java/com/myapp/Config.java

Using Resource XML files

<!-- Default config XML suitable for local development -->
<!-- src/main/res/values/app_config.xml -->
<?xml version=”1.0" encoding=”utf-8"?>
<resources>
<string name="app_name">MyApp</string>
<string name="server_url">localhost/...</string>
<string name="facebook_app_id">...</string>
<string name="flurry_api_key">...</string>
<string name="twitter_consumer_key">...</string>
<string name="twitter_consumer_secret">...</string>
...
</resources>
<!-- And then different app_config.xml for different flavors -->
<!-- src/alpha/res/values/app_config.xml -->
<resources>
<string name="app_name">MyApp Alpha</string>
<string name="server_url">dev.myapp.com/...</string>
...
</resources>
<!-- src/beta/res/values/app_config.xml -->
<!-- src/prod/res/values/app_config.xml -->

Once you have the above in place, you can simply compile a flavor of your app:

./gradlew assembleAlphaRelease
./gradlew assembleBetaRelease
./gradlew assembleLiveRelease

And get an APK with the right configuration values built-in. No need to maintain different git branches, nor hand-edit configuration values for each release anymore. All releases can be built from one single codebase/branch.

Which method to use — Java or XML?

Both have their pros and cons. In the XML file method, you need to have an active Context to get the right configuration values. Pretty difficult if you’re having a lot of Plain Java objects that do the heavy lifting. In that case, a Java class helps.

On the other hand, configuration values defined in XML can be used in many places, including layout XML files, the Android manifest, etc. Its very useful for many libraries which self-configure from values in the manifest (like Fabric, Flurry, etc). For example, this is how I set the Facebook Application ID:

<!-- src/main/res/values/app_config.xml -->
<resources>
...
<string name="facebook_app_id">...</string>
...
</resources>
<!-- AndroidManifest.xml -->
<meta-data android:name=”com.facebook.sdk.ApplicationId” android:value="@string/facebook_app_id"/>

Also, resource XML files are “merged”. If you miss a specific key/value — it is taken from the parent resource XML file. Whereas in the Java class, you must make sure that you re-enter all keys for every environment.

Not Convinced?

You can use Flavors just for different configurations, without changing the Application ID! Just don’t specify the Application ID (or make them same) for all the flavors. So you can continue to use a single app over Play Store, Hockey or any other distribution mechanism you have right now, and just benefit by the configuration overrides.

What to Configure?

  • App Name: For dev/demo, make sure your app name reads “MyApp Dev”, “MyApp Demo”, etc. This makes it very clear which version of the app you are launching. Your app name could also show up in the Toolbar/ActionBar/App Switcher, so you can be pretty clear you’re not messing with the production app.
  • Launcher Icon: I inscribe the icons with “Dev/Demo”. Again it makes it better when showing up in the Launcher, ActionBar, etc.
  • Third Party API Keys (Analytics, Social, etc)
  • And the rest are whatever custom key/values that your app needs — backend server URL, auth tokens, etc.

Other Tips

  • The default src/main of your app should be targeted for Local Development. i.e. The Backend URLs should point to the local development server, etc. It needs only the Debug Build Type.
  • Flavors will use the Release Build Type, including ProGuard, Signing and everything else.
  • Flavor Naming: Use your own flavor names. I use Local (for local development), Dev (alpha), Demo (beta) to not clash with the Play Store nomenclature. Production remains production.
  • There is an emerging piece of advice to store all app configuration in the cloud, so that you can switch simple variables on the fly without having to wait for an update. But even then, you still need different releases to point to different configuration URLs. I recommend combining both techniques. Store just one “Cloud Config URL” per flavor, and then fetch it to lookup the rest.
  • As we know, Gradle doesn’t have the equivalent of Maven’s provided dependency. With Android, simply use the Android-Apt plugin. apt dependencies will be used for compilation, but not packaged into the APK. Exactly what we want.
  • Use Jake Wharton’s excellent android-sdk-manager Gradle plugin to auto-download the Android build tools, platform tools, SDK version that your app uses, Support libraries and everything else! On a fresh machine with just gradle, anyone can just run “./gradlew assembleDebug” and get everything setup in one go. Ideal use case is a CI environment, where you don’t need to login and install everything manually. (NOTE: As of writing, its not yet up-to-date with the latest Android SDK versions)

--

--

RDX

Simpleton, CI/CD Platformer at goeuro.com, ex-ThoughtWorker, Full stack engineer, Hack Java/Node/Frontend/Ruby/Docker/OSS, Practice XP/KISS/Lean