š¤ How to automatize the Android app release process with CircleCI
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:
- How to do it with old apps using React Native:
Replace release signing config on build.gradle with UPLOAD key instead of RELEASE key
https://reactnative.dev/docs/signed-apk-android#migrating-old-android-react-native-apps-to-use-app-signing-by-google-play - Example of release signing config for new Play App Signing
Publishing to Google Play Store Ā· React Native - How Play App Signing works
Use Play App Signing ā Play Console Help - How to generate the upload keystore
Sign your app | Android Developers - How to generate the upload_certificate.pem from the upload-keystore.jks
Use Play App Signing ā Play Console Help - How to generate the encrypted private key
Sign your app | Android Developers - How to create upload-keystore in base64
Deploy Android applications ā CircleCI
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