A brief guide to implementing CI in Android projects using GitHub Actions and SonarCloud

Arthur Henz
Poatek
Published in
7 min readMay 20, 2024

--

In this article, I’ll cover a step-by-step guide to implementing multiple CI tools such as Lint, unit test, instrumentation test, and static code analysis using GitHub Actions and SonarCloud. Also, I’ll show you how to generate the fragment of an application to download.

For beginners, I encourage you to learn the difference between CI and CD and the main purpose of GitHub Actions and SonarCloud.

Cool! Can you give me a list of the things you will be implementing?

For sure! Here are the jobs I’ll run on my CI, divided into subtopics:

  • Lint
  • Unit Test
  • Instrumentation Test
  • Static Code Analysis (SonarCloud)
  • Generate executable file

To make it work on GitHub, you must have a .github folder on the root of your project. Inside it, create a folder named “workflows.” Then, create a file for your specific goal.

A root folder named “android example project” that contains a folder named “dot github” that contains a folder named “workflows”. Inside of it, there are four yml files named “automatic deploy”, “dev on pull request”, “main on push” and “manual deploy”. All these files are in snake case.

In this example, we’ll work with main_on_push.yml.

Lint

Lint is a type of tool that analyzes the code for common programming mistakes, such as dependency versions, style violations, and other problematic patterns that can lead to bugs or decrease code quality.

WARNING! The versions may be incompatible depending on the time you are reading this. Bump your Actions/JDK to the latest versions.

Below an example of a workflow with Lint, actioned when pushed to main:

name: Push to main  # optional name

on:
push:
branches: [ main ] # workflow runs when push to main

jobs:
lint: # arbitrary name to a job
runs-on: ubuntu-latest # it runs on a remote virtual machine
steps:
- name: Checkout the code
uses: actions/checkout@v

- name: Lint
run: ./gradlew lintDebug

- name: Upload txt test report
uses: actions/upload-artifact@v2
with:
name: index.txt
path: app/build/intermediates/lint_intermediate_text_report/debug/lint-results-debug.txt

On Lint job, we programmed three steps.

  1. actions/checkout: the most common job on GitHub Actions. It is responsible for granting GH Action’s virtual machine access to the actual Android code you wrote.
  2. ./gradlew lintDebug: gradle’s Lint that generates a Lint report to the virtual machine.
  3. actions/upload-artifact: uploads a file from GH Action’s virtual machine in the form of a fragment. In this case, it makes the output of lintDebug available for download. Below an example of warnings and the input as an Artifact that can be downloaded:

As you can see, we got a message related to deprecation. A way to fix it would be bumping the checkout version to the most current one.

Unit test

A unit test focuses on verifying the correctness of small, isolated units of code, typically at the function or method level, to ensure they perform as expected.

Below an example of unit testing:

unit-test:  # arbitrary name to a job
needs: [ lint ] # will only run after lint is done (optional)
runs-on: ubuntu-latest # it runs on a remote virtual machine
steps:
- name: Checkout the code
uses: actions/checkout@v4

- name: Run tests
run: ./gradlew test

- name: Upload test report
uses: actions/upload-artifact@v2
with:
name: unit_tst_report
path: app/build/reports/tests/testDebugUnitTest/

For unit tests, we implemented three other steps:

  1. actions/checkout: the most common job on GitHub Actions. It is responsible for granting GH Action’s virtual machine access to the actual Android code you wrote.
  2. ./gradlew test: Gradle’s testing routine generates an output to the virtual machine.
  3. actions/upload-artifact: uploads a file from GH Action’s virtual machine as a fragment. In this case, it makes the output of ./gradlew test available for download.

Instrumentation test

Unlike unit tests, instrumentation test is a type of software that focuses on verifying an application's functionality, performance, and reliability by simulating user interactions and measuring its responses using automated tools and frameworks.

In this case, we set the virtual machine to run an emulator and perform some tests using Espresso.

instrumentation-test:
needs: [ unit-test ] # will only run after lint is done (optional)
runs-on: macos-latest # macos has a better performance for emulating OS
steps:
- name: Checkout the code
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '18.0.2+101'
check-latest: true

- name: run espresso tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
target: default
arch: x86
profile: Nexus 6
script: ./gradlew connectedCheck

- name: Upload test report
uses: actions/upload-artifact@v2
with:
name: instrumentation_test_report
path: app/build/reports/androidTests/connected/

This time, we’ll run four steps:

  1. actions/checkout: the most common job on GitHub Actions. It is responsible for granting GH Action’s virtual machine access to the actual Android code you wrote.
  2. actions/setup-java: you can choose which Java distribution and version to use. I recommend you know the exact Java version you need to avoid crashes on the pipeline.
  3. reativecircus/android-emulator-runner: This action performs some interesting things. It emulates an Android device with possible specifications such as API level, architecture, and device model. Then, you can run certain Gradle scripts aimed at a virtual emulator. In this case, we’ll run ./gradlew connectetCheck to run tests located in src/androidTests/. I highly encourage you to create Espresso tests for UI, connection, and Intent tests for Android.
  4. actions/upload-artifact: upload a file from GH Action’s virtual machine in the form of a fragment. In this case, the output of ./gradlew connectedCheck is available for download.

Static Code Analysis

The name itself is already suggestive. static code analysis is a way to examine the code without compiling it. For this big boy, we will use SonarCloud. This software utilizes an abrangent lineup of mechanisms to analyze our code.

Fun fact: this software is so popular in the industry that even FAANGs such as AWS utilize it, and they make it public to be as transparent as possible (also with some intention of anyone non-AWS to be available to help on debugging and code improvement). Here, you can see what types of analysis are being made:

AWS Java SDK: 206k lines of code of Java and XML. Bugs, Vulnerabilities, Code Smells, Security Hotspots, Coverage and Duplications

Right. Now, I’ll give you the steps to sync SonarCloud to your project:

  • Sign up at SonarCloud with GitHub.
  • Connect a specific repository to your SonarCloud account.
  • After setting up, choose an Analysis Method. For GitHub Actions, it should generate a SONAR_TOKEN. Set it as a Secret on your GitHub project.
  • Also, create a GITHUB_TOKEN with any ultra super safe Secret. We’ll use it later.

Ok. You did everything necessary on GitHub and SonarCloud's side. Now, let’s dig into this code:

static-code-analysis:
runs-on: ubuntu-latest

steps:
# as you can see, the name is optional
- uses: actions/checkout@v4 # still, it is a good practice to use it.

- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '18.0.2+101'
check-latest: true

- name: SonarCloud Scan
run: ./gradlew app:sonarqube -Dsonar.login=${{ secrets.SONAR_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # secrets you set on GH
  1. actions/checkout: the most common job on GitHub Actions. It is responsible for granting GH Action’s virtual machine access to the actual Android code you wrote.
  2. actions/setup-java: you can choose which Java distribution and version to use. I recommend you know the exact Java version you need to avoid crashes on the pipeline.
  3. ./gradlew app:sonarqube: a script to remotely authenticate and authorize the code to be red by SonarCloud.

Then, when running this task, your project should be something like this on the SolarCloud platform:

Checking each session gives you powerful information to make your code just better.

Generate executable fragment

Right! You did all these things to measure and ensure your code is palatable. Now, let’s generate an APK.

Note: to deliver to the Play Store, the .apk should be assigned, generating an AAB file. As this isn’t our purpose here, we’ll just generate the APK. Also, I encorage you to know the difference between .aab and .apk.

  build-fragment:
needs: [ static-code-analysis ] # optional step
name: Generate APK
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '18.0.2+101'
check-latest: true

- name: Build debug APK
run: ./gradlew assembleDebug --stacktrace

- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: android-hilt.apk
path: app/build/outputs/apk/debug/app-debug.apk
  1. actions/checkout: the most common job on GitHub Actions. It is responsible for granting GH Action’s virtual machine access to the actual Android code you wrote.
  2. actions/setup-java: you can choose which Java distribution and version to use. I recommend you know the exact Java version you need to avoid crashes on the pipeline.
  3. .gradlew assembleDebug — — stacktrace: this step runs the Gradle command assembleDebug to build the Android application in debug mode. The --stacktrace flag provides a detailed stack trace in case of any build errors.
  4. actions/upload-artifact: uploads a file from GH Action’s virtual machine as a fragment. In this case, it makes app-debug.apk available for download on Github Actions.

Done! I hope you liked the steps I showed you. GH Action’s virtual machine is fully customizable, but I introduced just the tip of the iceberg for study purposes for your beloved project. If anything, just call me!

And if you want to implement CD with Firebase App Distributions, please feel free to read my other article, A guide to implement CD in Android projects using GitHub Actions and Firebase App Distribution.

--

--

Arthur Henz
Poatek
Editor for

An Android developer who loves connections and self improvement :)