Build, Test and Deploy your Android Application📱  with GitHub Actions 🤖
Tags: actionshackathon21, android, kotlin, github
Published: 2021-11-26 08:10:39

### My Workflow
As an Android developer, I would like to put under control the developments of an [oss sandbox application] (https://github.com/boitakub/Bogadex) 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.

[Note]: # (Please make sure the project links to the appropriate GitHub Actions repository, and includes [an open source license](https://choosealicense.com/) and README.)


### Submission Category: Phone Friendly


### Yaml File / Link to Code
In the project you can find different workflow files :
* [CI Workflow](https://github.com/boitakub/Bogadex/blob/main/.github/workflows/main.yml) - Make build, test and quality check
* [Release Workflow](https://github.com/boitakub/Bogadex/blob/main/.github/workflows/main.yml) - Deal with release control and deployment
* [Super-linter Workflow](https://github.com/boitakub/Bogadex/blob/main/.github/workflows/lint.yml) - Check all files with common lint rules

Here is the main **Build workflow** 👉

```
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

![CI workflow chaining](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aadgrv441wy5vfs8drd.jpg)

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](https://github.com/marketplace/actions/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](https://app.codacy.com/gh/boitakub/Bogadex/dashboard)

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](https://github.com/boitakub/Bogadex/blob/main/.github/workflows/main.yml) which do the same job but in an other part of the delivery workflow, when a new [version-tag](https://semver.org/lang/fr/) is added. You can notice that the workflow deploy the final apk to firebase through [firebase-app-distribution](https://github.com/marketplace/actions/firebase-app-distribution) task.

Finally the *.apk file is available to Firebase App Distribution
![App distribution ready to serve](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m78bexnl28etp5e47ll8.jpg)

### Additional Resources / Info

Do not hesitate to explore the repository {% github boitakub/bogadex%}

And some of the [Github Actions](https://github.com/marketplace?type=actions) it used :
* [Workflow folder](https://github.com/boitakub/Bogadex/blob/main/.github/workflows) - All Github Actions workflows
* [Android emulator for instrumented - GHAction](https://github.com/marketplace/actions/android-emulator-runner)
* [Super-linter - GHAction](https://github.com/marketplace/actions/super-linter)
* [Firebase App Distribution - GHAction](https://github.com/marketplace/actions/firebase-app-distribution)