DEV Community

Cover image for Automating Building Android APKs with GitHub Actions
Iván Valdés
Iván Valdés

Posted on

Automating Building Android APKs with GitHub Actions

As part of my goal to decrease my smartphone addiction, I recently bought a Unihertz Jelly phone, which could be a good compromise between a smartphone and a dumbphone. The phone has a multi-purpose function button. You can assign it to specific pre-defined actions or launch any application. As I regularly use the phone screen in grayscale mode, I decided to assign the button to an application that would toggle this mode. I searched and couldn't find a simple app to toggle it when launching. So I decided to write my own. However, this previous background could be a post on its own.

As I've been trying to leverage GitHub actions for manual tasks, I searched GitHub and found many actions that could do this for me. I tried with a couple of them, and they didn't work. One of the issues is that the actions are using obsolete dependencies, and I spent way more time trying to make them run than I wanted.

I decided to make it work without a pre-built action and write all of the required steps in a GitHub workflow, which, in the end, was very simple. Here's the action. I will do a walk-through so you can use it in your projects.

name: Release APK

on:
  push:
    tags:
      - '*'
Enter fullscreen mode Exit fullscreen mode

This workflow will run when pushing a tag to a repository.

jobs:
  Gradle:
    runs-on: ubuntu-latest
    steps:
    - name: checkout code
      uses: actions/checkout@v3
Enter fullscreen mode Exit fullscreen mode

This is the standard step to checkout the code. It can run with the latest checkout action (v3).

    - name: setup jdk
      uses: actions/setup-java@v3
      with:
        java-version: 17
        distribution: 'temurin'
Enter fullscreen mode Exit fullscreen mode

Then, we need to set up the JDK. Again, using the latest setup-java action (v3). I set the Java version to 17, which is the one that my Android Project uses. You can adjust the distribution, too. Refer to setup-javas documentation.

Now, to sign the released APK, we'll need to load the Keystore (you can read how to generate it on Android's official documentation).

   - name: Load keystore
      run: |
        echo "${{ secrets.KEYSTORE_FILE_CONTENTS }}" | base64 -d > keystore.jks && \
Enter fullscreen mode Exit fullscreen mode

The secret KEYSTORE_FILE_CONTENTS, contains the base64 encoded content of the keystore.jks file you generated. You can get the text for the secret by running cat keystore.jks | base64 -w0.

    cat <<EOL > local.properties
        storeFilePath=$PWD/keystore.jks
        storePassword=${{ secrets.RELEASE_STORE_PASSWORD }}
        keyAlias=${{ secrets.RELEASE_KEY_ALIAS }}
        keyPassword=${{ secrets.RELEASE_KEY_PASSWORD }}
        EOL
Enter fullscreen mode Exit fullscreen mode

The rest of the secrets are the ones you entered when configuring your keystore.

Next, we'll build the APK:

    - name: Build Release APK
      run: ./gradlew assembleRelease
      env:
        GRADLE_USER_HOME: ./gradle-config
Enter fullscreen mode Exit fullscreen mode

And set the current directory as a safe repository, as this is a known issue with actions/checkout@v3. And because we'll later use github-cli to do the release, I didn't want it to complain that it couldn't read the repository.

    - name: Set current directory as a safe repository
      run: git config --global --add safe.directory /github/workspace
    - uses: ButterCam/setup-github-cli@master
Enter fullscreen mode Exit fullscreen mode

Finally, release the APK and upload it to the repository's releases page.

    - name: Release using github cli
      run: gh release create ${GITHUB_REF##*/} ./app/build/outputs/apk/release/**.apk
      env:
       GITHUB_TOKEN: ${{ secrets.TOKEN }}
Enter fullscreen mode Exit fullscreen mode

You'll need to generate a token with repo access for this to work. For some reason, using the default repository's GITHUB_TOKEN won't work.

And here's the full YAML in case you want to copy-paste it. You can also check it in my repository.

name: Release APK

on:
  push:
    tags:
      - '*'
jobs:
  Release:
    runs-on: ubuntu-latest
    steps:
    - name: checkout code
      uses: actions/checkout@v3
    - name: setup jdk
      uses: actions/setup-java@v3
      with:
        java-version: 17
        distribution: 'temurin'
    - name: Load keystore
      run: |
        echo "${{ secrets.KEYSTORE_FILE_CONTENTS }}" | base64 -d > keystore.jks && \
        cat <<EOL > local.properties
        storeFilePath=$PWD/keystore.jks
        storePassword=${{ secrets.RELEASE_STORE_PASSWORD }}
        keyAlias=${{ secrets.RELEASE_KEY_ALIAS }}
        keyPassword=${{ secrets.RELEASE_KEY_PASSWORD }}
        EOL
    - name: Build Release APK
      run: ./gradlew assembleRelease
      env:
        GRADLE_USER_HOME: ./gradle-config
    - name: Set current directory as a safe repository
      run: git config --global --add safe.directory /github/workspace
    - uses: ButterCam/setup-github-cli@master
    - name: Release using github cli
      run: gh release create ${GITHUB_REF##*/} ./app/build/outputs/apk/release/**.apk
      env:
       GITHUB_TOKEN: ${{ secrets.TOKEN }}
Enter fullscreen mode Exit fullscreen mode

There's still one thing that needs configuration to sign the APK properly. You'll need to edit your app/build.gradle.kts file so we can feed the properties from GitHub secrets to the assmbleRelease task. Kudos to Willi Metzel's Stack Overflow answer. Inside android in app/build.gradle.kts add:

    signingConfigs {
        create("release") {
            val properties = Properties().apply {
                load(File("local.properties").reader())
            }
            storeFile = File(properties.getProperty("storeFilePath"))
            storePassword = properties.getProperty("storePassword")
            keyPassword = properties.getProperty("keyPassword")
            keyAlias = properties.getProperty("keyAlias")
        }
    }

Enter fullscreen mode Exit fullscreen mode

Then, add signingConfig = signingConfigs.getByName("release") to release inside buildTypes. It should look something like:

   buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            signingConfig = signingConfigs.getByName("release")
        }
    }
Enter fullscreen mode Exit fullscreen mode

See here for a complete reference to my app/build.gradle.kts.

Credits to r0adkll for his previous work; without it, building this would have been more difficult.

You can follow me at GitHub (@ivanvc). And check the repository (and source code for my grayscale switching app) if you're interested.

GitHub Repo

Top comments (2)

Collapse
 
androaddict profile image
androaddict

Auto build means , automatically sent to internal testing to Google play console?

Collapse
 
ivanvc profile image
Iván Valdés

No, currently, it only uploads the generated APK to GitHub releases. I plan on doing that later.