Continuous Integration with Android

In Appaloosa’s search for continuous improvement, we decided a while back that it was about time to make big efforts to give our Android app and its 8k+ daily users the care they deserved.
2016 was a major year for our Android native store: as the team got bigger, we got the time to give our app an overhaul.
Targeting Marshmallow and then Nougat, thus giving way for new features and design revisions to be done, came with its lot of breaking changes: changing flows to support the new runtime permissions, changes in the crypto libs used by Android, the FileProvider usage enforcement…
We also began our work on the client implementation of a feature set of Android for Work which has quite complex business rules that might require quick modifications based on the feedback we get from our clients.

All these changes gave us an urgent need for safety. That’s why it was time to extend our development practices to our Android app!
You may have already read about them. But now, things are a bit different for our Android dev process.

Specifically, each feature, bug fix or technical chore is done on its own branch that will be merged into another branch called “develop” following a successful pull request.

For a pull request to be merged, it must meet the following requirements, or, in other words, a “Definition of Done” :

  • Feature/Bugfix completed
  • Code review done
  • Release build is working (aka: ensure that Proguard did not mess something up)
  • Triple-checked locales
  • Version code and name incremented
  • PivotalTracker (our project management tool) status of current task is updated
  • Tests passed with flying colors!

Today, we are focusing on this last step, using CircleCI.
What you need: a tested Android app (we won’t go over this today :) ) whose code is on Github or Bitbucket and a paid Firebase account if you plan on running your Espresso instrumentation tests. CircleCI is free if you choose to make one build at a time.

CircleCI setup

Adding a new build for a Github project to CircleCI should be a breeze: you just have to link your GitHub account or organisation through the signup flow or the “ADD PROJECTS” view and choose your Android project from the next screen.

You will next receive a mail from GitHub: CircleCI automatically added a deploy key to your project!

This will also automatically run a first build. Don’t worry, it will probably fail as you have not specified anything related to your project, yet!

To begin, you can now click the wheel thingy next to your project’s name in the “BUILDS” section.

By default CircleCI runs a build for each new commit on any branch ; but, at Appaloosa, we prefer running a build only when a pull request is made instead, as many devs can work on multiple branches. Triggering a build for each branch whether or not a PR was made would be an option, but it’d be advised to check your plan pricing to adjust the building parallelism. That is not the solution we chose for now.

You can modify this setting from the “Advanced Settings” screen. The “Auto-cancel redundant builds” toggle is also an interesting one that can save you a lot of time.

So, that’s pretty much it as to how CircleCI will interact with your GitHub project ; now, let’s have a quick tour of Firebase to fetch everything you need to run and test your code on a real Android device! (emulator are also available, but they are not as cool…)

Optional but recommended: Firebase configuration

(Big kudos to CircleCI's documentation on this matter.)

Go to https://console.firebase.google.com. If that’s a fresh start for you, create a new project.

Once on your project’s dashboard, make sure on the bottom left of your screen that you’re on the “Blaze” pricing plan. It is needed in order to run your tests on the Google Test Lab!

Getting access to Firebase from CircleCI

Still from your Firebase's dashboard, go to your project’s settings and go to Permissions.
That will bring you to a quite intimidating interface known as the Google API Console where we just want to create a new service account for CircleCi to push your test artefacts and fetch its results. Create a service account from the eponymous section, to whom you may give the convenient name “circle-ci”, but the important part here is to give it the Project Editor role and ask for JSON private key download.

Setting up your environment variables

We need to use this file to give CircleCI access to Firebase, but we don't want to version it if your code repository is public, for example.
Go back to your CircleCI's project settings and to the "Environment Variables" section. There, we will add 3 environment variables that will later be used for your build:

  • FIREBASE_PROJECT, with the name of your Firebase's project.
  • FIREBASE_SERVICE_ACCOUNT. Here, paste the output of the base64 of your service account JSON.
  • TEST_LAB_BUCKET. This one is a bit tricky to get. The way I got it is to launch a dummy Robo test from the Firebase dashboard, going to the tests' result (whether they are successful or not does not matter) and click on the "VIEW SOURCE FILES" button. This will redirect you to your test bucket. The value we want is in the URL, which will look like something like this:
https://console.cloud.google.com/storage/browser/YOUR_TEST_LAB_BUCKET/timestamp/device

circle.yml

This file, which must be located at your project’s root, describes the steps performed by CircleCI for a build.
Here’s ours, we will go through it section by section.

## Gradle opts + JDK 8
machine:
environment:
_JAVA_OPTIONS:
"-Xms512m -Xmx1024m"
GRADLE_OPTS:
'-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
java:
version:
oraclejdk8
## Customize dependencies
dependencies:
pre:
- echo "y" | android update sdk --no-ui --all --filter tools,build-tools-25.0.2,android-25,extra-google-m2repository,extra-google-google_play_services,extra-android-m2repository
- sudo pip install -U crcmod
post:
- ./gradlew assembleVanillaDebug assembleVanillaDebugAndroidTest -PdisablePreDex
- echo $FIREBASE_SERVICE_ACCOUNT | base64 --decode > ${HOME}/client-secret.json
- sudo /opt/google-cloud-sdk/bin/gcloud --quiet components update
- sudo /opt/google-cloud-sdk/bin/gcloud config set project $FIREBASE_PROJECT
- sudo /opt/google-cloud-sdk/bin/gcloud --quiet components install beta
- sudo /opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file ${HOME}/client-secret.json
test:
override:
- ./gradlew testVanillaDebugUnitTest -PdisablePreDex
- echo "y" | sudo /opt/google-cloud-sdk/bin/gcloud beta test android run --app app/build/outputs/apk/app-vanilla-debug.apk --test app/build/outputs/apk/app-vanilla-debug-androidTest.apk --device-ids Nexus5 --os-version-ids 22 --locales en --orientations portrait
post:
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
- find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
- sudo /opt/google-cloud-sdk/bin/gsutil -m cp -r -U `sudo /opt/google-cloud-sdk/bin/gsutil ls gs://$TEST_LAB_BUCKET | tail -1` $CIRCLE_ARTIFACTS/ | true

For the machine section, the Java version override is only required if your project takes profit of the newer Java 8 JDK.
If your Gradle tests failed with an “OutOfMemory” exception, try capping the memory usage with the _JAVA_OPTIONS and GRADLE_OPTS.

The pre section of dependencies runs before CircleCI’s inferred commands.
Here we need to have a fully updated Android SDK (if your project is also up-to-date, of course). This is all done in CLI, so echo “y” auto-accepts the numerous Android SDK’s terms of use prompts. You then have to specify the needed dependencies (target SDK, build-tools version, etc…).
crcmod is needed by Google Cloud tools, only when passing your tests through Firebase.

Next, in the post section of dependencies, your test artefacts are built using Gradle.
The rest is also needed for Firebase to run the tests: CircleCI’s gcloud tool is updated and set up using your environment variables.

In test’s override section, we tell CircleCi exactly what to do, meaning, running the unit tests and the Espresso tests.

As you can see, you have to deduce your test APKs filenames following their respective Gradle task. Here, :app:assembleVanillaDebug outputs app-vanilla-debug.apk and :app:assembleVanillaDebugAndroidTest gives app-vanilla-debug-androidTest.apk. They will always be built in app/build/output/apk.
You also have to specify on which device(s) configuration(s) you want your tests to run. To see what’s on the menu, give gcloud a go and run gcloud beta android devices list.

Note that the build will fail at the first error encountered, so you won’t have to wait for the whole thing. No need to go through all the steps if dependencies are not met or the unit tests don’t pass, for example.

And for the final stage, in the post section of test, we “simply” fetch your tests results from your Google test bucket. Note that $CIRCLE_ARTIFACTS is provided.

And then, you can only hope for the best…

What if my tests fail?

CircleCI has a neat interface, where you can see at a glance where something went wrong.
Fixing your unit tests should be easy as errors are shown quite clearly, but what if your Espresso tests fail?

Don’t worry, they’ve got that on tape.

Go to the top of your failed build's page, and to the "Artifacts" tab, you can find your tests' video there.

CircleCI's build artifacts

If you want to browse a more readable interface, via Google Cloud Platform, go to your failed build and scroll down to the step in red.

You will see an URL for your tests’ bucket that will send you to a nice interface on Google Cloud Platform where you can browse results.

But what you’re looking for is the “All good” update on your Pull Request.

Saving a few minutes on your builds (at a cost)

Our Android build at Appaloosa took around 18minutes. Leveraging CircleCI's cache system, we managed to quicken our build times by 2~3 minutes. 
Back to your circle.yml file, replace:

dependencies:
pre:
— echo “y” | android update sdk — no-ui — all — filter tools,build-tools-25.0.2,android-25,extra-google-m2repository,extra-google-google_play_services,extra-android-m2repository
— sudo pip install -U crcmod

by:

dependencies:
cache_directories:
— ~/android
 pre:
— sudo pip install -U crcmod
override:
— ./circle_dependencies.sh

and change the $ANDROID_HOME environment variable:

machine:
environment:
ANDROID_HOME:
/home/ubuntu/android

Here are the contents of circle_dependencies.sh (which must have Unix execution permissions):

#!/bin/bash
export PATH=”$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH”
if [ ! -d $ANDROID_HOME ]; then
mkdir $ANDROID_HOME
cp -r /usr/local/android-sdk-linux/* $ANDROID_HOME &&
echo “y” | android update sdk — no-ui — all — filter tools,build-tools-25.0.2,android-25,extra-google-m2repository,extra-google-google_play_services,extra-android-m2repository
fi

CircleCI will now back up what you need from the Android SDK and restore it at each build from the last successful one.
This unfortunately did not save as many time as expected as the restoring is quite slow, and this method also has the usual drawbacks of caching.
Your build will fail when you update one of your Android dependencies version. To fix it, just retry the build without cache, from CircleCI's interface.

What’s next?

Wouldn’t it be nice if, when your new feature is ready and your code integrated with its tests passing with flying colors, to deploy your newly merged branch to your user’s devices? If your app’s meant for the consumer market, have a look at Triple-T’s Gradle Play Publisher.
But if your app is meant for a more private audience, why not give Appaloosa’s APK publishing APIs a try?

Written by Robin Sfez, Lead Android Developer at Appaloosa.

Want to be part of Appaloosa? Head to Welcome to the Jungle.