How to implement CI/CD in your Android application

In this article, we will explore the benefits of implementing CI/CD into Android app development and how to do it.

$ant article

By Marko Lovrić

8 min read

How to implement CI/CD in your Android application
 article

In today's fast-paced software development world, it is essential to adopt efficient and reliable processes to ensure timely delivery of high-quality software products. Continuous Integration and Continuous Deployment (CI/CD) is one such process that has become increasingly popular in recent years. CI/CD helps developers to streamline the entire development process, from coding to testing to deployment, allowing them to focus on delivering features and fixing issues quickly. In this article, we will explore the benefits of implementing CI/CD into Android app development and how to do it.

What is CI/CD?

CI/CD is a software development process that involves continuous integration, continuous delivery, and continuous deployment. It aims to automate the entire software development lifecycle, from building to testing to deployment. CI/CD enables developers to detect and fix issues early in the development cycle, leading to a more stable and reliable application.

Why Implement CI/CD in Android App Development?

  1. Faster Release Cycles: CI/CD helps to automate the entire development process, leading to faster and more frequent releases. This enables developers to respond quickly to market demands and user feedback.
  2. Improved Code Quality: With automated testing and continuous integration, developers can identify and fix issues early in the development cycle, leading to higher code quality and fewer bugs in the final product.
  3. Reduced Manual Effort: CI/CD automates the entire development process, reducing the need for manual testing and deployment. This saves time and reduces the risk of human error.
  4. Increased Collaboration: CI/CD encourages collaboration between developers, testers, and other stakeholders, leading to better communication and coordination between team members.

How to implement CI/CD in Android App Development

Firstly let's split this task into multiple tasks:

  1. In general, you will have three environments:
  • Development
  • Staging
  • Production According to which environment build we want to generate, we want to change parameters of the app like Backend URL, App name, version, app ID sufix, icon...
  1. We need to compile the build and generate APK/AAB
  2. We need to test the app (Unit tests, Automation tests)
  3. Upload/Deploy APK/AAB to third-party distribution (Firebase App Distribution, Google Play Console)

Setting up the Environment

Let's say we have three different environments. As a developer, I want to have three variants of the same app but with different environments simultaneously. We can achieve that by creating three different build variants in gradle script of our android project. Let's see how to do it.

defaultConfig {  
  applicationId "com.antcolony.automatization"  
  minSdk 28  
  targetSdk 33  
  versionCode 1  
  versionName "1.0"  
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  buildConfigField "String", "API_URL", "\"https://api.production.com/\""  
  manifestPlaceholders = [appIconChange: "@mipmap/ic_launcher", appName: "Production App"]
}

buildTypes {  
	release {  
    	minifyEnabled false  
		proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'  
	    buildConfigField "String", "API_URL", "\"https://api.production.com/\""  
	    manifestPlaceholders = [appIconChange: "@mipmap/ic_launcher", appName: "Production App"]  
      }

Here we have default config for our android project with only one build type, and that is release. We added two additional parameters in default config and release build type, and that is build config field and manifest placeholders
Now we need to do the same for two more build types, staging and develop.

 staging{  
     buildConfigField "String", "API_URL", "\"https://api.staging.com/\""  
     manifestPlaceholders = [appIconChange: "@mipmap/ic_launcher", appName: "Staging App"]  
     signingConfig signingConfigs.debug  
}

develop {  
    buildConfigField "String", "API_URL", "\"https://api.develop.com/\""  
    manifestPlaceholders = [appIconChange: "@mipmap/ic_launcher", appName: "Develop App"]  
    signingConfig signingConfigs.debug  
}

Let's explain what we did here:

  1. Build config field: In build config field we specify our environment, so when we build the app, we are sure that the proper environment is selected
  2. Manifest placeholders: This field is optional, here you can add different icons and/or App name to an app so when you have all variations installed you can distinguish between these apps and which environment is used in app

There is a lot of other variables you can add to make variants more unique, such as versionNameSufix or applicationIdSuffix.

Building and generating APK/AAB

If you want, you can stop the automatisation here, and manually build and deploy apps to Firebase or Google play. You can just simply do:

  1. In the menu bar, click Build > Generate Signed Bundle/APK.
  2. In the Generate Signed Bundle or APK dialog, select Android App Bundle or APK and click Next.
  3. Select which build variant you want to generate and click Create

And thats it, now you can upload file you generated to firebase or google play, depending if your app is in testing phase or deployment phase.

Also you can automatically deploy app to firebase using firebase app distribution dependency in android studio.

In your project level gradle, add the App Distribution Gradle plugin as a buildscript dependency:

classpath 'com.google.firebase:firebase-appdistribution-gradle:4.0.0'

In your module gradle file, add the App Distribution Gradle plugin:

id 'com.google.firebase.appdistribution'

Now, select your build that you want to deploy to Firebase (In this article, we will use staging build variant), and add this code:

firebaseAppDistribution {
   releaseNotesFile="/path/to/releasenotes.txt"
   testers="QA-Group"  
   }

Here you can add release notes as .txt file or you can just type them as string, and for testers, you can create group on firebase, or add emails in testers field.

Now we are ready to automatically deploy app to firebase, using this command:

./gradlew assemble{buildType} appDistributionUpload{BuildType}

Note: If you're running this command from terminal on your machine, you will need to add global variable FIREBASE_TOKEN so you can validate uploading build to firebase.

Implementing Gitlab CI

GitLab CI is a continuous integration and delivery (CI/CD) tool that allows you to automate your software development process. With GitLab CI, you can define pipelines that automate the building, testing, and deployment of your applications.

GitLab CI is built into the GitLab platform, which means that it's easy to use and fully integrated with GitLab's other features.

To build an Android app with GitLab CI, you'll need to define a pipeline that specifies the steps involved in the build process.

You can use Gitlab's CI pipeline template to start implementing testing, building and deployment of your app.

First you need to define stages as:

 stages:
     -  build
     -  test
     -  publish

Next thing to do is to set up stages to your needs, I'll use example for develop :

Build:

  build:
     tags:
     -  docker-executor
     interruptible:  true
     stage:  build
     script:
     -  ./gradlew  assembleDebug
     artifacts:
     paths:
     -  app/build/outputs/

Test:

localTests:
 tags:
 -  docker-executor
 interruptible:  true
 stage:  test
 script:
 -  ./gradlew  -Pci  --console=plain  :app:testDebug

Publish:

publish:
 tags:
 -  docker-executor
 stage:  publish
 script:
 - ./gradlew assembleDebug appDistributionUploadDebug

Implementing Github Actions

GitHub Actions is a feature of the GitHub platform that enables developers to automate their software development workflows. With GitHub Actions, you can create custom workflows that can perform various tasks, such as building, testing, and deploying applications. GitHub Actions workflows are defined using YAML files, and they can be triggered automatically or manually, depending on your requirements.

Steps for implementing Github actions are similar as Gitlab CI. You will need to create workflow file that should contain a list of jobs that define the tasks you want to perform. Each of those jobs should have a name, set of steps, and an optional set of environment variables, same as Gitlab CI.

name: Android CI

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      run: ./gradlew assembleDebug appDistributionUploadDebug

The on section specifies the events that trigger the workflow. In this case, the workflow is triggered when code is pushed to the "master" branch or when a pull request is opened or updated for the "master" branch. The jobs section defines the tasks that will be performed as part of the workflow. The build job specifies that the workflow will run on an Ubuntu Linux machine runs-on: ubuntu-latest. The steps section lists the steps that will be executed as part of the build job. The first step - uses: actions/checkout@v3 checks out the repository code. The second step - name: set up JDK 11 sets up the Java Development Kit (JDK) version 11 using the actions/setup-java@v3 action. The with section specifies the Java version and the distribution to use temurin and enables caching for Gradle. The third step - name: Grant execute permission for gradlew grants execute permission for the Gradle wrapper script gradlew. The fourth step - name: Build with Gradle runs the assembleDebug and appDistributionUploadDebug tasks using the Gradle wrapper script ./gradlew. These tasks assemble the app and upload it to Firebase App Distribution for testing.

Overall, this script sets up a basic CI pipeline for an Android app, which checks out the code, sets up the JDK, builds the app using Gradle, and uploads it to Firebase App Distribution for testing.

And that's it, we have successfully Implemented CI/CD into an android project. Now this script will run every time we merge into a specific branch, for example, if we merge into Develop branch, pipeline will deploy develop build variant to firebase. When we merge develop into master, pipeline will deploy staging build variant, and finally, when we merge master into production, app will be deployed to Google Play console.