I hope you have been following my previous blog on Github actions and how to set up manual triggers. Also read the second part on how to set up ruby in the CI/CD realm.
In this blog, I am going to focus mainly on Android-related steps, like JDK set up, building aab, and uploading to the Play store.
Let’s get started with setting up the JDK environment!
Setting Up JDK Environment (Android only)
-
Set Up JDK Environment
- name: JDK environment Set-Up uses: actions/setup-java@v1.4.3 with: java-version: 11.0.15
-
Decode Keystore
- name: Decode Keystore for Nitor # decode keystore and generate
jks/keystore file
if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' ) }}
id: decode_keystore_for_Nitor
uses: timheuer/base64-to-file@v1
with:
fileName: "Nitor.jks"
encodedString: ${{ secrets.ANDROID_SIGNING_KEY }}
1. Move keystore android/app
- name: "move keystore Nitor"
if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging') }}
run: |
mv ${{steps.decode_keystore_for_Nitor.outputs.filePath}} android/app
ls android/app
2. Make Gradlew Executable
- name: Make Gradlew Executable run: cd android && chmod +x ./gradlew
3. Generate App Bundle
4. Before moving ahead, we need to modify android/app/build.gradle file as we are also going to pass minor version and major version.
5. You can create function to update the versionCode and versionName from the command line:
def getVersionName = { ->
def name = project.hasProperty('versionName') ? versionName : "1.0"
return name
}
def getVersionCode = { ->
def code = project.hasProperty('versionCode') ? versionCode.toInteger() : 1
return code
}
6. Then in the Android block:
defaultConfig {
applicationId "your.app.id"
minSdkVersion 15
targetSdkVersion 23
versionCode getVersionCode()
versionName geVersionName()
--------
--------
}
You can truly just refer to any task as:
./gradlew assembleDebug -PversionCode=483 -PversionName=4.0.3
You can read more about it here: https://web.archive.org/web/20160119183929/https://robertomurray.co.uk/blog/2013/gradle-android-inject-version-code-from-command-line-parameter/
- name: Generate App Bundle for Nitor / staging
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }}
run: |
cd android
./gradlew bundleNitorstagingRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
-
Sign generated App Bundle
- name: Sign APK For Nitor / staging
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }}
id: sign_app_Nitor_staging
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitorstagingRelease/
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
- The Android APK will be signed using r0adkll/sign-android-release@v1 using the private signing keys we created in the earlier stage.
- The environment variables that must be supplied as Github secrets are listed below:
- The base64-encoded signing key used to sign your app is ANDROID SIGNING KEY.
- On *nix computers, you can create your key by using this command.
- openssl base64 < your-upload-key.keystore | tr -d ‘\n’ | tee my-upload-key.keystore.base64.txt
- Then transfer the text file contents to your private ANDROID SIGNING KEY GH location.
- The alias of your signing key is ANDROID ALIAS.
- ANDROID KEY STORE PASSWORD: Your signing Keystore’s password
- The private key password for your signing Keystore is ANDROID KEY PASSWORD.
- Open your Github project home page > Project Setting > Secrets > Actions > new repository secret to add new GH secrets.
How to Upload the App to Google Play?
- name: Upload App to Google Play Nitor / staging
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.Nitor.staging
releaseFiles: android/app/build/outputs/bundle/NitorstagingRelease/*.aab
track: internal
status: completed
inAppUpdatePriority: 2
To upload the app to Google Play, we will largely follow the same procedure, but we will utilize the .aab bundle rather than the APK.
You must create a new service account to upload your .aab to Google Play. Remember:
⚠️ ️Delete the signing of the release aab with the debug credentials from the android/app/build.gradle file.
⚠️ Only Owners can produce service account JSON files.
The process of creating an Android service account is explained in detail here – Generate Service account JSON file
The content of the JSON file can then be uploaded to GitHub secrets with the name ANDROID SERVICE ACCOUNT JSON TEXT.
****************************************************************
Here’s a useful link: https://www.obytes.com/blog/react-native-ci-cd-github-action
Explore how you can add a self-hosted-runner here: https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners

Know how our DevOps implementation improved the CI/CD engine for a leading software company.
Kindly refer to the following .yml file which I have used as an example.
name: Android Build Deployment
on:
workflow_dispatch: # manually triggers with inputs
inputs:
target:
description: "Target"
required: true
default: "Nitor"
type: choice
options:
- Nitor
- NitorPro
flavor:
description: "Environment"
required: true
default: "staging"
type: choice
options:
- staging
- development
- production
minorversion:
description: "Minor Version" #versionCode
required: true
majorversion:
description: "Major Version" #versionName
required: true
jobs:
android:
name: Publish App To Play Store
runs-on: self-hosted # [ubuntu-latest, macOS-latest, self-hosted]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
with:
persist-credentials: false # make it false if you are going to clone
private repositories in yarn
- name: JDK environment Set Up
uses: actions/setup-java@v1.4.3
with:
java-version: 11.0.15
- name: Decode Keystore for Nitor # decode keystore and generate
jks/keystore file
if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' ) }}
id: decode_keystore_for_Nitor
uses: timheuer/base64-to-file@v1
with:
fileName: "Nitor.jks"
encodedString: ${{ secrets.ANDROID_SIGNING_KEY }}
- name: Decode Keystore for NitorPro # decode keystore and generate
jks/keystore file
if: ${{ github.event.inputs.target == 'NitorPro' && (github.event.inputs.flavor == 'development' || github.event.inputs.flavor == 'production') }}
id: decode_keystore_for_NitorPro
uses: timheuer/base64-to-file@v1
with:
fileName: "NitorPro.jks"
encodedString: ${{ secrets.NITORPRO_RELEASE_STORE_FILE }}
- name: Decode build.gradle # decode build.gradle and generate build.gradle file
id: decode_app_build_gradle
uses: timheuer/base64-to-file@v1
with:
fileName: "build.gradle"
encodedString: ${{ secrets.GRADLE}}
- name: Decode .env.Nitor.staging # decode .env.Nitor.staging and generate
.env.Nitor.staging file
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging'}}
id: decode_Nitor_staging
uses: timheuer/base64-to-file@v1
with:
fileName: ".env"
encodedString: ${{ secrets.NITOR_STAGING}}
- name: Decode .env.Nitor.development # decode .env.Nitor.development and
generate .env.Nitor.development file
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development'}}
id: decode_Nitor_development
uses: timheuer/base64-to-file@v1
with:
fileName: ".env"
encodedString: ${{ secrets.NITOR_DEVELOPMENT}}
- name: Decode .env.Nitor.production # decode .env.Nitor.production and
generate .env.Nitor.production file
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production'}}
id: decode_Nitor_production
uses: timheuer/base64-to-file@v1
with:
fileName: ".env"
encodedString: ${{ secrets.NITOR_PRODUCTION}}
- name: Decode .env.NitorPro.staging # decode .env.NitorPro.stagingand
generate .env.NitorPro.staging file
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging'}}
id: decode_NitorPro_staging
uses: timheuer/base64-to-file@v1
with:
fileName: ".env"
encodedString: ${{ secrets.NITORPRO_STAGING}}
- name: Decode .env.NitorPro.development # decode .env.NitorPro.development and
generate .env.NitorPro.development file
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development'}}
id: decode_NitorPro_development
uses: timheuer/base64-to-file@v1
with:
fileName: ".env"
encodedString: ${{ secrets.NITORPRO_DEVELOPMENT}}
- name: Decode .env.NitorPro.production # decode .env.NitorPro.production and
generate .env.NitorPro.production file
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production'}}
id: decode_NitorPro_production
uses: timheuer/base64-to-file@v1
with:
fileName: ".env"
encodedString: ${{ secrets.NITORPRO_PRODUCTION}}
# - name: move "jks" to android/app
- name: "move keystore Nitor"
if: ${{ github.event.inputs.target == 'Nitor' || (github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging') }}
run: |
mv ${{steps.decode_keystore_for_Nitor.outputs.filePath}} android/app
ls android/app
- name: "move keystore NitorPro"
if: ${{ github.event.inputs.target == 'NitorPro' && (github.event.inputs.flavor == 'development' || github.event.inputs.flavor == 'production')}}
run: |
mv ${{steps.decode_keystore_for_NitorPro.outputs.filePath}} android/app
ls android/app
- name: "move build gradle"
run: |
mv ${{steps.decode_app_build_gradle.outputs.filePath}} android/app
ls
cat android/app/build.gradle
- name: "move Nitor_staging"
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging'}}
run: |
mv ${{steps.decode_Nitor_staging.outputs.filePath}} ./
pwd
cat .env
- name: "move Nitor_development"
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development'}}
run: |
mv ${{steps.decode_Nitor_development.outputs.filePath}} ./
pwd
cat .env
- name: "move Nitor_production"
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production'}}
run: |
mv ${{steps.decode_Nitor_production.outputs.filePath}} ./
pwd
cat .env
- name: "move NitorPro_staging"
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging'}}
run: |
mv ${{steps.decode_NitorPro_staging.outputs.filePath}} ./
pwd
cat .env
- name: "move NitorPro_development"
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development'}}
run: |
mv ${{steps.decode_NitorPro_development.outputs.filePath}} ./
pwd
cat .env
- name: "move NitorPro_production"
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production'}}
run: |
mv ${{steps.decode_NitorPro_production.outputs.filePath}} ./
pwd
cat .env
- name: git config with PAT_GITHUB
run: git config --global url.https://${{ secrets.PAT_GITHUB }}
@github.com/.insteadOf https://github.com
- name: Set up our node configuration
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: yarn Install dependencies
if: |
steps.cache-yarn-cache.outputs.cache-hit != 'true' ||
steps.cache-node-modules.outputs.cache-hit != 'true'
run: |
yarn install --network-concurrency 1
- name: Make Gradlew Executable
run: cd android && chmod +x ./gradlew
# Generate App Bundle for Nitor / staging
- name: Generate App Bundle for Nitor / staging
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }}
run: |
cd android
./gradlew bundleNitorstagingRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
# Generate App Bundle for Nitor / development
- name: Generate App Bundle for Nitor / development
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development' }}
run: |
cd android
./gradlew bundleNitordevelopmentRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
# Generate App Bundle for Nitor / production
- name: Generate App Bundle for Nitor / production
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production' }}
run: |
cd android
./gradlew bundleNitorRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
# Generate App Bundle for NitorPro / staging
- name: Generate App Bundle for NitorPro / staging
run: |
cd android
./gradlew bundleNitorProstagingRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' }}
# Generate App Bundle for NitorPro / development
- name: Generate App Bundle for NitorPro / development
run: |
cd android
./gradlew bundleNitorProdevelopmentRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development' }}
# Generate App Bundle for NitorPro / production
- name: Generate App Bundle for NitorPro / production
run: |
cd android
./gradlew bundleNitorProRelease -PversionCode=${{
github.event.inputs.minorversion }} -PversionName=${{
github.event.inputs.majorversion }}
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production' }}
# sign generated aab for Nitor / staging - name: Sign APK For Nitor / staging
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }}
id: sign_app_Nitor_staging
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitorstagingRelease/
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
# sign generated aab for Nitor / development
- name: Sign APK For Nitor / development
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development' }}
id: sign_app_Nitor_development
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitordevelopmentRelease/
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
# sign generated aab for Nitor / production
- name: Sign APK For Nitor / production
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production' }}
id: sign_app_Nitor_production
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitorRelease/
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
# sign generated aab for NitorPro / staging
- name: Sign APK For NitorPro / staging
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' }}
id: sign_app_NitorPro_staging
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitorProstagingRelease/
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
# sign generated aab for NitorPro / development
- name: Sign APK For NitorPro / development
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development' }}
id: sign_app_NitorPro_development
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitorProdevelopmentRelease/
signingKeyBase64: ${{ secrets.NITORPRO_RELEASE_STORE_FILE }}
alias: ${{ secrets.NITORPRO_RELEASE_KEY_ALIAS }}
keyStorePassword: ${{ secrets.NITORPRO_RELEASE_STORE_PASSWORD }}
keyPassword: ${{ secrets.NITORPRO_RELEASE_KEY_PASSWORD }}
# sign generated aab for NitorPro / production
- name: Sign APK For NitorPro / production
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production' }}
id: sign_app_NitorPro_production
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/bundle/NitorProRelease/
signingKeyBase64: ${{ secrets.NITORPRO_RELEASE_STORE_FILE }}
alias: ${{ secrets.NITORPRO_RELEASE_KEY_ALIAS }}
keyStorePassword: ${{ secrets.NITORPRO_RELEASE_STORE_PASSWORD }}
keyPassword: ${{ secrets.NITORPRO_RELEASE_KEY_PASSWORD }}
#Upload App to Google Play For Nitor / staging
- name: Upload App to Google Play Nitor / staging
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'staging' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.Nitor.staging
releaseFiles: android/app/build/outputs/bundle/NitorstagingRelease/*.aab
track: internal
status: completed
inAppUpdatePriority: 2
#Upload App to Google Play For Nitor / development
- name: Upload App to Google Play Nitor / development
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'development' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.Nitor.dev
releaseFiles: android/app/build/outputs/bundle/NitordevelopmentRelease/*.aab
track: internal
status: completed
inAppUpdatePriority: 2
#Upload App to Google Play For Nitor / production
- name: Upload App to Google Play Nitor / production
if: ${{ github.event.inputs.target == 'Nitor' && github.event.inputs.flavor == 'production' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.Nitor
releaseFiles: android/app/build/outputs/bundle/NitorRelease/*.aab
track: internal
status: completed
inAppUpdatePriority: 2
#Upload App to Google Play For NitorPro / staging
- name: Upload App to Google Play NitorPro / staging
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT
}} # please update this with NitorPro android
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.NitorPro.staging
releaseFiles: android/app/build/outputs/bundle/NitorProstagingRelease/*.aab
track: internal
status: draft
inAppUpdatePriority: 2
#Upload App to Google Play For NitorPro / development
- name: Upload App to Google Play NitorPro / development
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'development' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} # please update this with NitorPro android
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.NitorPro.dev
releaseFiles: android/app/build/outputs/bundle/NitorProdevelopmentRelease/*.aab
track: internal
status: completed
inAppUpdatePriority: 2
#Upload App to Google Play For NitorPro / production
- name: Upload App to Google Play NitorPro / production
if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'production' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }} # please update this with NitorPro android
serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON_TEXT }}
packageName: com.Nitor.NitorPro
releaseFiles: android/app/build/outputs/bundle/NitorProRelease/*.aab
track: internal
status: completed
inAppUpdatePriority: 2
So, this is how you can go about the Android setup of CI/CD with GitHub Actions! Mail us with your thoughts about this setup and visit us at Nitor Infotech to learn about our offerings.