How To Implement CI/CD For Android App Development?


Let me start with one question, As an Android developer how much time do you waste in deploying/distributing an APK for someone to test?

Let’s cut-up the whole process into small tasks and understand it.

  • We have 3 environments: Development, Staging, Release. According to which environment build we want to generate change backend URLs, firebase files, other dependencies that vary according to the environment.
  • Compile it, Generate APK.
  • Upload APK to third-party distribution (e.g Google drive, Firebase distribution).
  • Notify testers with release notes that the build is ready for testing.


A boring tiring process that takes a lot of time and energy every single time when you want to do it. Here automation comes to make our life easier. We can automate this manual process and get rid of the time-wasting problem. By using CI/CD we can achieve this.

What Is CI/CD?

Let me tell you what CI/CD in a nutshell?

CI/CD is an abbreviation of Continuous Integration and Continuous Delivery or Continuous Deployment.

Continuous Integration(CI)

CI is a development practice that requires developers to integrate source code into a shared repository frequently. Then each check-in(commit) is verified by an automated build which allows the team to detect the problem early and solve problems quickly.

Continuous Delivery(CD)

CD is the ability to get changes of all types—including new features, configuration changes, bug fixes, and experiments—into production, or into the hands of users, safely and quickly in a sustainable way.

Now, allow me to share my implementation of CI/CD for android. It is composed of 4 steps,

1. Environment Setup: As I mentioned before that we have 3 different environments, to automate this we will know what changes we need to make.

2. Firebase Setup: Firebase has an app distribution service that makes distributing your apps to testers painless. You can get early feedback as your apps are getting quickly to testers’ devices.

3. Fastlane: Fastlane is an open-source platform aimed at simplifying Android and iOS deployment. Fastlane lets you automate every aspect of your development and release workflow.

4. Jenkins: It is open source and free automation server software by which we can achieve continuous integration and continuous delivery.

Check Out What It Takes To Build A Successful App Here

How To Do It?

1. Environment Setup:

Why do we have 3 different API environments?

Creating separate API environments for development, testing and production provide each built with its own database, code-base and other respective backend services. This allows developers to continue to work and make changes, even while the app is in testing mode, and ensures (beta) testers do not mess up the production database.

My requirement from the environment is I have different backends and use different firebase projects. Also, I want to install 3 different applications on my mobile simultaneously. So I can track the features and testers also can differentiate between different builds, , also OkHttp Interceptors help in HTTP requests during android development.

To differentiate builds we can name our application as “DEV APPNAME”, “STAG APPNAME”, “APPNAME”.

1. Create properties files

Create a new directory “Config” and inside that create 3 properties files-

  • staging. properties

The purpose of these files is to store constant variables that vary across different environments such as BASE_URL, S3 BUCKET_URL, AWS credentials, etc. In our case, we have different names of apps as well as different app id. We are going to store all these properties in these files.

variable decleration

As you can see, I have declared 3 variables in a properties file, APPNAME, APPLICATION_ID_SUFFIX, API_URL.

Now we want to access these variables in Gradle and inside code. To read this variable inside Gradle, let’s write one function which will load these variables from a properties file. Write this function at the end of the app/build.gradle

def getProps(path) {

   Properties props = new Properties()

   props.load(new FileInputStream(file(path)))

   return props


The above function takes the properties file path, loads and returns the properties.

Now we want to set these properties such as these variables can be read in code.

At build time, Gradle generates the BuildConfig class so your app code can inspect information about the current build. You can also add custom fields to the BuildConfig class from your Gradle build configuration file using the buildConfigField() method and access those values in your app’s runtime code.

You can read more about it here.

Now we want to set APPNAME and APPLICATION_ID_SUFFIX and CONSTANTs according to build type. So add/replace the build types block with the below code in app/build.gradle

buildTypes {

       release {

           minifyEnabled false

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

           ext.config = getProps('../config/')

           ext.config.each { p ->

               if(p.key == "APPNAME"){

                   resValue "string","app_name", p.value


               else if(p.key=="APPLICATION_ID_SUFFIX"){

                   applicationIdSuffix p.value


                   buildConfigField 'String', p.key, p.value



       staging {

           minifyEnabled false

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

           ext.config = getProps('../config/')

           ext.config.each { p ->

               if (p.key == "APPNAME") {

                   resValue "string", "app_name", p.value.replace('"', '')

               } else if (p.key == "APPLICATION_ID_SUFFIX") {

                   applicationIdSuffix p.value

               } else {

                   buildConfigField 'String', p.key, p.value




       debug {

           minifyEnabled false

           proguardFiles getDefaultProguardFile('proguard-android.txt'), ''

           ext.config = getProps('../config/')

           ext.config.each { p ->

               if (p.key == "APPNAME") {

                   resValue "string", "app_name", p.value

               } else if (p.key == "APPLICATION_ID_SUFFIX") {

                   applicationIdSuffix p.value

               } else

                   buildConfigField 'String', p.key, p.value




Read more about build types.

Now we are setting the app name from the Gradle so we have to delete the pre-created app name string variable from the app/src/main/res/values/strings.xml file.

We Helped A Top Market Research Firm Gather Better Market Intelligence Using IoT

2. Firebase:

To distribute the app for testing, there are multiple distribution service providers available in the market. But we are going to use the firebase app distribution service. This service lets us manage the distribution and versions of the app.

Firebase App Distribution makes distributing your apps to trusted testers painless. By getting your apps onto testers’ devices quickly, you can get feedback early and often.

Steps for firebase setup:

1. Create a firebase project

Now we have 3 different apps for 3 different environments, so create 3 projects. Make sure you enter the application id.
For development =>

For staging => com.myapp.staging
For release => com.myapp

2. Enable Firebase app distribution service for all the apps

3. Copy APP id.

4. If you are using any other service of firebase such as firestore which needs googleservices.json file, then download.

In this case, we have three different googleservices.json files for different environments. To maintain this android provides a simple directory structure. The google-services.json file is generally placed in the app/ directory (at the root of the Android Studio app module). As of version 2.2.0, the plugin supports build type and product flavor specific JSON files.

// debug, staging and release are product flavors.


Recommended Reading:

To distribute the app, we need to authenticate and upload the build to the firebase from CI, for that firebase provides a CLI token.

1. To generate a CLI token, we need to install Firebase CLI

$ curl -sL | bash
$ firebase login: ci

It will generate a firebase token by which we can authenticate and upload the build to firebase app distribution.

Token Generation

Token generation

Copy and save the firebase token, we will need it later in fastlane.

2. Add tester group in firebase app distribution.

App Distribution

3. Get app id, we will need it later

Get App ID

Now we are going to use Fastlane for automating the generation and distribution of app to testers. But,



Fastlane can be simply described as the easiest way to automate building and release your iOS and Android apps, via a suite of tools that can work either autonomously or in tandem to accomplish tasks in android such as:

  1. Generating build
  2. Sign APK using Keystore
  3. Upload to firebase distribution
  4. Notify Testers

Now we know what Fastlane is and why it is used? Let’s move on to how to use Fastlane?

Install Fastlane

There are multiple ways to install Fastlane. You can choose any one of them.

Getting started with fastlane for iOS

Once you successfully install Fastlane. We will first initialize Fastlane.

$ cd project_root
$ fastlane init

It will generate a Gemfile and Fastlane folder in the root.

It follows simple instructions defined in a Fastfile. After you set up Fastlane and your Fastfile, you can integrate App Distribution with your Fastlane configuration. The Fastfile has to be inside your ./fastlane directory.

To add App Distribution to your Fastlane configuration, run the following command from the root of your android project:

$ fastlane add_plugin firebase_app_distribution


platform :android do
 desc "Generate build and upload to firebase"
 lane :build do
   slack_send(':crossed_fingers: Generating '+ENV['BUILD_TYPE']+' build')
     task: "assemble",
     build_type: ENV['BUILD_TYPE'],
     properties: {
       "" => ENV['KEYSTORE_FILE'],
       "" => ENV['KEYSTORE_PASS'],
       "android.injected.signing.key.alias" => ENV['KEY_ALIAS'],
       "android.injected.signing.key.password" => ENV['KEY_PASS'],
   slack_send(ENV['BUILD_TYPE']+' Build Successfully completed...:star-struck: \n Uploading to Firebase')
     app: ENV["FIREBASE_APP_ID"],
     release_notes_file: "releaseNotes.txt",
     groups: "Internal",
     firebase_cli_token: ENV['FIREBASE_CI_TOKEN'],
     debug: false
   slack_send(':tada: Hooooooorrrayyyyy!!! '+ENV['BUILD_TYPE']+' Build is successfully uploaded on Firebase  Distribution!! :dancer::man_dancing:')

def slack_send(msg)
      message: msg,
      success: true,
      channel: '#'+ENV['CHANNEL'],
      default_payloads: []

Fastlane’s build lane will compile and sign APK file with Keystore, notify status to slack and then it will upload to firebase app distribution.

Now you notice we have used the environment variable in Fastfile. You can export these variables or pass the environment variable file while running Fastlane.

Let’s add environment variable files. We will use this Fastfile for multiple environments.

Create environment variable file for development




We will have the same variables for staging and release. Only build type, keystore and Firebase app id variables will be changed for different environments.

Firebase token, channel name and slack URL will be the same.







You can run this fastlane build using,

$ fastlane android build --env <env-file>
$ fastlane android build --env development

4. Jenkins

Jenkins® is is a free and open-source automation server. With Jenkins, organizations can accelerate the software development process by automating it. Jenkins manages and controls software delivery processes throughout the entire lifecycle, including build, document, test, package, stage, deployment, static code analysis and much more.


You can set up Jenkins to watch for any code changes in places like GitHub, Bitbucket or GitLab and automatically do a build a with tools like Maven and Gradle. You can utilize container technology such as Docker and Kubernetes, initiate tests and then take actions like rolling back or rolling forward in production.

What Is The Jenkins Pipeline?

Jenkins Pipeline (or simply “Pipeline”) is a suite of plugins that supports implementing and integrating continuous delivery pipelines into Jenkins.

A continuous delivery pipeline is an automated expression of your process for getting software from version control right through to your users and customers.

We can write one text file in which we define multiple stages of the process which in turn can be committed to a project’s source control repository. This is the foundation of “Pipeline-as-code”; treating the CD pipeline a part of the application to be versioned and reviewed like any other code.

A Jenkinsfile can be written using two types of syntax

  • Declarative
  • Scripted

Declarative Pipeline is a more recent feature of Jenkins Pipeline which:

  • Provides richer syntactical features over Scripted Pipeline syntax, and is designed to make writing and reading Pipeline code easier

To generate an android build we need to install android SDK, Fastlane on the Jenkins agent. To avoid that, we will use docker containers for running the Jenkins pipeline.

  • Docker container has: git, android SDK, fastlane

Let’s write Pipeline as code

Create ‘Jenkinsfile’ in the root directory and paste the below code.

slack_channel = 'android-ci-cd'

            image 'mindbowser/android-30-sdk:1.0'
            args '-u root:root'
            //check git commit message contains "skip ci" if found don't run the pipeline
            steps {
                script {

                    lastCommitInfo = sh(script: "git log -1", returnStdout: true).trim()
                    commitContainsSkip = sh(script: "git log -1 | grep 'skip ci'", returnStatus: true)
                    slackMessage = "*${env.JOB_NAME}* *${env.BRANCH_NAME}* received a new commit. \nHere is commmit info: ${lastCommitInfo}\n*Console Output*: <${BUILD_URL}/console | (Open)>"

                    if(commitContainsSkip == 0) {
                        skippingText = " Skipping Build for *${env.BRANCH_NAME}* branch."
                        currentBuild.result = 'ABORTED'
                        error('BUILD SKIPPED') 
            // call fastlane lane for generate apk and uploading to testflight

                    sh "chmod +x gradlew"
                    sh "chmod +x Gemfile"
                    sh "fastlane build --env ${env.BRANCH_NAME}"    //eg. fastlane build --env development

    post {
        always {
            // delete the workspace

            sh "chmod -R 777 ."
             slack_send("Jenkins job  for *${env.BRANCH_NAME}* completed successfully. ","#0066ff")
            slack_send("Jenkins job  for *${env.BRANCH_NAME}* Skipped/Aborted.","warning")
        failure {
          slack_send("*${env.BRANCH_NAME}* Something went wrong.Build failed. Check here: Console Output*: <${BUILD_URL}/console | (Open)>","danger")


def slack_send(slackMessage,messageColor="good")
    slackSend channel: slack_channel , color: messageColor, message: slackMessage

The above file contains two-stage:

In the first stage we check the commit message, if it contains ‘skip ci’ then Jenkins job is aborted.

The next stage is the build stage, which we call fastlane build lane which will generate and distribute the app according to the branch.

Create Jenkins job


BitBucket Branch Source Plugin:

Slack plugin:

Click on a new item


jenkins code

Enter pipeline name and select multibranch pipeline type

Add name, description and select your branch source

Select your SCM provider.

Add credentials and then select the repository name from the drop-down list

Now select your branch discovery strategy, In my case, I want to generate build only when commits to the following branches

  • Development
  • Staging
  • Master

For that, first, we will select all branches and then will filter them out.

Now add space-separated branch names

After that, Save this configuration.

It will scan the branches and select the branches which have jenkinsfile and we are filtered.


Note: To make sure the build gets triggered, add a webhook to the repository.



In this blog, we learned How to implement CI/CD for android app development. With these strategies, you can speed up your development time and reduce feedback loop time.

Hire Our Best Remote Android App Developers In Affordable Price


DevOps Engineer

Shubham is DevOps Engineer at Mindbowser Global Inc. Shubham has hands-on experience in implementing automation, CI/CD pipelines and DevOps processes. He loves to write blogs on developers’ technical issues in day-to-day work and guide them through his tech blogs.

Get in touch for a detailed discussion.

Hear From Our 100+ Customers

Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.


We had very close go live timeline and MindBowser team got us live a month before.

Shaz Khan
CEO, BuyNow WorldWide

They were a very responsive team! Extremely easy to communicate and work with!

Kristen M.
Founder & CEO, TotTech

We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.

Chacko Thomas
Co-Founder, TEAM8s

Mindbowser is one of the reasons that our app is successful. These guys have been a great team.

Dave Dubier
Founder & CEO, MangoMirror

Mindbowser was very helpful with explaining the development process and started quickly on the project.

Hieu Le
Executive Director of Product Development, Innovation Lab

The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.

Alex Gobel
Co-Founder, Vesica

Mindbowser is professional, efficient and thorough. 

MacKenzie R
Consultant at XPRIZE

Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.

Laurie Mastrogiani
Founder, S.T.A.R.S of Wellness

MindBowser was great; they listened to us a lot and helped us hone in on the actual idea of the app.” “They had put together fantastic wireframes for us.

Bennet Gillogly
Co-Founder, Flat Earth

They're very tech-savvy, yet humble.

Uma Nidmarty
CEO, GS Advisorate, Inc.

Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.

Katie Taylor
Founder, Child Life On Call

As a founder of a budding start-up, it has been a great experience working with Mindbower Inc under Ayush's leadership for our online digital platform design and development activity.

Radhika Kotwal
Founder of Courtyardly

The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!

Michael Wright
Chief Executive Officer, SDOH2Health LLC

They are focused, patient and; they are innovative. Please give them a shot if you are looking for someone to partner with, you can go along with Mindbowser.

David Cain
CEO, thirty2give

We are a small non-profit on a budget and they were able to deliver their work at our prescribed budgets. Their team always met their objectives and I'm very happy with the end result. Thank you, Mindbowser team!!

Bart Mendel
Founder, Mindworks

Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.

George Hodulik
CEO, Stealth Startup, Ex-Google