Android CI/CD pipeline with Github Actions: Demystifying Github Actions

Android build generation and deployment has always been a pain point for Android developers. Hundreds of dev hours are wasted while gazing on the computer screen, waiting for our build to complete. That’s where CI/CD comes to our rescue. There are various tools to build CI/CD pipelines like Jenkins, CircleCI, Bitrise, TravisCI etc. Github Actions is a new addition to this list. It makes the whole process of building a pipeline very easy and is completely free for public repositories. You can find the pricing here.

In this article, I would demonstrate how to use Github Actions, to automate your android build and deployment process.

Glossary:

  1. Workflow : Set of sequential or parallel Jobs to form entire CI/CD flow, defined in a YAML file
  2. Job : Set of steps to perform one particular action i.e Run Test cases/Generate build/Checkout code/Setup JDK etc
  3. Steps : Series of commands/actions that combine together to form a Job. In a step, one must run a command OR uses an action.
  4. Actions : Predefined, Ready-To-Use commands that act as Plugins to save your time. More than 10k actions available on Actions Marketplace. Blue ticks ones are the verified ones.
  5. Runners : Server/Machine on which your entire Workflow would be executed. Can be github hosted or your own hosted
  6. workflow_dispatch : Yaml key to mark that a workflow can be trigger manually from Actions Tab or REST API
  7. inputs : Yaml key if our workflow_dispatch event requires any runtime input
  8. runs_on : Yaml key to determine Runner label on which the Job would be executed. Github hosted: windows-latest , macos-latest , ubuntu-latest . Your own machine: self-hosted
  9. needs : Yaml key to set a job dependent on other, so that it waits for the previous job to finish. Otherwise jobs will run in parallel.

Steps to create the Workflow YAML file

One can create the workflow file by going to Actions Tab > Setup workflow yourself.

Action Tab in Github Repository

This will take you to the YAML editor, where we will be defining our workflow according to our need. A basic workflow structure looks something like this:

# Name of your workflow
name: Android Build and Deployment Pipeline
# Define on which Github event, workflow will trigger
on:
push:
branches: [ master ]
pull_request:
branches: [ main ]
jobs:
job1:
name: Job 1
runs-on: ubuntu-latest
steps:
- name: 'Check Inputs'
run: echo 'Job 1'

Note that the YAML file is indentation sensitive

  1. Add trigger for Github events
on:
push:
branches: [ master ]
pull_request:
branches: [ main ]

You can also add triggers for merging, pull request etc

2. Add support for manual trigger(if needed)

workflow_dispatch:
inputs:
app_id:
description: 'The application Id/package name'
required: true
branch:
description: 'The branch from which you want the build'
required: true
default: 'master'

3. Checkout code

Default branch:

- name: Checkout the code to specific branch
uses: actions/checkout@v2

Specific branch(branch name from Step 2 inputs, can be hardcoded too)

- name: Checkout the code to specific branch
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.branch }}

4. Setup JDK

We have a predefined action in Github Actions Marketplace. It handles the task of downloading the JDK, unzipping it, setting the JAVA_HOME path etc.

- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '11'

5. Setup Android SDK

We have an action for this step also. Downloading SDK, unzipping, env variables, accepting licences everything is handled.

- name: Setup Android SDK
uses: android-actions/setup-android@v2

6. Gradle Caching(optional)

For this, we will use this action .

- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

7. Make Gradle Executable

We will use bash command for this.

- name: Make gradlew executable
run: chmod +x ./gradlew

8. Generate the Artifact

For Bundle:

- name: Generate app bundle.
run: ./gradlew app:bundleRelease --stacktrace

For APK:

- name: Generate app APK.
run: ./gradlew assembleRelease --stacktrace

9. Sign the Artifact

If you have defined signing config in gradle file, then this step is not needed.

To sign the APK/Bundle you will need KeyStore Password, Alias, Key Password. You can define these as global variables in Github on organisation or repository level. These secrets are encrypted and safe to use in Github Actions. To create secrets you can visit this link

- name: Sign app bundle
run: |
jarsigner -keystore app/*.jks \
-storepass ${{ secrets.KEY_STORE_PASSWORD }} -keypass ${{ secrets.KEY_PASSWORD }} \
app/build/outputs/bundle/release/app-release.aab ${{ secrets.ALIAS }}

There is an action for the same. We can use it too.

- name: Sign app bundle
uses: r0adkll/sign-android-release@v1
id: sign_app
with:
releaseDirectory: app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "30.0.2"

10. Upload the artifact to Github(optional)

Incase if you want to access the generated bundle/APK from previous steps, you can upload it to Github. It will be available to download inside the workflow.

- name: Upload Bundle
uses: actions/upload-artifact@v2
with:
name: signed_app_bundle
path: app/build/outputs/bundle/release/app-release.aab

11. Create release to Playstore

We will use a predefined action for this purpose.

For making a release to PlayStore, we need a service account json file, which is created from the playstore console. The steps involved are out of scope for this article. You can follow this link for creating the service account json file.

steps:
- name: Create service_account.json
run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
- name: Deploy to Play Store
uses: r0adkll/upload-google-play@v1.0.15
with:
serviceAccountJson: service_account.json
packageName: ${{ github.event.inputs.app_id }}
releaseFiles: app/build/outputs/bundle/release/*.aab
track: internal
whatsNewDirectory: whatsnew/
mappingFile: app/build/outputs/mapping/release/mapping.txt
inAppUpdatePriority: 5

Final complete workflow.yml file will look like:

If this article helped you in any way, show some 👏🏽 to this article. Do let me know, if there is any mistake in this article, or you face any issue while setting up the workflow.

Android Dev