šŸ¤– How to automatize the Android app release process with CircleCI

VerĆ³nica Valls
Game & Frontend Development Stuff
8 min readJan 10, 2023

On the previous article, I talked about the joy of releasing a new version of our app and how we lost that joy when doing the app release process manually.

We learnt how to automatize the app release process on iOS with CircleCI, on this article, weā€™ll learn how to do the same with the Android release process.

šŸ” Define Google Play Store account settings & CircleCI environment variables

Set connection from circleCI to Google Play Store

In first place, we need to collect our Google Play credentials to be able to connect with the Google Play Store API. This is done by following the instructions on the Collect your Google credentials section on this link or on the following screenshot:

Youā€™ll need to have special permissions to access this data.

Create the Appfile

We need to create the Appfile containing the json key file path and the pakage name:

your_project/android/fastlane/Appfile

json_key_file("android/your-google-play-key.json")
package_name("com.bla.bla")

Add the Google credentials to CircleCI

We should create CircleCI environment variables for the following values gotten from the ā€œSet connection from CircleCI to Google Play storeā€ step by going to your project > āš™ļø Project Settings > Environment Variables > Add environment variable.

$BASE64_KEYSTORE - Your base64 keystore, generated from the ā€œSet connection from CircleCI to Google Play storeā€ step.

$GOOGLE_PLAY_KEY - The full content of your api.json file, generated from ā€œSet connection from CircleCI to Google Play storeā€ step.

$RELEASE_KEY_ALIAS - Your key alias.

$RELEASE_KEY_PASSWORD - Your key password.

$RELEASE_STORE_PASSWORD - Your keystore password

Prepare Gem dependencies for Android Fastfile

If this is the first Android integration you do for CircleCI, youā€™ll need to set the Gem dependencies for Android platform.

First, create the Gemfile with the following content:

your_project/android/Gemfile

source "https://rubygems.org"
ruby ">= 2.6.1"

gem "fastlane"

Second, on your Terminal, go to your project folder and run:

cd android
bundle install

// after these steps, commit and push the Gemfile.lock

Finally, on the config.yml file, youā€™ll create the Android release job, and one of its steps will be:


- run:
name: Install Gem dependencies for android Fastfile
command: bundle install working_directory: android

Weā€™ll dive deeper into this, two sections ahead.

šŸš€ Define the Fastfile Release lane

We need to create a new lane to specify the needed steps in order to fulfill successfully a new release to the Google Play Store.

Create playstore lane

Letā€™s go to create our Fastfile, which will be created on your-project-folder/android/fastlane/Fastfile. On this file weā€™ll define all our desired lanes related with Android processes.

This article will cover creating a lane for creating a new release on Google Play Store and uploading a previously created release APK to the new release.

šŸ†™ upload_to_play_store command

This command makes all the magic related with creating a new release and uploading the latest production APK created. Letā€™s take a look at the command params:

apk : The folder where the release APK generated by a step of the Android release job on CircleCI is placed.

track : The track where the release will be done, if we want to create a release for production, weā€™ll use production .

release_status : If we fill completed , the new release will be automatically published, so we donā€™t need to click on it manually from Google Play Console page. If thatā€™s not the case and we prefer to review it manually, weā€™ll fill it like draft .

skip_upload_changelogs : Check this parameter to false , in order to force the upload of the Whatā€™s new changelogs, weā€™ll check how to do this later on this article.

json_key : In order to successfully log in Google Play Store API when uploading the new release, we need to attach the name of our Google Play key json.

# your-project-folder/android/fastlane/Fastfile

default_platform(:android)

platform :android do
before_all do
setup_circle_ci
end

desc "Upload APK to Google Play Store"
lane :playstore do
# gradle(task: "bundle")
upload_to_play_store(
apk: './app/build/outputs/apk/release/your-app-release.apk',
track: 'production',
release_status: 'draft',
skip_upload_changelogs: false,
json_key: 'your-google-play-key.json'
)
end

after_all do |lane|
# This block is called, only if the executed lane was successful

# slack(
# message: "Successfully deployed new App Update."
# )
end

error do |lane, exception|

end
end

šŸ¾ Define the Release job on config.yml

Once our lane is finished, we need to create a job that calls this lane among other processes to have our flow ready.

Prepare the credentials data

Weā€™ll need to create the following steps related with the first section ā€œDefine Google Play Store account settings & CircleCI environment variablesā€ in order to have all the credentials data available during the release job:

- run:
name: Decode keystore
working_directory: android
command: echo $KEYSTORE_RELEASE_BASE64 | base64 -d | tee app/your-project.keystore > /dev/null
- run:
name: Create keystore.properties
working_directory: android
command: echo -e "releaseKeyAlias=$RELEASE_KEY_ALIAS\nreleaseKeyPassword=$RELEASE_KEY_PASSWORD\nreleaseKeyStore=$RELEASE_KEYSTORE\nreleaseStorePassword=$RELEASE_STORE_PASSWORD" > your-keystore.properties
- run:
name: Create Google Play key
working_directory: android
command: echo $GOOGLE_PLAY_KEY > your-google-play-key.json

Define the upload to Google Play Store

This will be the final step on our Android release job:

- run: 
name: Upload to Google App Store
command: cd android && bundle exec fastlane playstore --verbose

The config.yml release job skeleton will be something like this:

android-release:
working_directory: ~/your-project-folder
docker:
- image: circleci/android:api-28-node

steps:

- run:
name: Decode keystore
working_directory: android
command: echo $KEYSTORE_RELEASE_BASE64 | base64 -d | tee app/your-project.keystore > /dev/null

- run:
name: Create keystore.properties
working_directory: android
command: echo -e "releaseKeyAlias=$RELEASE_KEY_ALIAS\nreleaseKeyPassword=$RELEASE_KEY_PASSWORD\nreleaseKeyStore=$RELEASE_KEYSTORE\nreleaseStorePassword=$RELEASE_STORE_PASSWORD" > your-keystore.properties

- run:
name: Build release
working_directory: android
command: ./gradlew assembleRelease -x bundleReleaseJsAndAssets

- store_artifacts:
path: android/app/build/outputs/apk/release/
destination: artifact-file

- run:
name: Create Google Play key
working_directory: android
command: echo $GOOGLE_PLAY_KEY > your-google-play-key.json

- run:
name: Install Gem dependencies for android Fastfile
command: bundle install
working_directory: android

- run:
name: Upload to Google App Store
command: cd android && bundle exec fastlane playstore --verbose

šŸ•¹ Define the Release workflow on config.yml

Weā€™re going to define two jobs on the config.yml file:

  • request-apk-and-prepare-release: In order to be able to decide when we want to create a release, this job will give us the control to decide on which branch we want to trigger a new Android release.
  • android-release: When clicking on the thumb up from request-apk-and-prepare-release job, the Android release process will be triggered, so a new release will be created on Google Play Store and the new generated APK will be uploaded.

The workflows skeleton would be something like this:

workflows:
version: 2
android:
jobs:
- request-apk-and-prepare-release:
type: approval
filters:
<<: *filters-node
- apk-release:
requires:
- request-apk-and-prepare-release
filters:
<<: *filters-node

And this is how it would look on circleCI:

šŸ†• Upload Whatā€™s new changelogs automatically

Itā€™s very easy to automatize the upload of the release notes for each new Android release, so we donā€™t need to go to the Google Play Store and fill it for each language. Here is the last step to follow:

  • Create a folder for each one of your app supported languages on the following location:
android/fastlane/metadata/android/en-US/changelogs/default.txt
android/fastlane/metadata/android/es-ES/changelogs/default.txt
android/fastlane/metadata/android/fr-FR/changelogs/default.txt

On the default.txt files, add the text as you would do it.

This, along with the ā€œskip_upload_changelogsā€ param set to false on the upload_to_play_store command, upload the Whatā€™s new notes automatically.

šŸ¤” How about building and uploading an .aab?

On the case of generating an AAB instead of an APK, we first have to enroll on Play App Signing from Google. You can follow the instructions on these links to generate the needed files and to configure it properly:

Google Play Console: Release > Setup > App integrity: Configure Play App Signing šŸ‘‡

On .circleci/config.yml, on android-release job, we add the following step to append the upload key store data to the gradle.properties file:

- run:
name: Add upload key data to gradle.properties
working_directory: android
command: echo -e "MYAPP_UPLOAD_KEY_ALIAS=$MYAPP_UPLOAD_KEY_ALIAS\nMYAPP_UPLOAD_KEY_PASSWORD=$MYAPP_UPLOAD_KEY_PASSWORD\nMYAPP_UPLOAD_STORE_FILE=$MYAPP_UPLOAD_STORE_FILE\nMYAPP_UPLOAD_STORE_PASSWORD=$MYAPP_UPLOAD_STORE_PASSWORD" >> gradle.properties

On the ā€œDecode keystoreā€ and ā€œCreate keystore.propertiesā€œ steps, weā€™ll need to change the release key data by the upload key data too.

The ā€œBuild releaseā€ step would look like this:

- run:
name: Build aab release
working_directory: android
no_output_timeout: 30m
command: ./gradlew bundleRelease

- store_artifacts:
path: android/app/build/outputs/bundle/release/
destination: artifact-file

On the Fastfile, on the ā€œUpload AAB to Google Play storeā€ command, we would change the apk prop by the aab like this:

upload_to_play_store(
aab: './app/build/outputs/bundle/release/your-app-release.aab',
...

šŸ¤ž Donā€™t forget to add the new upload keystore data as environment variables on circleCI like we did previously with the release keystore.

Congratulations, youā€™ve made it! Now your Android release process is automated! šŸ„³

āœØ How to delete a draft release with an associated APK on Google Play

Unlike iOS, when a release + APK is first sent and created on Google Play Developer Console through CircleCI, if the workflow is triggered again from CircleCI, it will fail telling us that it already exists an APK on that release or that it already exists a release with the current versionCode.

On Google Play Developer is very easy to delete a draft release and not that easy to delete the related APK at first sight šŸ˜“

First, go to Production, click on Edit release from the desired release, click on Discard release:

Second, Go to App bundle explorer, select the desired release, and click on Delete APK:

šŸ“š Docs

Documentation & extra tutorials
-
Deploy Android applications ā€” CircleCI
- Setup on Android ā€” fastlane docs
- Deploy Android applications: Setup config ā€” CircleCI
- Fastlane + CircleCI for Android ā€” Part 4
- Deploy to Google Play | GameCI
- Configure fastlane for deployment to google play store ā€” CircleCI
- upload_to_play_store ā€” fastlane docs
- šŸŽ How to automatize the iOS app release process with CircleCI

--

--

VerĆ³nica Valls
Game & Frontend Development Stuff

Mobile & frontend developer for real world projects. Game designer/developer for my mindā€™s delirium ideas. Cats & dogs dietetical and nutritional advisor.