How to configure Bitrise workflows for your Android project
Setting up Android projects on Bitrise CI
At Droids On Roids, we use Bitrise.io for continuous integration and continuous deployment of our mobile projects. In order to do its job efficiently, Bitrise needs to be configured properly. Let’s take a look at how we did that in one of our recent Android projects!
Each project and development team may have different needs. There is no silver bullet that suits all the projects in the world. Workflows mentioned in this article are not meant to be copy-pasted but rather should be treated as examples and bases for customizations.
Kinds of workflows
We can divide workflows into groups basing on their purpose. For example, we can have the following groups in the given app:
- new/updated pull request – we want to verify it
- pull request verified successfully – we want to deploy a test/staging variant of the app
- milestone/sprint finished – we want to deploy a production variant of the app
During verification, we usually perform unit tests and static code analysis. Instrumentation and/or UI tests may also be included but they often take much more time than the latter. Different kinds of checks may be performed in parallel using Start Build and Wait for Build steps.
Complete verification workflow look like that in Bitrise workflow editor:
Firstly we start children workflows performing independent tasks (I’ll explain them later). They run in parallel so overall build time is shorter compared to a single workflow containing all the steps.
Next, we prepare the environment by providing source code and cached dependencies. Then we perform actual analysis and publish its results. Finally, we wait for children’s workflows started at the beginning.
It is worth noting that the analysis is done using Gradle Unit Test step only. We invoke
check task there and define its dependencies (like detekt or lint) inside buildscript. Although there are Bitrise steps integrating mentioned tools, having them defined in buildscript allows us to run them locally, outside Bitrise.
Children verification workflows
They both look the same in workflow editor:
Here we use Virtual device testing preceded by Gradle runner step to build an app (APKs or AABs). The latter differs from the Gradle Unit Test by additionally exporting paths to output artifacts needed in tests. Finally, we use Deploy to Bitrise.io step so we can see VDT results (including video and screenshots). Note that we don’t use such a step in verification workflow since Danger takes care of publishing the most of the results (lint and detect warnings) and the rest can be easily retrieved from the build log.
In most Android app projects there at least 2 deployable variants: connected with non-prod environment used for internal testing (usually called QA or staging) and another one connected with production for public releases. Even if there are more environments they often can be divided into 2 groups: non-public and public and workflows for any flavor belonging to the given group will look similar.
In this example, we’ll deploy the app using Firebase App Distribution. Note that at the time of writing that service is still in beta stage but it replaced deprecated Fabric Beta. If you are using another distribution platform e.g. AppCenter or TestFairy look at the Bitrise Integrations list, there might be ready steps for them. Note that in the case of Android projects some services provides also dedicated Gradle plugins, so you may configure everything in buildscript instead of using additional steps.
Although Bitrise provides Firebase App Distribution step (at the time of writing in beta), we’ll use dedicated Gradle plugin and Gradle Runner step like in verification workflow. Note there are several ways to authenticate the Firebase user. In this case, we’ll use a service account.
In order to use a service account, we need a json file with the private key. It can be uploaded to Bitrise.io and then injected to builds when needed. There are a few options to do that. One of the easiest is to use the Generic File Storage step which downloads all the files from storage and exports path to a directory containing them as an environment variable which then can be referenced in the Gradle Runner step.
At the end, we deploy staging APK(s) using Deploy to bitrise.io – Apps, Logs, Artifacts step. This additional deployment acts only as a fallback. If something goes wrong with Firebase App distribution e.g. Firebase API responds with HTTP 504 error then APK is still available without necessity to retrigger the build. Finally, we update the Bitrise cache to speed up further builds.
The structure of this workflow is exactly the same as the previous one. It differs only by deployment destination. Instead of Firebase, we use the Google Play Store here. Note that deploying to the store does not necessarily mean automatic public release. Usually, the app is first deployed to one of the pre-release tracks like alpha or internal test and then promoted to the public production track in an appropriate moment.
Bitrise provides Deploy to Google Play step which can take URL to store credentials file. So there is no need to download it like in the case of Firebase. However, we use the File downloader step to get keystore used to sign the app.
At Droids On Roids, we use Slack to communicate within the company. So it may be useful to receive notifications related to Bitrise also there. Usually, each project has dedicated channel for alerts (coming not only from Bitrise). Notification sending is a common action for all the workflows. We can configure it once in utility workflow and then include the latter in regular workflows.
Often we may want to receive notifications only if something goes wrong. Namely for failed builds, avoiding messages related to successful ones. Such configuration is possible on Bitrise but cannot be configured using graphical WYSIWYG editor. We need to add
run_if: ".IsBuildFailed" to step configuration in bitrise.yml mode. Utility workflow looks like that in workflow editor:
Bitrise workflows can be flexibly configured which gives myriads of possible combinations. Apart from workflows content (steps), we can also configure their triggers. In our app verification workflow is triggered by Pull Requests, staging deployment by merges to the master branch and production deployment by tag pushes. Those (especially deployments) are only examples and may vary in different projects.
You can find the complete configuration of all the workflows below. Note that inputs like API keys, tokens, etc. are saved as secrets so their values are not available.