Continuous Delivery for Android Using GitHub Actions

Learn how to create a continuous delivery pipeline in Android to deploy your apps to the Google Play Store. By Subhrajyoti Sen.

4.7 (7) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Storing Secrets

For security’s sake, it’s important not to hard code secrets inside the codebase. A good way to avoid this is by using environment variables to refer to the secrets. GitHub Actions provides a similar mechanism.

Open your repository on GitHub and go to the Settings tab. On the left navigation bar, click Secrets:

Github Secrets page

Click New repository secret and add the following four secrets:

  1. ALIAS: Alias of your signing key.
  2. KEY_STORE_PASSWORD: The password to your signing keystore.
  3. KEY_PASSWORD: The private key password for your signing keystore.
  4. SIGNING_KEY: The base 64-encoded signing key used to sign your app.

To generate the base 64-encoded key, run the following command in Terminal and copy the output string:

openssl base64 < path_to_signing_key | tr -d '\n' | tee some_signing_key.jks.base64.txt

In the code above, replace path_to_signing_key with the actual path to your keystore.

Signing the Build

Add a new job named build to the workflow, indented below the jobs tag as shown below:

jobs:
  build:
    needs: [ unit_tests, android_tests ]
    runs-on: ubuntu-latest
    steps:
      # 1
      - name: Checkout code
        uses: actions/checkout@v2
      # 2
      - name: Generate Release APK
        run: ./gradlew assembleRelease
      # 3
      - name: Sign APK
        uses: r0adkll/sign-android-release@v1
        # ID used to access action output
        id: sign_app
        with:
          releaseDirectory: app/build/outputs/apk/release
          signingKeyBase64: ${{ secrets.SIGNING_KEY }}
          alias: ${{ secrets.ALIAS }}
          keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
          keyPassword: ${{ secrets.KEY_PASSWORD }}
      # 4
      - uses: actions/upload-artifact@master
        with:
          name: release.apk
          path: ${{steps.sign_app.outputs.signedReleaseFile}}
      # 5
      - uses: actions/upload-artifact@master
        with:
          name: mapping.txt
          path: app/build/outputs/mapping/release/mapping.txt

In the code above, the build job performs multiple steps. It:

  1. Checks out the code.
  2. Generates a release APK using the assembleRelease Gradle task.
  3. Signs the APK using the r0adkll/sign-android-release action, which is a third party action available on the github marketplace linked earlier. This step uses the four secrets you added in the previous section. It also has an ID: sign_app.
  4. Uploads the signed APK as an artifact to GitHub. This step uses the ID from the previous step to access its output, named signedReleaseFile.
  5. Uploads the mapping file as an artifact. You'll use this in a later step, when you upload to the Play Store.

Commit the file to your project and push it to GitHub. Open the GitHub repository and go to the Actions tab. You'll see that a workflow named Test and deploy is running. Wait for a few minutes and the workflow should complete successfully:

Note: If you're using a Windows machine, you may need to change the gradlew file permissions such that they can executed. If your action fails with a permission error, run the following command to set the gradlew file as executable: git update-index --chmod=+x gradlew

GitHub Actions workflow success

Also, notice that the signed APK and mapping file are attached as artifacts, similar to the ones shown below:

GitHub workflow artifacts

Congratulations, you've completed the first part of your continuous delivery pipeline.

Triggering a Release

In the current implementation, the workflow runs every time you push code to your repository. But, you don't want to release a new build every time you push a new commit. Ideally, you want to release a build in the following scenarios:

  • You push a version tag to the repository.
  • You create a pull request targeting the master branch.

In this section, you'll modify the workflow so it triggers when those conditions occur.

Pushing a Version Tag

A version tag name usually has the v prefix. For example, v1.10, v0.31, v/3.31 etc. You'll use this convention to trigger the workflow when you push a version tag.

Add the following code under push in the workflow:

    tags:
      - 'v*'

Here, v* is a regular expression that matches any string starting with v.

Your workflow's on condition will now be:

on:
  push:
    tags:
      - 'v*'

Commit your changes and push them to GitHub.

Create a new release tag and push it by running the following commands in Terminal:

git tag v0.1 -a -m "Release v0.1"
git push --follow-tags

The code above creates a tag named v0.1 and pushes it with the commit message Release v0.1.

Once the push is complete, open the Actions tab. You'll see that the workflow is running. To verify that the workflow triggers only on the version tag, push an empty commit and check if that triggers the workflow. You can do so by running the following commands from the command line:

git commit --allow-empty -m "Empty commit"
git push

In the code above, --allow-empty lets you create an empty commit — that is, a commit without any changes.

Open the Actions tab in the repository and verify that the workflow hasn't triggered.

Pull Request to Master

A typical workflow among teams is to send a release APK to the QA team whenever a pull request is made to the master branch. Your next step is to make this happen automatically.

Add the following code to the on section of the workflow:

pull_request:
  branches:
    - master

This code triggers the workflow when a pull that targets the master branch is created.

Commit and push the changes to GitHub, then verify the changes by creating a pull request from any branch to the master branch.

Preventing a Merge if Tests Fail

Before merging any code to master, you want to make sure that the new code builds correctly and all tests pass. If any of these conditions fail, you want to block the pull request from merging. Branch protection helps you do this.

Add protection by going to Settings ▸ Branches on the repository. You'll see a page like the one shown below:

GitHub branch settings page

Click Add rule in the Branch protection rules section. You'll see a list of options, each with a checkbox. Check Require status checks to pass before merging. Since you want both the build job and the test jobs to pass, you have to check the build, unit_test and android_test tasks.

Finally, click Create at the bottom of the page. From now on, any pull request that fails these checks will be unable to merge.

Deploying to Firebase App Distribution

Once the unit and instrumentation tests pass, you want to send the build to your QA team. A structured way of doing this is to use Firebase App Distribution. It lets you keep track of the uploaded builds and notify teams when new builds become available. You can even create groups and distribute a build only to specific groups.

Next, you'll see how to set this up for your project.