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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Continuous Delivery for Android Using GitHub Actions
30 mins
- Getting Started
- Using GitHub Actions
- Uploading the Project to GitHub
- Creating a Workflow
- Running the Tests
- Generating a Signed Release Build
- Creating a Keystore
- Storing Secrets
- Signing the Build
- Triggering a Release
- Pushing a Version Tag
- Pull Request to Master
- Preventing a Merge if Tests Fail
- Deploying to Firebase App Distribution
- Setting up Firebase
- Adding Testers
- Fetching the App ID and Token
- Upload to Firebase Action
- Visualizing a Workflow
- Deploying to Play Store
- Setting up a Service Account
- Upload the Google Play Action
- Where to Go From Here?
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:
Click New repository secret and add the following four secrets:
- ALIAS: Alias of your signing key.
- KEY_STORE_PASSWORD: The password to your signing keystore.
- KEY_PASSWORD: The private key password for your signing keystore.
- 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:
- Checks out the code.
- Generates a release APK using the
assembleRelease
Gradle task. - 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.
- Uploads the signed APK as an artifact to GitHub. This step uses the ID from the previous step to access its output, named
signedReleaseFile
. - 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:
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
Also, notice that the signed APK and mapping file are attached as artifacts, similar to the ones shown below:
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:
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.