Continuous integration for Android projects
Tags: circleci, android, cicd
Published: 2022-11-02 17:47:23

---
title: Continuous integration for Android projects
published: true
date: 2022-08-26 19:00:00 UTC
tags: circleci,android,cicd
canonical_url: https://circleci.com/blog/building-android-on-circleci/
cover_image: https://production-cci-com.imgix.net/blog/media/Tutorial-Beginner-RP.jpg
---

CircleCI is popular among Android developers for several reasons: it’s quick to get started, fast to execute your builds with high parallelism, (whether native, cross- or multi-platform), and even supports running Android emulators right from CircleCI with our Android machine images.

This article will show you how to build and test Android applications for an example project on the CircleCI platform. The full source code is available [on GitHub - CircleCI-Public/android-testing-circleci-examples](https://github.com/CircleCI-Public/android-testing-circleci-examples), and you can find the working pipelines [in the corresponding CircleCI project](https://app.circleci.com/pipelines/github/CircleCI-Public/android-testing-circleci-examples).

To get the most out of this article you should have

- Working knowledge of Android development
- Familiarity with Android tooling and testing frameworks
- Ability to use the Gradle build system

## What’s available in CircleCI for Android

We have made a few tools available for you for building Android:

- Docker image
- Android machine image
- Android orb

### Docker image

The Docker image is the main executor teams use. It runs in a Docker container and so is very fast to start, and contains everything you might need to get started building Android projects. The full list of preinstalled tools can be found [in the documentation](https://circleci.com/developer/images/image/cimg/android). The images also ship with variants that have Node.js, the Android NDK, and browser tools preinstalled.

### Machine image

The biggest change is the [new Android machine image](https://circleci.com/developer/machine/image/android). This is a virtual machine image, so takes a bit more time than the Docker image mentioned above, but it contains all the tools you may need to build your Android applications, including the SDK, Google Cloud tools for Firebase, [Python](/features/python/), Node.JS, and Fastlane. The biggest change however is the support for nested virtualisation, which allows you to run Android emulators from within this machine image.

Android machine images support nested virtualization, which allow running the x86 emulators in CircleCI jobs. If you have been developing for Android since the early years of the platform, you may remember that emulators were very slow. Then x86 emulators started supporting Intel’s Hyper-V hypervisors, which lets the emulator use the resources of the host computer directly. Using resource directly like this makes the emulators able to run much, much more smoothly and quickly.

For a long time, support was confined to our local hardware. Limited support on CircleCI slowed down emulators and made it difficult to run UI tests. Nested virtualization solves this problem by adding support for fast emulation to a massively reproducible CI/CD environment, making all the benefits of that available.

### Android Orb

Finally there is the CircleCI Android orb which provides easy access to both images as executors, as well as handy jobs and commands for installing SDKs, running emulators, caching, testing, and more. You can read more about what the orb offers in the orb docs [at this link](https://circleci.com/developer/orbs/orb/circleci/android).

## Introducing the Android CI demo project

I have created a [demo project](https://github.com/zmarkan/android-testing-circleci-examples), which is a fork of Google’s own Android Testing Codelab sources. The Codelab is a simple TO-DO list manager application and contains a combination of unit tests and instrumentation tests, with and without the UI. The project is also available to view [on CircleCI](https://app.circleci.com/pipelines/github/zmarkan/android-testing-circleci-examples). If you open it in Android Studio, make sure to switch to the `Project` view. Project view lets you view the `.circleci/config.yml` file we will be reviewing.

The project has a single `app` module, with the default `debug` and `release` variants. The `test` directory contains unit tests, while the `androidTest` directory contains instrumentation tests. The full CircleCI configuration is included, and I will be referring to it in this article. The configuration is located in `.circleci/config.yml`.

## Building and testing CI for the sample Android app

Our CI process must do three things:

1. Run unit tests inside a virtual machine
2. Run instrumentation tests on the emulator
3. Build a release version of the app

We will run unit tests and instrumentation tests side by side. If both types of tests are successful, we can assemble the release build. We will also add a condition to continue on to the release build _only_ if new work has been commited to the “main” branch, and only following successful emulator tests on every Android version from 23 to 30. When our build meets this condition, we will have solid assurance that our application will work on many different Android devices.

**Note:** _As I mentioned before, everything from this point on will refer to the CircleCI configuration file in `.circleci/config.yml`._

### Using the CircleCI Android orb

The first step for creating our [CI pipeline](https://dev.to/ronpowelljr/what-is-a-ci-cd-pipeline-4lc5-temp-slug-7512512) is to use the Android orb, which contains many of the steps already pre-written for you. The latest version is available in the [orb docs](https://circleci.com/developer/orbs/orb/circleci/android).

After the orb is defined, the script specifies these jobs:

- `unit-test`
- `android-test`
- `release-build`

One workflow, `test-and-build`, is also included.

```
version: 2.1

orbs:
  android: "2.1.2"

jobs:
  unit-test:
    ...
  android-test:
    ...
  release-build:
    ...
    # We'll get to this later

workflows:
  test-and-build:
    ...
  # We'll get to this later

```

### Running unit tests

Our `unit-test` job contains an executor from the Android orb: `android/android-docker`. As mentioned this runs it in a Docker container which is extremely fast to start up.

In the `steps` stanza, there are a few things to note. First, we run `android/restore-gradle-cache` Gradle cache helps store our dependencies and build artifacts. After running the test command, we also run `android/save-gradle-cache` and `android/save-build-cache` to make sure subsequent builds run faster by using the cache. These all come from the Android orb, as indicated by the prefix `android/`.

We could also modify what gets cached by passing the `find-args` parameter to `restore-gradle-cache`. A great advanced example can be found [in this open source project](https://github.com/owntracks/android/blob/59f13afa93a27904e739493d96af64ab36f783eb/.circleci/continue-config.yml#L17-L44).

The main build step happens in the `android/run-tests` command, where we call `./gradlew testDebug` within a `test-command` parameter. This command runs all unit tests for the `debug` build variant. As a bonus, this command comes with a default retry value to help you avoid test flakiness.

```
jobs:
  unit-test:
    executor:
      name: android/android-docker
      tag: 2022.08.1
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/run-tests:
          test-command: ./gradlew testDebug
      - android/save-gradle-cache
      ...

```

### Running Android tests on the emulator

Our `android-test` job is the one that uses the new Android emulator capability. The job uses the same Android machine executor as the `build` job. All subsequent jobs will use the same executor, and the cache restoration steps are also the same.

Emulator requires us to run on a machine image, so we specified `android/android-machine` as the executor. To improve build times, we specify the `resource-class` as xlarge. You can also run it on a `large` executor, but I have found that the lack of resources causes slower build times. I recommend that you do some experimentation on your own to find the right size executor for your project.

The next step is even shorter; we need just the `android/start-emulator-and-run-tests` command. This command does exactly what it says: it starts the specified emulator and runs the tests. We pass in the `test-command` again as the parameter (it uses the `android/run-tests` command). The `system-image` is the fully qualified Android emulator system image. We currently bundle our Android machine images back to SDK version 23. You could install more by using the `sdkmanager` tool.

```
jobs:
  android-test:
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/start-emulator-and-run-tests:
          test-command: ./gradlew connectedDebugAndroidTest
          system-image: system-images;android-30;google_apis;x86
      ...

```

You could code all the emulator setup steps manually using the commands specified in the Android orb: `create-avd`, `start-emulator`, `wait-for-emulator`, and `run-tests`.

### Storing the test results

There is more to testing than just running the tests, as we can get a lot of value from seeing exactly what has failed from right inside the CircleCI dashboard. For that we can use `store_test_results` functions built into the CircleCI platform, which will show our passing (or failing) builds.

The steps differ slightly between unit tests and instrumentation tests, as each of their respective Gradle tasks stores the tests in a different place. For unit tests the tests will be in `app/build/test-results`, and for instrumentation tests they will be in `app/build/outputs/androidTest-results`.

We suggest you create a new `Save test results` step, which uses the `find` utility and copies all test results files into a common location - like `test-results/junit`, and then store it from there. Also make sure to add the `when: always` parameter to the step, so the step runs regardless of whether the tests above succeed or fail.

### Storing unit tests

For this project, we want the XML based results. Calling `store_artifacts` makes those results parseable on the CircleCI platform. You can also store the HTML output if you prefer.

```
...
- android/run-tests:
  test-command: ./gradlew testDebug
- run:
    name: Save test results
    command: |
        mkdir -p ~/test-results/junit/
        find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \;
    when: always
- store_test_results:
    path: ~/test-results
- store_artifacts:
    path: ~/test-results/junit

```

### Storing instrumentation tests

There are different regex command for different test types. The rest of the steps to store test results are identical to the unit tests example.

```
- android/start-emulator-and-run-tests:
        test-command: ./gradlew connectedDebugAndroidTest
        system-image: << parameters.system-image >>
    - run:
        name: Save test results
        command: |
          mkdir -p ~/test-results/junit/
          find . -type f -regex ".*/build/outputs/androidTest-results/.*xml" -exec cp {} ~/test-results/junit/ \;
        when: always
    - store_test_results:
        path: ~/test-results
    - store_artifacts:
        path: ~/test-results/junit

```

### Making a release build and storing the artifacts

For this project, we need to store the application we are building. To complete this part of the process, we use the `release-build` job, which has two parts of note. A `run` step calls `./gradlew assembleRelease`. Then, `store_artifacts` points to where the built APK is. For a full CI/CD project, you could sign and upload that APK to a beta distribution service. You could even create a release on Play Store with Fastlane. Both of those activties are out of scope for this article, though.

```
jobs:
  ...
  release-build:
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/restore-build-cache
      - run:
          name: Assemble release build
          command: |
            ./gradlew assembleRelease
      - store_artifacts:
          path: app/build/outputs/apk/release

```

### Running tests on multiple Android versions simultaneously

The Android platform evolves all the time, and each version has its own bugs and pecularities. I am sure you have encountered some of them. A good way to avoid undefined behaviour across different versions of Android is to test on as many of them as you can. The CircleCI job matrix feature makes it easier to run jobs on multiple versions.

To use the job matrix, add a parameter to the job you want to test on multiple versions. Our `android-test` job does that using the `system-image` parameter. We have also specified a default value for the parameter, so you can run the job without it.

To use the parameter, specify where you need its value by placing it within pairs of angle brackets - `<< >>`:

```
 android-test:
    parameters: # this is a parameter
      system-image:
        type: string
        default: system-images;android-30;google_apis;x86
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/start-emulator-and-run-tests:
          test-command: ./gradlew connectedDebugAndroidTest
          system-image: << parameters.system-image >> # parameter being used

```

To pass values to the parameter in the workflows, use the `matrix` parameter of the job we are calling.

```
workflows:
  jobs:
  ...
  - android-test:
      matrix:
        alias: android-test-all
        parameters:
          system-image:
            - system-images;android-30;google_apis;x86
            - system-images;android-29;google_apis;x86
            - system-images;android-28;google_apis;x86
            - system-images;android-27;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
      name: android-test-<<matrix.system-image>>

```

### Making each run faster by caching

Alongside building our application, installing dependencies, and running tests, we often want to leverage cache as well. This allows us to do repeated tasks less often. Android and especially Gradle provides an excellent mechanism for this - [the Gradle Build cache](https://docs.gradle.org/current/userguide/build_cache.html).

This lets you skip pre-built parts of the application.

### Choosing what runs when

During development, we create many commits, and developers should be encouraged to push them to a remote repository as often as possible. Each one of these committs creates a new CI/CD build, though, and we might not need every test run on all possible devices for a single commit. On the other hand, if the `main` branch is our primary source of truth, we want to run as many tests on that as possible, to ensure it is always working as intended. We might want to initate the release deployment only on `main`, and not on any other branch.

We can use CircleCI’s `requires` and `filters` stanzas to define workflows that fit our process. Our `release-build` job `requires` the `unit-test` jobs. That means the `release-build` is put in the queue until all the `unit-test` jobs have passed. Only then is the `release-build` job run.

We can use `filters` to set up logical rules for running a particular job only on a specific branch or git tag. For example, we have a filter for our `main` branch that runs the `android-test` with a full matrix of emulators. In another example, the `release` build triggers only on `main`, and only when its two required jobs have run successfully.

```
workflows:
  test-and-build:
    jobs:
      - unit-test
      - android-test: # Commits to any branch - skip matrix of devices
          filters:
            branches:
              ignore: main
      - android-test: # Commits to main branch only - run full matrix
          matrix:
            alias: android-test-all
            parameters:
              system-image:
                - system-images;android-30;google_apis;x86
                - system-images;android-29;google_apis;x86
                - system-images;android-28;google_apis;x86
                - system-images;android-27;google_apis;x86
                - system-images;android-26;google_apis;x86
                - system-images;android-25;google_apis;x86
                - system-images;android-24;google_apis;x86
                - system-images;android-23;google_apis;x86
          name: android-test-<<matrix.system-image>>
          filters:
            branches:
              only: main 
      - release-build:
          requires:
            - unit-test
            - android-test-all
          filters:
            branches:
              only: main # Commits to main branch

```

## Improving your Android application CI process

The steps I have described in this article are only a beginning. There are many ways you can improve the flow. Here are just a handful of ideas:

### Build once, run many times

If you are an experienced Android developer you know that the `connectedAndroidTest` will always run the entire build process from the start. Using CircleCI, we could build the entire application and tests once, pass the artifacts down to following jobs, and simply run the tests on the emulators. This process could potentially save several build minutes per job run.

To make this happen, add three commandline steps in each emulator run: install the app, install the instrumentation app, and run the instrumentation tests. For our application, we would enter:

```
adb install app/build/outputs/apk/debug/app-debug.apk  
adb install app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk  
adb shell am instrument -w com.example.android.architecture.blueprints.reactive.test/androidx.test.runner.AndroidJUnitRunner 

```

It is true that this code sends output to the the command line. That is not the tidy test output that Gradle provides. If you are motivated to make the output look better, [this StackOverflow topic](https://stackoverflow.com/questions/33896315/how-to-retrieve-test-results-when-using-adb-shell-am-instrument) might help you.

### Running on real devices

Emulators are one tool, but real world devices are often (sadly) much different. There is nothing that can replace a real-world device test. For that you can use a solution offering real devices in the cloud, like Sauce Labs or Firebase Test Lab.

### Deploying to beta testers, and releasing the application

This build and test example project is just one part of a wider CI/CD story. You can take your CI/CD process much further. For example, you can automatically release new versions of your applications right to the Play Store. CircleCI has a handful of guides to help you. [This tutorial](https://dev.to/ronpowelljr/continuous-integration-and-deployment-for-android-apps-with-fastlane-36j7-temp-slug-5167162) is a great place to start. The [official Fastlane documentation](https://docs.fastlane.tools/getting-started/android/release-deployment/) is another helpful resource.

## Conclusion

In this article, I described building an Android application with CircleCI, and testing it. For our sample project, we used unit tests and a matrix of Android platform emulators, using the new CircleCI Android orb and machine images. We also touched on how to store and display test results, and how to orchestrate workflows to run tests will not give us the tidy test output that Gradle provides, and all output is on a portion of devices.

Do let me know if there is another Android topic you might want me to explore, either in an article or a live stream. Contact me on [Twitter - @zmarkan](https://twitter.com/zmarkan/).