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.
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.
In a nutshell, CI/CD stands for Continuous Integration and Continuous Delivery (or Continuous Deployment). It’s an essential practice that aligns perfectly with the principles of DevOps and DevSecOps Services.
CI is a development practice requiring developers to frequently integrate source code into a shared repository. Then, each check-in(commit) is verified by an automated build, which allows the team to detect the problem early and solve problems quickly.
CD, on the other hand, focuses on swiftly delivering various types of changes, including new features, configuration changes, bug fixes, and experiments—into production or into the hands of users. This is done securely and efficiently, which is where DevSecOps services come into play.
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.
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-
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.
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'), 'proguard-rules.pro' ext.config = getProps('../config/release.properties') 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 } } staging { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' ext.config = getProps('../config/staging.properties') 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'), 'proguard-rules.pro' ext.config = getProps('../config/development.properties') 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.
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 => com.myapp.dev
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. app/ src/debug/google-services.json src/staging/google-services.json src/release/google-services.json ....
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 https://firebase.tools | bash
$ firebase login: ci
It will generate a firebase token by which we can authenticate and upload the build to firebase app distribution.
Copy and save the firebase token, we will need it later in fastlane.
2. Add tester group in firebase app distribution.
3. Get app id, we will need it later
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:
Now we know what Fastlane is and why it is used? Let’s move on to how to use 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
./fastlane/Fastfile
default_platform(:android) platform :android do desc "Generate build and upload to firebase" lane :build do slack_send(':crossed_fingers: Generating '+ENV['BUILD_TYPE']+' build') gradle( task: "assemble", build_type: ENV['BUILD_TYPE'], properties: { "android.injected.signing.store.file" => ENV['KEYSTORE_FILE'], "android.injected.signing.store.password" => 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') firebase_app_distribution( 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:') end end def slack_send(msg) slack( message: msg, success: true, channel: '#'+ENV['CHANNEL'], default_payloads: [] ) end
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
./fastlane/.env.development
BUILD_TYPE=Debug KEYSTORE_FILE = "CICDDemoDev.jks" KEYSTORE_PASS = "CICDDemo@123" KEY_ALIAS = "CICDDemo" KEY_PASS="CICDDemo@123" FIREBASE_APP_ID="1:304151838b333a0491c" FIREBASE_CI_TOKEN="1//0gPzWx8kEFjgKAOjSnvtU1VbDIxLq78" CHANNEL="jenkins" SLACK_URL="https://hooks.slack.com/services/T04PdMmDa3s5"
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.
./fastlane/.env.staging
BUILD_TYPE=Staging KEYSTORE_FILE = "CICDDemoStag.jks" KEYSTORE_PASS = "CICDDemo@123" KEY_ALIAS = "CICDDemo" KEY_PASS="CICDDemo@123" FIREBASE_APP_ID="1:8b333a0491c30415183" FIREBASE_CI_TOKEN="1//0gPzWx8kEFjgKAOjSnvtU1VbDIxLq78" CHANNEL="jenkins" SLACK_URL="https://hooks.slack.com/services/T04PdMmDa3s5"
./fastlane/.env.master
BUILD_TYPE=Release KEYSTORE_FILE = "CICDDemo.jks" KEYSTORE_PASS = "CICDDemo@123" KEY_ALIAS = "CICDDemo" KEY_PASS="CICDDemo@123" FIREBASE_APP_ID="1:151838b333a0491c304" FIREBASE_CI_TOKEN="1//0gPzWx8kEFjgKAOjSnvtU1VbDIxLq78" CHANNEL="jenkins" SLACK_URL="https://hooks.slack.com/services/T04PdMmDa3s5"
You can run this fastlane build using,
$ fastlane android build --env <env-file> Eg: $ fastlane android build --env development
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 Pipeline is a more recent feature of Jenkins Pipeline which:
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.
Let’s write Pipeline as code
Create ‘Jenkinsfile’ in the root directory and paste the below code.
slack_channel = 'android-ci-cd' pipeline { agent{ docker{ image 'mindbowser/android-30-sdk:1.0' args '-u root:root' } } stages{ stage('Init') { //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)>" slack_send(slackMessage) if(commitContainsSkip == 0) { skippingText = " Skipping Build for *${env.BRANCH_NAME}* branch." currentBuild.result = 'ABORTED' slack_send(skippingText,"warning") error('BUILD SKIPPED') } } } } stage('build') { // call fastlane lane for generate apk and uploading to testflight steps{ 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 ." deleteDir() } success{ slack_send("Jenkins job for *${env.BRANCH_NAME}* completed successfully. ","#0066ff") } aborted{ 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
Prerequisite:
BitBucket Branch Source Plugin:
Click on a new item
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
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.
Related Read: How to Implement Continuous Integration for Quality Automation in Product Engineering?
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.
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowEnhance Your Epic EHR Expertise in Just 60 Minutes!
Register HereMindbowser played a crucial role in helping us bring everything together into a unified, cohesive product. Their commitment to industry-standard coding practices made an enormous difference, allowing developers to seamlessly transition in and out of the project without any confusion....
CEO, MarketsAI
I'm thrilled to be partnering with Mindbowser on our journey with TravelRite. The collaboration has been exceptional, and I’m truly grateful for the dedication and expertise the team has brought to the development process. Their commitment to our mission is...
Founder & CEO, TravelRite
The Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project.
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
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.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
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.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork