Android Continuous Integration and Deployment Tutorial
Tags: android, tutorial, cicd, devops
Published: 2020-11-10 13:25:42

---
title: Android Continuous Integration and Deployment Tutorial
published: true
description: "How high-performance CI/CD can double your Android development productivity."
canonical_url: https://semaphoreci.com/blog/android-continuous-integration-deployment
tags: android,tutorial,cicd,devops
---

As we work on an Android app, we test our code regularly — hopefully with the help of automated tests. And when it's time to release a new version, we assemble the source code into an APK or AppBundle format which we deploy into the Google Play Store.

It seems like a simple process, but there are several other steps involved, like signing the package and updating the version code and name.

After a while, going through this process manually gets very tedious and tiring, so how can we automate this process?

## Enter Semaphore

[Semaphore](https://semaphoreci.com/) is the fastest [continuous integration (CI)](https://dev.to/markoa/continuous-integration-explained-59f9) and [continuous delivery (CD)](https://dev.to/semaphore/ci-cd-continuous-integration-delivery-explained-75l) service which helps you automate your workflow by building, testing, and deploying your Android project. This way you can focus on what matters: **your code**.

In this tutorial we will explain how to set up Semaphore for a native Android project.

## Prerequisites
If you wish to follow this tutorial you'll need the following:

- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and a
  [GitHub](https://github.com/) account.
- A Semaphore account. You can get one for free at
  [semaphoreci.com](https://semaphoreci.com/).
- A [Google Play Developer account](https://play.google.com/apps/publish) to
  publish the app.

## How to finish this tutorial in 1 minute

To get started _really_ quickly, do this:

In Semaphore, follow the link in the top navigation to **create a new project**.

From the examples list, **select Android (Kotlin)**.

Semaphore will fork a demo project:

{% github semaphoreci-demos/semaphore-demo-android no-readme %}


And set up a complete CI/CD pipeline for you in less than a minute:

![The Android CI/CD pipeline on Semaphore](https://dev-to-uploads.s3.amazonaws.com/i/ckuqrgngfza3t0nldc5u.png)

That’s it. You’re done. \*_mic drop_\*

The demo is a simple app that lists all the open source [Semaphore demo projects](https://github.com/semaphoreci-demos/) on GitHub. It includes unit and integration tests, and has the [Gradle Play Publisher](https://github.com/Triple-T/gradle-play-publisher/) plugin set up that adds a Gradle task to publish your app to the store.

You can see this app live [on the Play Store](https://play.google.com/store/apps/details?id=com.semaphoreci.demo).

However, I know that you’re most likely here to learn how to set up continuous integration for your own Android project. So let's see how to do that using the demo project as a foundation.

Adapting these instructions to your project should not be too difficult. But if you do run into a challenge, feel free to ask a question in the comments section. 💬

## Configuring CI/CD on Semaphore from scratch

After creating your Semaphore account you'll need to [create your first Semaphore project](https://docs.semaphoreci.com/guided-tour/creating-your-first-project/). Your Semaphore configuration is stored in a directory at the root of your repository called `.semaphore`. The entry point of your [CI/CD pipeline](https://dev.to/markoa/ci-cd-pipeline-a-gentle-introduction-2n8k) is defined by the `semaphore.yml` file.

In this tutorial we will explain how to configure an Android CI/CD workflow by reading the configuration files. That way you’ll learn the most about Semaphore and Android continuous integration in general.

But **you don't have to write YAML by hand**. You can also use Semaphore’s [graphical workflow builder](https://semaphoreci.com/blog/cicd-workflow-builder) for a more visual approach.

> 👉 [See this public workflow](https://semaphore-demos.semaphoreci.com/workflows/a89a9d45-3bbe-4edb-a84a-08748a17d61b?pipeline_id=e33b9a63-a85e-4875-be01-65e4b4bf7cde) to get a taste of Semaphore’s UI and the final project running.

## Continuous integration: the safety net for your Android project

We want to perform regular checks on the code by running a linting tool and by running the project tests, and we want those to be automated. Let's check on what can we do on Semaphore and edit the `semaphore.yml` configuration file.

> 👉 You can see the full file of the demo project [on GitHub](https://github.com/semaphoreci-demos/semaphore-demo-android/blob/dev/.semaphore/semaphore.yml).

### Agent: where your code runs

```yml
agent:
  machine:
    type: e1-standard-2
    os_image: ubuntu1804
  containers:
    - name: main
      image: registry.semaphoreci.com/android:29
```

Here we're defining the environment where the build will run. We want a Linux machine with 2 vCPUs, 4 GB of RAM that runs Ubuntu 18.04 OS. We also want to run our jobs in a container that has the latest (at the time of writing) Android SDK installed, provided by [Semaphore Container Registry](https://docs.semaphoreci.com/ci-cd-environment/semaphore-registry-images/).

> Semaphore always caches the [two latest stable versions of Android SDK](https://docs.semaphoreci.com/ci-cd-environment/android-images/) to ensure that the machine is ready in no time, making our builds faster.

### Global job configuration

```yml
global_job_config:
  env_vars:
    - name: ADB_INSTALL_TIMEOUT
      value: "10"
  secrets:
    - name: android_keys
  prologue:
    commands:
      - checkout
      - mv ~/release-keystore.jks ~/$SEMAPHORE_GIT_DIR/app/release-keystore.jks
      - cache restore gradle-wrapper
      - cache restore gradle-cache
      - cache restore android-build
```

Semaphore's [Global Job Config](https://docs.semaphoreci.com/reference/pipeline-yaml-reference/#global_job_config) allows us to define a set of configurations that are shared across the whole pipeline. This is very helpful since it allows us to define everything in one
place instead of repeating it in every task.

Here we're defining a new environment variable `ADB_INSTALL_TIMEOUT` so that
`adb` doesn't timeout while we're setting up the emulator and running the
integration tests.

We're also saying that we want to use the [global secrets](https://docs.semaphoreci.com/essentials/using-secrets/) `android_keys` that will allow us to use our keys and passwords defined as secrets, as environment variables in the build.

Specifically on the [demo project](https://github.com/semaphoreci-demos/semaphore-demo-android) we're saving the following secrets on `android_keys`:

- `release-keystore.jks`: the keystore file used to sign our release builds;
- `RELEASE_KEYSTORE_PASSWORD`: our keystore password;
- `RELEASE_KEY_ALIAS`: our keystore key alias;
- `RELEASE_KEY_PASSWORD`: our keystore key password;
- `service-account-key.json`: the [service account
  key]((https://github.com/Triple-T/gradle-play-publisher/#service-account)),
  used to deploy the demo app to the [Play
  Store](https://play.google.com/store/apps/details?id=com.semaphoreci.demo).

This is the ideal place so save sensitive data, such as keys. Be sure to use [secrets](https://docs.semaphoreci.com/essentials/using-secrets/) on your builds instead of pushing sensitive information into your git repository.

The [prologue](https://docs.semaphoreci.com/reference/pipeline-yaml-reference/#prologue-and-epilogue) section allow us to run a set of commands before the job begins. We're checking out the code from the git repository, moving our keystore file to the correct path and restoring our cache so builds are faster.

### Pipeline blocks

Here's where we can add all the build tasks we want to run. We define the jobs
of each task, and the commands we want to execute in this pipeline. Let's start
at the top.

#### Build Block
```yml
- name: 'Build'
  task:
    jobs:
      - name: 'Build Project'
        commands:
          - ./gradlew bundle
    epilogue:
      on_pass:
        commands:
          - cache clear
          - cache store gradle-wrapper ~/.gradle/wrapper
          - cache store gradle-cache ~/.gradle/caches
          - cache store android-build ~/.android/build-cache
```

The first thing we want to do is to build our project by running `./gradlew
bundle` to check that everything is ok. Then, in the [epilogue](https://docs.semaphoreci.com/reference/pipeline-yaml-reference/#prologue-and-epilogue), that will only run if this job is successful, we are refreshing the cache.

#### Verification Block
```yml
- name: 'Verification'
  skip:
    when: "pull_request !~ '.*'"
  task:
    jobs:
      - name: 'Analyze Code'
        commands:
          - ./gradlew lint
      - name: 'Unit Tests'
        commands:
          - ./gradlew test
      - name: 'Integration Tests'
        commands:
          # Install the required tools and the emulator itself
          - sdkmanager "platform-tools" "platforms;android-29" "build-tools;30.0.0" "emulator"
          # Install system images for the emulator
          - sdkmanager "system-images;android-29;google_apis;x86"
          # Create an emulator with the installed system images
          - echo no | avdmanager create avd -n test-emulator -k "system-images;android-29;google_apis;x86"
          # Start the emulator with no audio, boot animation, window, and with GPU acceleration off
          - emulator -avd test-emulator -noaudio -no-boot-anim -gpu off -no-window &
          # Wait for the emulator to boot completely
          - adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'
          # Dismiss the emulator lock screen and wait 1 second for it to settle
          - adb shell wm dismiss-keyguard
          - sleep 1
          # Disable window and transition animations. This is required to run UI tests correctly
          - adb shell settings put global window_animation_scale 0
          - adb shell settings put global transition_animation_scale 0
          - adb shell settings put global animator_duration_scale 0
          - ./gradlew connectedAndroidTest
    epilogue:
      always:
        commands:
          - artifact push job --expire-in 2w --destination reports/ app/build/reports/
```

Now the real work begins. This is the verification block where we're doing a
lint check and running our tests. We also have a [skip condition](https://docs.semaphoreci.com/reference/pipeline-yaml-reference/#skip-in-blocks) that tells Semaphore that we only want to run this block when a pull request is
open.

Here's what's happening:
- `./gradlew lint` runs the [Android
  Lint](https://developer.android.com/studio/write/lint) that analyzes the code
  to check if there's any problems.
- `./gradlew test` will run all the [Unit
  Tests](https://developer.android.com/studio/test/command-line) on our project.
- `./gradlew connectedAndroidTest` will run the [Integration
  Tests](https://developer.android.com/studio/test/command-line), after setting
  up the emulator.

> Usually, this is enough to verify the code and should always be checked/ran before merging and deploying a new app version so that we're sure that we're not introducing bugs or bad code into the project. You can add more [jobs](https://docs.semaphoreci.com/reference/pipeline-yaml-reference/#jobs) here if you need to perform additional checks.

After everything completes, on the epilogue, we're uploading our test reports as [artifacts](https://docs.semaphoreci.com/essentials/artifacts/) so if something goes wrong with our tests we can consult the reports to see more details, making our lives a bit easier.

> 👉 It's important to note that every thing will run in parallel making Semaphore builds extremely fast.

### Promotions
[Promotions](https://docs.semaphoreci.com/guided-tour/deploying-with-promotions/) are used when we want to branch out our pipeline when certain conditions are met. This is **perfect for deployments**.

```yml
promotions:
  - name: Internal Deploy
    pipeline_file: deploy-internal.yml
    auto_promote:
      when: "result = 'passed' AND branch = 'dev'"
  - name: Beta Deploy
    pipeline_file: deploy-beta.yml
    auto_promote:
      when: "result = 'passed' AND branch = 'master'"
```

We have two other pipelines, and like so two promotions. We want to promote to the pipeline that deploys our app to the internal track when we push new code to `dev`, and to the beta track when we push new code to `master`.

![Verification pipeline](https://dev-to-uploads.s3.amazonaws.com/i/92rhsqpgbll3azfxknnz.png)

Here's the pipeline in it's full glory. Notice how quick it is.

## Continuous Deployment: you can do it with Android too!

After verifying that everything is ok with our project we want to automate deploys.

> Check the full configurations for the [internal pipeline](https://github.com/semaphoreci-demos/semaphore-demo-android/blob/dev/.semaphore/deploy-internal.yml) and [beta pipeline](https://github.com/semaphoreci-demos/semaphore-demo-android/blob/dev/.semaphore/deploy-beta.yml).

Most of the configuration is the same as explained before. We only added a command to the global config to move the service account key to the correct path as that is needed to deploy the app and added a new block.

#### Deployment Block
```yml
- name: 'Internal Deployment'
  task:
    jobs:
      - name: "Deploy to Play Store - Beta Track"
        commands:
          - ./gradlew publishReleaseBundle --track beta
```

Here we're simply running the publish task that was created by the [Google Play Publisher](https://github.com/Triple-T/gradle-play-publisher) gradle plugin. The only thing that differs from the internal pipeline is the argument `track` that specifies that we want to deploy to the `internal` track instead. Also, we don't have to worry about incrementing the `versionCode` every time because we're using the Semaphore workflow number [environment variable](https://docs.semaphoreci.com/ci-cd-environment/environment-variables/) for that, that is always incremented when new a new build is running.

## On to you
Remember to check the complete demo project on [GitHub](https://github.com/semaphoreci-demos/semaphore-demo-android) for a complete example and be sure to explore the code on your own. Feel free to fork it and use it as the base of your next [Semaphore](https://semaphoreci.com/) project.

There are still somethings that you can do to improve the workflow, like adding [Slack notifications](https://docs.semaphoreci.com/essentials/slack-notifications/), using [webhooks](https://docs.semaphoreci.com/essentials/webhook-notifications/), updating a CHANGELOG file or even creating GitHub releases, but the nice thing about Semaphore, and CI/CD tools in general, is that now you can finally focus on what matters: the code for your next Android app.

Have questions about this tutorial? Want to show off your results? Leave a comment below. 🙇‍♂️