×
Abhishek Tiwari
Software Engineer
Abhishek Tiwari, a Software Engineer at Nitor Infotech, has more than two years of experience in React Native, mobile app development, and i... Read More

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/[email protected] 
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 }}
  1. The Android APK will be signed using r0adkll/sign-android-release@v1 using the private signing keys we created in the earlier stage.
  2. The environment variables that must be supplied as Github secrets are listed below:
  3. The base64-encoded signing key used to sign your app is ANDROID SIGNING KEY.
  4. On *nix computers, you can create your key by using this command.
  5. openssl base64 < your-upload-key.keystore | tr -d ‘\n’ | tee my-upload-key.keystore.base64.txt
  6. Then transfer the text file contents to your private ANDROID SIGNING KEY GH location.
  7. The alias of your signing key is ANDROID ALIAS.
  8. ANDROID KEY STORE PASSWORD: Your signing Keystore’s password
  9. The private key password for your signing Keystore is ANDROID KEY PASSWORD.
  10. 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/[email protected] 
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.

subscribe image

Subscribe to our
fortnightly newsletter!

we'll keep you in the loop with everything that's trending in the tech world.

We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it. Accept Cookie policy