CI & CD is a software development practice most mobile developers in team projects use daily, however, most times the Version Control practices (or git branching strategy) as well as the automated testing/deployment is already established and will only require small changes if anything. On the other hand, when working on individual or personal projects setting up our own CI & CD pipelines can seem overkill if not with a learning motive or client requirement.
As a result, many mobile developers might feel like CI & CD falls outside their fields of expertise, and perhaps without the use of a mobile DevOps platform this might be the case, but mobile CI & CD automation has never been easier through the use of these platforms.
This article will not focus on CI & CD as a concept but on an overview of the implementation of CI & CD through a mobile DevOps platform, making use of its simplicity but multiple configurable features.
CI (Continuous Integration)
When we talk about CI we refer to regularly integrating or pushing code into a shared code repository, and automatically running tests against the changes to detect any issues before being merged into the team’s shared repository.
CD (Continuous Delivery/Deployment)
As for CD in a mobile app development context, we refer to automatically building our apps and delivering them to groups of interest such as QA, clients, testers… etc, or deployed to an app store, when a particular CI action is triggered.
DevOps platforms
There are very popular Source Control platforms such as GitHub and Gitlab, and they both provide their own CI/CD pipeline integrations. These are definitely viable solutions, especially when your project is already being hosted there, but due to their multi-purpose and multi-platform nature, they are harder to understand when it comes to the sole aspect of mobile continuous integration & development.
The end product of a CI/CD configuration file is an YML file with the instructions to be carried out by an external machine. Mobile DevOps platforms allow us to use their interfaces and a wide variety of features to customize our pipeline, in a safe way, as it generates said YML file without us having to edit any line of code in the file. Likewise, modifying manually the YML will also update the UI steps we see in the workflows. It also provides build reports and information more clearly to the user, as the platform solely focuses on CI/CD integration.
Within CI/CD and more specifically the DevOps platforms there are some common concepts that should be explained:
Workflows – The core tool we will use to customize the automation. Workflows are a set of steps the platform performs for us to implement a CI/CD pipeline. These may include building, testing, or deploying an app.
Steps – Steps are the different actions our platform can run inside a workflow, these may include git repository actions, running builds, deploying an apk, modifying a Gradle file, or simply a script… etc. There are several possibilities.
Triggers – These are the conditions that will trigger a workflow. These will most likely be related to pushes and merge requests from one branch to another.
Environment variables – Just like your standard environment variables, these variables can be stored in the project’s space so they are widely available within workflows. Examples could include apk directories, package names, app modules, or variants.
Secrets – Similar to environment variables, these might be required in some steps in the workflow, but secrets contain secure sensitive information, for example, a token to access Play Store APIs.
Code signing – This will include any files necessary when releasing, such as Keystore files, their alias, and passwords. Bitrise will store and safely handle these for you.
YML file – This is the configuration file you have set up through the platform’s interface. It is always accessible for you to read and edit. It will act as an instruction guide for the machine running the pipeline. This file should be stored in the app repository (Bitrise can manage this for you). This is useful in case you ever run into CI configuration problems as you can easily revert to a previous version and update the YML file in Bitrise.
Introducing Bitrise
There are multiple DevOps platforms, but Bitrise is a mobile-first provider and arguably one of the most simple but capable platforms to get set up. Also, you get a few free credits to set up your pipeline and run a few builds. You can find Bitrise at the top of many mobile DevOps Platforms lists.
For almost every Android CI/CD action you might want to run, Bitrise docs have a template for that workflow, explaining what steps you should add and why. Some steps could be considered boilerplate as they might be required in the workflow regardless of the platform or goal such as: connecting to our VCS, cloning the project repository, pulling/pushing caches from bitrise to speed up build times, or deploying build results back to bitrise. Most other steps, however, will be defined by the goal and platform.
CI & CD in practice
To integrate pipelines we first need to define a version control or git branching strategy. Next, we will describe a common sample* strategy that will help us define our workflows.
This will consist of the following branches and their roles:
Development – This is the base branch with a stable shared codebase for the devs in the team. Every time we want to add code we checkout this branch and push or pull request the changes back into development, triggering unit tests as well as UI tests which when passed, will allow the MR or push to succeed.
Staging – When we want our code to pass a QA check we will merge development into staging, QAs should only use this branch to pass their tests. This stage might generate an unsigned apk build to be deployed to a platform such as a firebase for testers and/or clients to test. No testing is required in this workflow as we would be repeating the same tests against the same codebase from the development workflow.
Release – When our app is ready to be deployed we can merge staging into release. This can trigger a release to the Play Store, but even though some might prefer to manually update the app and skip this trigger, you can also use it to release it to an alpha or beta channel, not necessarily production.
*This strategy is just an example and not the best practice, it provides a good base and understanding for CI/CD to be implemented through a mobile DevOps platform. The best or most optimal strategy can depend on many factors and will vary from project to project.
Our workflows
Next, we will look at how 3 different workflows in the development–staging–release VC strategy we described above would look like, and how they are triggered.
Development workflow:
Role → Merging into this branch means adding changes to the code that require tests to pass before merging, to ensure new code doesn’t break anything.
Trigger → Pushing or making a merge request from a development/feature_branch (or any other) to the development branch.
Specific steps → This workflow will run all the Android steps needed to run Unit Tests and UI Tests if we have any. For this we can read Bitrise docs and figure out what steps we need and configure them accordingly. For example, when testing for UI tests we might need an AVD Manager step, and if we are using an Emulator we need a Wait for Android emulator step.
Staging workflow:
Role → Merging into this branch could mean an APK should be made available to a group of interest, such as QA, or a client.
Trigger → Pushing to the staging branch
Specific steps → This workflow doesn’t include any testing steps, as there have been no changes in the code from the codebase in development. This workflow is characterized by having an Android Build step that generates an unsigned APK and another step that distributes it to Firebase.
To showcase some interesting features we also added steps to automatically modify the versionCode and the versionName of our app. To do so we need to run a script that will modify the Gradle file dynamically, however, it requires the branch name to contain the desired version name, and the versionCode can only be updated to the Bitrise build number so it does have obvious limitations.
Release workflow:
Role → Merging into this branch means an APK should be deployed to the playstore, to a channel(s).
Trigger → Pushing to the release branch
Specific steps → This workflow is characterized by adding an Android Sign step to the build (will require App Signing information such as Keystore JKS file and alias key and password) and another step to distribute to the play store (requires a play store console service account set up, as well as a file with the release notes if desired, and other specifications such as channel, apk or bundle… etc)
Setting up the triggers
Now that we have defined our workflows, we can configure the triggers, which means specifying what actions will trigger each workflow. Bitrise has a tab for us to specify these actions easily.
For the development workflow, we set a trigger for pull requests (or pushes), in which we state that any branch making a PR into development will start up the development (called development_tests in the screenshot) workflow:
Next time we make a PR our VC website will pop up a message indicating a Bitrise pipeline is running. Depending on the build’s success it will show a green check or an error.
For the staging (called staging_deploy_firebase_apk in the screenshot) and release (called release_deploy in the screenshot) workflows, we have similar triggers. Both will trigger when there is a push action to their respective branches:
All set!
Once we have defined the workflows and the triggers, we have essentially built our pipelines, and all we should do is respect the branching strategy for the CI/CD to be correctly implemented.
We did not dive into the details for every step configuration and platform feature to keep it short and sweet but hopefully this article will provide some insight into how simple CI/CD in an Android project can look like and the capabilities of mobile DevOps platforms.