Blog Infos
Author
Published
Topics
Published

Image from pexels

 

To release an Android app to the public, every Android Developer needs to build and sign their Android build securely to publish it on Play Store or any other store to download. No store allows unsigned builds to be published.

Signing Android build gives developers ownership of the Android app so that no other developer can release the same app with a different sign.

Even if your project is open source and you’re not planning to publish on any store and distribute through the Github Release page or any other tool, you’ll still need to build and sign your Android build. There are several reasons why a release build is necessary:

  • Performance: A release build is optimised for performance, with code and resources stripped of unnecessary debug information and libraries.
  • Security: Signing the app helps ensure its integrity and authenticity, preventing security issues and harm to users.
  • Updates: Signing enables efficient and secure updates to the app, maintaining user trust.

You can easily use the Android Studio’s built-in Generate Signed Bundle/APK function. Still, it is cumbersome to do manually for every release and it is also not secure to share your keystore file and password with other peers to create a release build. As the quote says

Anything that you do more than twice has to be automated.

In this blog, we will explore how you can use GitHub Actions to automate the process of signing Android build and creating release on GitHub. We will cover the basics of GitHub Actions, the steps involved in building and signing your Android app, and best practices to ensure your app is secure and ready for release. So let’s dive in and learn how to build and sign your Android app securely using GitHub Actions.

Setting up Gradle

Before we can start with the actual implementation, we need to set up signingConfigs in our app-level build.gradleto use environment variables from our GitHub secrets, which we will later set up. The following code will use our decoded keystore file and use environment variables to sign the app.

android {
...
    signingConfigs {
        release {
            storeFile file("keystore.jks")
            storePassword System.getenv("SIGNING_STORE_PASSWORD")
            keyAlias System.getenv("SIGNING_KEY_ALIAS")
            keyPassword System.getenv("SIGNING_KEY_PASSWORD")
        }
    }
...
}
Encoding KeyStore

To sign your android build, you need keystore(.jks) a file. If you don’t already have your keystore file then you can follow the process from the official doc here.

Do not add your keystore file in your project and push along with your project file.

For encoding, we will use the popular Base64 encoding scheme. This scheme will allow converting binary data into a text representation

Text representation will allow us to store the file as text in our GitHub Secrets and later on in the GitHub workflow process decode it back to our original KeyStore file.

To encode the file use these commands based on ur machine.

Mac:
 base64 --i [Jks FilePath] --i [EncodeFilePath].txt

Alpine & Ubuntu:
 base64 [Jks FilePath] > [EncodeFilePath].txt

e.g. `base64 — input ~/Document/Project/keystore.jks — output encoded_keystore.b64

You should see a new file named encoded_keystore.txt

GitHub Actions Workflow

GitHub Actions is Github’s tool to automate the software CI/CD workflows.

GitHub Secrets

GitHub Secrets is the feature that allows users to securely store and manage sensitive information that their workflows need to access, such as API keys, tokens, or passwords.

Using GitHub Secrets, you can avoid exposing sensitive information in your code or workflows. Secrets can be used to store encrypted values and only decrypted during runtime, and they are never shown in the logs or exposed to users.

We used in environment variables in out build.gradle in signingConfigs. To access those variables, we need to create GitHub Secrets with the same name.

SIGNING_KEY_STORE_BASE64

To store base64 encoded text we created in previous step. Copy the content from the file and paste it as secret in the GitHub console

SIGNING_STORE_PASSWORD

To store the password which you used while creating the keystore (.jks) file

SIGNING_KEY_ALIAS

To store the key alias

SIGNING_KEY_PASSWORD

To store the key password you used.

SIGNING_KEY_STORE_PATH

In build.gradle you must have noticed storeFile path or file name. This is secret is to store the path and name of the decoded keystore (.jks) file for easily accessible by the workflow for decoding.

Set the value to keytore.jks

Workflow
on:
  push:
    branches: master
    tags:
      - v*

 release_build:
     runs-on: ubuntu-latest
     if: startsWith(github.ref, 'refs/tags/')
 
     steps:
       - uses: actions/checkout@v2.6.0
 
       - name: Setup JAVA 11
         uses: actions/setup-java@v3
         with:
           distribution: 'corretto'
           java-version: 11
 
       - name: Cache Gradle and wrapper
         uses: actions/cache@v2
         with:
           path: |
             ~/.gradle/caches
             ~/.gradle/wrapper
           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
      
      #1     
       - name: Decode Keystore
         env:
           ENCODED_STRING: ${{ secrets.SIGNING_KEY_STORE_BASE64 }}
           SIGNING_KEY_STORE_PATH: ${{ secrets.SIGNING_KEY_STORE_PATH }}
 
         run: |
           echo $ENCODED_STRING > keystore-b64.txt
           base64 -d keystore-b64.txt > $SIGNING_KEY_STORE_PATH
           
       #2
       - name: Build Release apk
         env:
           SIGNING_KEY_STORE_PATH: ${{ secrets.SIGNING_KEY_STORE_PATH }}
           SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
           SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
           SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
         run: ./gradlew assembleRelease
 
       - name: Build Release bundle
         env:
           SIGNING_KEY_STORE_PATH: ${{ secrets.SIGNING_KEY_STORE_PATH }}
           SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
           SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
           SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
         run: ./gradlew bundleRelease
       #3
       - name: Upload Release Build to Artifacts
         uses: actions/upload-artifact@v3
         with:
           name: release-artifacts
           paths: |
             app/build/outputs/apk/release/
             app/build/outputs/bundle/release/
       #4      
       - name: Create Github Release
         uses: softprops/action-gh-release@v1
         with:
           generate_release_notes: true
           prerelease: true
           files: |
             app/build/outputs/apk/release/app-release.apk
             app/build/outputs/bundle/release/app-release.aab

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

From Chaos to Consistency: Managing Build and Release for 25+ Android Repos with Github Actions

Managing the build and release process for over 25 Android repositories can be a daunting task. With each repository having its own pipeline or workflow, it can become difficult to ensure consistency and quality across…
Watch Video

From Chaos to Consistency: Managing Build and Release for 25+ Android Repos with Github Actions

Shrikant Ballal
Staff Engineer
YML

From Chaos to Consistency: Managing Build and Release for 25+ Android Repos with Github Actions

Shrikant Ballal
Staff Engineer
YML

From Chaos to Consistency: Managing Build and Release for 25+ Android Repos with Github Actions

Shrikant Ballal
Staff Engineer
YML

Jobs

This is the job to build a release apk and bundle, upload artifacts and finally create a release.

This is the job release_build. This will run on ubuntu-latest and this condition startsWith(github.ref, 'refs/tags/')will make sure that this job will only be triggered when the tag value be pushed in the master branch.

I will directly jump to the Decode keystore step but if you’re unfamiliar with steps before this then you should check my previous article, I have explained this in that.

Streamline Your Android Builds using GitHub Actions

#1 Decode Keystore

First, we create the env variable to be used in the command. run: will run 2 commands.

  • First to store the encode keystore text data to keystore-b64.txt
  • Second to decode the encoded text to its original value.

#2 Build Release APK and Bundle

./gradlew assembleRelease and ./gradlew bundleRelease will build the release APK and bundle respectively in your project build folder which will access in the next step to upload.

#3 Upload Release Build to Artifacts

In this step, we will use the upload-artifact GitHub Action to upload our release builds. This action will take the multiple paths of artifacts which you want to upload.

By default, gradle generate release signed APK in this path app/build/outputs/apk/release/ and bundle in this path app/build/outputs/bundle/release/ So we have used both of these paths.

#4 Create Github Release

In this step, we will use action-gh-release to create a release for your project. By default release name will be the tag that is pushed to the main branch. This action accepts take inputs to make the GitHub release more customisable and informative.

generate_release_notes It will create the release notes for free since your last release

prerelease Indicator of whether or not

filesNewline-delimited globs of paths to assets to upload for release

You can find more inputs in the GitHub Repository. Below is the release it will generate something like that for you.

Workflow in Action

Push the tag in the main branch then go to the Actions tab in your project and see the release Workflow in Action. This shows all the steps taken to complete this job.

Conclusion

In this article, we have discussed how we can automate and simplify the process of building the release for the android project without compromising with our keystore file by uploading it to the project.

We used GitHub secrets to save encoded keystore file and later decode it to temporary file to use for building the release.

If you run a public repository, this strategy is extremely helpful. The trade-off is that throughout the workflow process, the KeyStore file is temporarily decoded to the original file.

This solution can be applied to any other CI pipeline that supports for the store the secrets, not just GitHub Workflows.

By setting up this workflow, you can automate the build procedure, sign the release build, and distribute it to your users. This can help you save time and effort and streamline and improve the release process. With the expertise of GitHub Actions at your fingertips, you can focus on building your app and offering customers with new features while GitHub handles the rest.

If you have come this far, I hope you have learned something new today.

Feel free to connect with me on Twitter

If you’re interested in Kotlin Multiplatform, then you should check out my previous article. How you can start with KMM

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Life is hard. We are engulfed in tasks that take time, are boring, and…
READ MORE
blog
As a developer working on various Kotlin Multiplatform projects, whether for your job or…
READ MORE
blog
There are a lot of blogs and videos on why we need CI/CD. From…
READ MORE
blog
When you edit your Github Actions workflows it would be nice to have a…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu