Build, Test and Deploy your Android Application📱 with GitHub Actions 🤖
Julien Foratier
Posted on November 26, 2021
My Workflow
As an Android developer, I would like to put under control the developments of an oss sandbox application gathering some practices in a functional application.
In a craftmanship approach I like to work with practices such as industrialization, continuous quality control, functional orientation and fast delivery.
You will find in this repo a some practices and patterns on workflow files that I will detail below.
Submission Category: Phone Friendly
Yaml File / Link to Code
In the project you can find different workflow files :
name: Build
on:
push:
branches: [ main ] # Just in case main was not up to date while merging PR
pull_request:
types: [ opened, synchronize ]
jobs:
build-and-test:
name: Build, Lint and Test
runs-on: macos-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up our JDK environment
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Cache Gradle and wrapper
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
# Decode Google services configuration file from secrets
# - name: Decode google-services.json
# env:
# FIREBASE_CONFIG: ${{ secrets.FIREBASE_CONFIG }}
# run: echo $FIREBASE_CONFIG > app/google-services.json
# Run emulator
- name: Run integration test on emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew createDebugCoverageReport --stacktrace
# Generate jacoco report
- name: Generate report
run: ./gradlew jacocoTestReport
# Upload report
- name: Upload Reports
uses: actions/upload-artifact@v2
if: always()
with:
name: reports
path: |
/build/coverage-report
app/build/reports
# Upload coverage report to Codacy
- name: Run codacy-coverage-reporter
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
static-analysis:
name: Execute analyse on code
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up our JDK environment
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Cache Gradle and wrapper
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
# Check the code with detekt, you can remove this job if you don't use detekt
- name: Run detekt Linter
run: ./gradlew detekt
# Check the code with ktlint, you can remove this job if you don't use ktlint
- name: Run Kotlin Linter
run: ./gradlew ktlintCheck
# Check the code with Android linter (need assemble)
- name: Run Android Linter
run: ./gradlew lint
# Check the code with Spotless
- name: Run Spotless
run: ./gradlew spotlessCheck
generate-release-apk:
name: Try generate Releasable
runs-on: ubuntu-latest
environment: Release
timeout-minutes: 20
needs:
- build-and-test
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up our JDK environment
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Decode Keystore
env:
ENCODED_STRING: ${{ secrets.KEYSTORE }}
run: |
TMP_KEYSTORE_FILE_PATH="${RUNNER_TEMP}"/keystore
mkdir "${TMP_KEYSTORE_FILE_PATH}"
echo $ENCODED_STRING | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"/keystore_file.jks
- name: Cache Gradle and wrapper
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Release
run: ./gradlew app:assembleRelease
env:
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
In this file you can see 3 main sections :
build-and-test
static-analysis
generate-release-apk
The build-and-test part is in charge of building application (and check for build failure) and running tests, both unit and instrumented. For instrumented it used android-emulator-runner to prepare a device in order to run. At the end a Jacoco task is merging different coverage result and upload them to Codacy
In parallel of this task, static-analysis is running some of android popular linters : ktLint, detekt, Android lint, Spotless
The last task generate-release-apk is waiting for build-and-test to complete and succeed before running then it will try to build an releaseable apk without publish it.
The real publishing task is in the Release Workflow which do the same job but in an other part of the delivery workflow, when a new version-tag is added. You can notice that the workflow deploy the final apk to firebase through firebase-app-distribution task.
Finally the *.apk file is available to Firebase App Distribution
🎲 BoardGameGeek collections explorer application using Hilt, Coroutines, Flow, Jetpack (Room, ViewModel) based on MVVM architecture.
Bogadex
Bogadex is a small demo and functionnal application based on modern Android application tech-stacks and MVVM architecture. This project aim to regroup and present most of current practices and patterns.
Also dealing with data (from BoardGameGeek) and presenting them in elegants ways