How to build React Native Android app with GitHub Actions

In this post, I’m going to automate the build process (see my previous post) for a React Native Android app with GitHub Actions. This obviously ties into the the DevOps tidal wave but in a way that’s very developer friendly. Developers spend significant time in GitHub and have a great developer experience (DX). Instead of popping out to external systems, you can build, test and deploy you app within GitHub and also leverage the extensive, open GitHub Marketplace to reuse workflows and integration into other systems. I also love the fact that all of your workflow is source controlled in the same repo with you code.

Future posts will build on this workflow approach to automated the creation of SBOMs, automatically security and privacy test mobile apps and more.

Note: between the time I originally wrote this blog and then published it, the Joplin repo added a GitHub Action to build the Android app. So I’ve changed the name of the workflow file below. Definitely check out Joplin’s build-android.yml file as well.

1. Code setup

So folks can follow along, I’ll continue using Joplin, an open source note-taking app. You should first fork the Joplin app into your open account (makes a copy) and then clone the repo to your computer (replace ahoog42 with your GitHub username):

$ mkdir -p ~/spfexpert ; cd ~/spfexpert
$ git clone git@github.com:ahoog42/joplin.git
$ cd joplin

2. Configure GitHub Action build workflow

To configure a new GitHub Action, you create a .yml file under .github\workflows. GitHub has great documentation on the workflow syntax. You can name the yml file whatever you like so let’s just create a file called .github\workflows\build-android.yml. Key configuration includes:

  • name - what to call the action
  • on - when to do it (workflow_dispatch is really handy for testing)
  • jobs - these are the specific steps your workflow needs

Create build-android.yml

Below is an working workflow yml file for building the Joplin Android app (see previous blog for steps to build Joplin Android locally) which you could easily adapt for your build process:

$ vim .github/workflows/build-android-spfexpert.yml
name: "Build Android app"

on:
  workflow_dispatch:
    branches: [dev]
    # can add push and pull_request here 

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Install Java
        uses: actions/setup-java@v2
        with:
          java-version: "11"
          distribution: "adopt"
          cache: "gradle"

      - name: Validate Gradle wrapper
        uses: gradle/wrapper-validation-action@v1

      - name: Setup Node  
        uses: actions/setup-node@v3
        with:
          node-version: '16'

      - name: Run Yarn Install
        run: |
             cd ./packages/app-mobile/
             npm i -g corepack
             yarn install             
      - name: Build application
        run: |
             cd ./packages/app-mobile/android
             ./gradlew assembleDebug             
      - name: Upload application
        uses: actions/upload-artifact@v2
        with:
          name: app
          path: ./packages/app-mobile/android/app/build/outputs/apk/debug/app-debug.apk
          retention-days: 3

Commit changes

Next we’ll want to commit these changes to the forked repo (skip this step if you’re using my forked version vs. your own):

$ git add .github/workflows/build-android-spfexpert.yml
$ git commit .github/workflows/build-android-spfexpert.yml -m 'GH Action to build Android app'

Remove unneeded workflows

Next, let’s removed a few GitHub Action workflows that the main Joplin app repo uses so we do’t kick off unneeded workflows:

$ git rm .github/workflows/github-actions-main.yml
$ git rm .github/workflows/close-stale-issues.yml
$ git rm .github/workflows/build-android.yml
$ git commit -a -m 'remove existing Joplin workflows'

Push committed code

And finally let’s push these changes to the repo:

$ git push

3. Run build workflow

Ok, we’re now ready to kick off the build, using the GitHub UI. You can trigger the workflow based on many different types of GitHub events (e.g. pull requests, commits to a branch, etc.) but for a tutorial and initial testing, manually kicking off with a workflow_dispatch event is perfect.

To run the workflow, navigate to the GitHub repo (for me, it would be at https://github.com/ahoog42/joplin/ however you will just swap in your GitHub username for ahoog42:)

GitHub Repo UI -> Actions -> Build Android app -> Run Workflow Dropdown -> Run Workflow

4. Accessing the build

GitHub provides a number of ways to access workflow artifacts. In the workflow configuration, we used the actions/upload-artifact@v2 action and stored the apk as an artifact named app and set a retention of 3 days. If you are accessing artifacts during the same workflow, you can use the actions/download-artifacts action. After the workflow, you can access artifacts via the GitHub UI or their Artifacts REST API.

Download artifact via GitHub Artifacts REST API

For automation and integrations, you’ll want to access artifacts via the REST API. Here’s a quick step-by-step:

1. GitHub Personal Access Token

You need to generate a GitHub Personal Access Token and I would suggest a fine-grained GitHub access token since this is a security-related blog after all! I chose the following:

  • Repository access -> Only select repositories (and then chose my Joplin fork)
  • Repository permissions -> Actions (read-only)

The Actions read-only added the Metadata permission for me automatically. Pay attention to the expiration date and securely store the token if you save it locally.

Then you simply export the GitHub personal access token in your shell:

$ export GH_TOKEN=github_pat_11AAPO<snip>

And then you can easily list artifacts in your repo:

curl -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" https://api.github.com/repos/ahoog42/joplin/actions/artifacts

and you’ll get a response like:

{
  "total_count": 5,
  "artifacts": [
    {
      "id": 410082801,
      "node_id": "MDg6QXJ0aWZhY3Q0MTAwODI4MDE=",
      "name": "app",
      "size_in_bytes": 56994698,
      "url": "https://api.github.com/repos/ahoog42/joplin/actions/artifacts/410082801",
      "archive_download_url": "https://api.github.com/repos/ahoog42/joplin/actions/artifacts/410082801/zip",
      "expired": false,
      "created_at": "2022-10-24T20:56:13Z",
      "updated_at": "2022-10-24T20:56:14Z",
      "expires_at": "2022-10-27T20:56:06Z",
      "workflow_run": {
        "id": 3316021778,
        "repository_id": 553849935,
        "head_repository_id": 553849935,
        "head_branch": "dev",
        "head_sha": "8550cdb12d93cee85724f9dd0373a14ec747b680"
      }
    },
    <snip>

You can see there is an array of artifacts (5 in this example, most recent at the top or artifacts[0]) which you easily then download using the archive_download_url:

$ curl -LO -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" https://api.github.com/repos/ahoog42/joplin/actions/artifacts/410082801/zip

When a workflow uploads an artifact, it uses compression (zip) to reduce data storage and group multiple files together. As you may know, apk files are actually zip files so after you download the apk artifact, you’ll actually have a double zipped file (by default, the downloaded file is actually named zip):

$ file zip

which will return:

zip: Zip archive data, at least v2.0 to extract, compression method=deflate

$ unzip -l zip
Archive:  zip
  Length      Date    Time    Name
---------  ---------- -----   ----
 56994698  10-24-2022 20:56   app-release.apk
---------                     -------
 56994698                     1 file

You can verify the file type with the file command and then list off the file contents with unzip -l zip. To extract the artifact, you can simply omit the -l:

$ unzip zip
Archive:  zip
  inflating: app-debug.apk

And you’ll now have the apk artifact available locally.