Been busy, its not like I lost interest in posting here, I just didn’t allocate time for writing here, which is the opposite of what I set out to do.. which in turn does not track my progress at all, I should be held accountable for my progress. ~pff I hate it.

A friend told me in the time that I did not post, not necessarily everything should be a progress, maybe this is right or maybe wrong..only time will tell. (Meaning sometimes you can just relax, but to be honest.. I was not releaxing aswell)

Why & About

This is going to short, Recently I created a CI for development android builds, this is to reduce & eliminate the manuall build process to every pr, this reduces a lot of time for me.

so what does this do?

When a Pull Request is made to development or master branch, a CI will trigger that will pull all the latest code from that branch and try to build the code in dev mode, it will report whether tests and build is success and export the final apk. This will be helpful for testers and even me to take the build right away and check whether the code breaks the build or not. Additionally I dont have to manually make a comment everytime for testers to take the build.

Android CI with github actions

GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows to automate builds, test, and deployment pipeline. We can create workflows that build and test every pull request to our repositories.

Github actions free tier

GitHub Actions are unlimited for public repositories and the Free plan also provides 2,000 minutes of Actions each month for use with out private projects.

Gradle build

There are different variants of your application (release & debug, each variant has its own set of porperties). But what do we need here? debug variant, because that is what we share with the users and get feedback from. I mainly wanted this because everytime I make a pull request, I have to check if it builds, take builds.. I then have to link the builds to that pull request, so others/ even I can use it and test that pull request.

Commands

debug: This is the specific variant of the application that is being built. In Android projects, you can define different build variants, such as debug and release, to configure different settings and behavior. The debug variant is typically used during development and testing, while the release variant is used for production-ready builds. (note that: for the debug build the sizes will be significantly big than release builds)

For example: In my case I have these two builds

buildTypes {
    release {
        minifyEnabled true
        shrinkResources true

        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }

    debug {
        minifyEnabled true
        shrinkResources true

        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

I just specifically need the debug build alone.

What is a Keystore & Why do we need it?

A keystore plays a pivotal role in ensuring the security and integrity of our application. Essentially, a keystore is a secure store that holds cryptographic keys, certificates, and sensitive data used to digitally sign our App. These keys are used to digitally sign our App’s APK file. This digital signature serves as a means to verify the authenticity and origin of your application. When a user installs an app, the Android system uses the keystore’s digital signature to confirm that the App has not been tampered with and that it indeed originates from the expected source. This prevents malicious actors from altering the app’s code and introducing potential security vulnerabilities.

To simply put, we need this to distribute our app builds, users can trust this build since it is from us.

This keystore file should be kept private, should not be shared with others and can not be comitted along with other files in the repository.

If we keep this file private, how do we take builds then?

This is where configuring secret in github actions comes into play.

Once the secrets are configured, it can be used like below

- name: Decode keystore
  env:
    DEBUG_KEYSTORE: ${{ secrets.DEBUG_KEYSTORE }}
  run: |
    echo "$DEBUG_KEYSTORE" | base64 --decode > debug.keystore

Configuring the workflow

  • We set a name & when should the builds be triggered, it can be of several things Cron, Issue Comment, Pull Request, Fork, Deployment Status..etc. See the full list here

For my usecase, what I specifically need here is push

name: Android CI

on:
  push:
    branches: [ "master", "development" ]
  pull_request:
    branches: [ "master", "development" ]

Then we need to specify the jobs, there can be multiple job, but in our case, we just need a build job, give it a name.

jobs:
  build:

Choosing which type of machine this should be run on.

runs-on: ubuntu-latest

A job contians sequence of tasks called steps.

Checking out

- name: checkout & set up JDK 11
- uses: actions/checkout@v3
  uses: actions/setup-java@v3
  with:
    java-version: '11'
    distribution: 'temurin'
    cache: gradle

Decode keystore from repository secrets

- name: Decode keystore
  env:
    DEBUG_KEYSTORE: ${{ secrets.DEBUG_KEYSTORE }}
  run: |
    echo "$DEBUG_KEYSTORE" | base64 --decode > debug.keystore

Make gradle executable & run gradle build

- name: Grant execute permission for gradlew
  run: chmod +x gradlew

- name: Build with Gradle
  env:
    KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
    KEY_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
    KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
  run: ./gradlew assembleDebug -Pandroid.injected.signing.store.file=$(pwd)/debug.keystore -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD -Pandroid.injected.signing.key.alias=$KEY_ALIAS -Pandroid.injected.signing.key.password=$KEY_PASSWORD

We inject the debug.keystore & other environment variables into the build using params like -Pandroid.injected.signing.store.file=$(pwd)/debug.keystore.

Upload the artifacts

- name: Upload debug build
  uses: actions/upload-artifact@v3
  with: 
    name: <upload-name>.apk (build artifact)
    path: app/build/outputs/apk/debug/app-debug.apk

Full workflow file.

Build in action

Conclusion

This enabled me to not care about dev-debug builds entirely and focus just on development, which is a great time save.

In future, I would create a separate releases named nightly, where the dev branch is built daily and these builds will be updated there.