CI/CD for an Android Jetpack Compose App with GitHub Actions
Build with Gradle, run unit and Compose UI tests on an emulator, and assemble a release APK.
This recipe builds an Android Jetpack Compose app. CI runs unit tests with Gradle, runs Compose instrumented UI tests on an emulator via reactivecircus/android-emulator-runner, then assembles a release APK.
What the pipeline does
- set up the JDK and Gradle cache
- run unit tests with ./gradlew test
- boot an emulator and run connected Compose UI tests
- assemble the release APK
- upload the APK artifact
The workflow
android-emulator-runner boots an AVD with KVM acceleration and runs connectedCheck for the Compose UI tests. assembleRelease produces the signed APK.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: gradle/actions/setup-gradle@v4
- run: ./gradlew test
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
script: ./gradlew connectedCheck
- run: ./gradlew assembleRelease
- uses: actions/upload-artifact@v4
with:
name: app-release
path: app/build/outputs/apk/release/*.apkCaching and speed
gradle/actions/setup-gradle caches the Gradle dependency and build cache. The emulator boot plus Compose instrumented tests are the slowest stage; cheaper managed runners such as Latchkey (around 69% cheaper than GitHub-hosted) with nested virtualization keep emulator runs fast and auto-retry a flaky AVD boot.
Deploying
Sign the release with a keystore stored in secrets (decode it in a step) and the signing config in build.gradle. Upload the signed APK or AAB as an artifact, or add a distribution step to push it to a Play Store internal track.
Key takeaways
- Run Compose UI tests on an emulator via android-emulator-runner.
- Cache the Gradle dependency and build cache to speed runs.
- Decode the signing keystore from a secret for the release build.