Android CI/CD part 1: Locally building and pushing to Play Store using Fastlane

Automating the deployment of Android apps to Google Play, I believe is a must for teams with more than 1-2 people. Having the latest test version on your physical device, allows you to perform more thorough testing and allows non-engineer members of the team to play with the app and maybe even contribute to QA.

Even though Play Console is elegant and intuitive, having to manually do these test releases is arduous and a waste of time. The good news is that Play Console provides an API to perform almost all the publishing actions. The bad news is that it takes some time to set it up and requires many steps.

In this 2 posts series, I aim to give the simplest way possible to set up a CI/CD system for Android apps using GitHub Actions. The result would be to push to a specific branch (e.g. called staging) and the Android app will be deployed to Play Console's internal testing channel. If you add and properly opt-in testers in that channel, then they will get an available update for the specific app within a few minutes (for an overview of Play Console's distribution capabilities, check this post).

In general, most CI/CD platforms such as GitHub Actions do not offer running them locally. This makes it hard to debug while setting up the system. Therefore, my preference is to create pipelines that are as CI/CD-agnostic as possible. In this case, I will be using Fastlane which is a tool for automating many app-specific workflows, for iOS & Android, including pushing to the Play Console Internal channel. Once we set this up, and test it locally, we will just run this in the GitHub Action runner in the next post.

Set up access to the Google Play Developer API

The first step in this journey is to get access to the API that allows you to make publishing actions in the Google Play Console. The steps to set this up are listed here. What you want to end up with, is a JSON file that has the credentials for a service account that has access to your developer account. Since these steps are confusing at first, I am listing here an overview of what needs to be done (at the time of writing):

  • Enable the API from the API Access page in Play Console, and create/link the developer account to a Google Cloud project (only the developer account owner can do that, admins cannot).
  • For the Google Cloud project you created/linked, in Google Cloud Console, create a Service Account. This is similar to a normal Google user account (it even has an email assigned, ending in gserviceaccount.com) but is only used to perform API actions.
  • For the created Service Account, in Google Cloud Console, go to the Keys tab and create a new key. When asked, choose the JSON format and save that file on your PC.
  • In Play Console, go to the Users & Permissions page and grant the service account the necessary permissions to release the app (in this case all the permissions in the Release section). Do not worry, when you invite a Service Account to join a developer account, they auto-join (they don't need to "accept" the invitation).

Set up Fastlane

Fastlane is an open-source software used for automating many parts of the mobile development world (for both iOS & Android), including releasing to the app stores. The result is running a single command and the Android app is bundled into an ABB, signed, and pushed to the configured Play Console test channel.

To get started, install Fastlane on your machine and run fastlane init . This will create a fastlane/ folder with 2 files: Fastfile and Appfile (Note: a Gemfile will be created in that parent directory as well - include it without any modification when committing). Replace the content of those files with the following:

package_name(ENV["STAGING_PACKAGE_NAME"])
Appfile
default_platform(:android)

platform :android do
  lane :deploy do
    gradle(
      task: "clean bundle",
      flavor: "staging",
      build_type: "Release",
      print_command: true,
      properties: {
        "android.injected.signing.store.file" => 
        	ENV["STAGING_KEYSTORE_FILE"],
        "android.injected.signing.store.password" => 
        	ENV["STAGING_KEYSTORE_PASSWORD"],
        "android.injected.signing.key.alias" => 
        	ENV["STAGING_KEY_ALIAS"],
        "android.injected.signing.key.password" => 
        	ENV["STAGING_KEY_PASSWORD"],
      }
    )

    upload_to_play_store(
      json_key: ENV["GPLAY_SERVICE_ACCOUNT_KEY"],
      track: "internal",
      skip_upload_metadata: true,
      skip_upload_changelogs: true,
      skip_upload_images: true,
      skip_upload_screenshots: true,
      timeout: 600,
    )
  end
end
Fastfile

In a nutshell, we replaced all the necessary bits of data with environment variables. The bulk of the information is placed in Fastfile. Fastlane has excellent documentation for the actions (gradle, upload_to_play_store) used here, but tl;dr there are 2 steps: the AAB build and signing, and the push to Play Store.

Now create a bash script file (assuming you use a Unix/Linux OS - but the equivalent can be done on Windows), where you will be setting these environment variables and then running the fastlane command. Do not forget to add this file to your .gitignore since sensitive info might live here.

#!/usr/bin/env bash

export STAGING_PACKAGE_NAME="staging.package.name"

export STAGING_KEYSTORE_FILE="/path/to/keystore_file"
export STAGING_KEYSTORE_PASSWORD="keystore_passowrd"
export STAGING_KEY_ALIAS="key_alias"
export STAGING_KEY_PASSWORD="key_password"

export GPLAY_SERVICE_ACCOUNT_KEY="/path/to/service_account_key.json"

fastlane deploy

Running this bash script now, will initiate a build, subsequent signing with the specified key, and finally an upload to the Play Console's Internal channel.

Just a few quirks if this is the first time you are uploading an app to the Play Console. You would need to first upload an AAB to the Internal track manually before you can set up this automation. This is for the package name to be associated with the app in Play Console. Also, to use the API with the Internal track, you would first need to publish at least once in the Closed  Alpha channel, but after you do this once, there's no need to touch the Closed Alpha again.

Hopefully, this was helpful enough for your CI/CD journey. In the next post, we will cover how to run this in the GitHub Actions automatically when pushed to a specific branch.

Happy coding!


In this series:

Android CI/CD part 2: Building and pushing to Play Store in GitHub Actions
In this post, we will show how to create a CI/CD system for Android that gets triggered when you push to a specific branch.