This blog post will cover a way to publish APK files with GitHub Actions for Flutter mobile app with Firebase backend.

Workflow example.

GitHub Repository Secrets

Flutter/Firebase apps have 3 files that hold sensitive information:

  • services.json - Google Cloud credentials in JSON format, this is used by Firebase
  • keystore.jks - Key used to sign the app
  • key.properties - Key properties, such as key password, alias and it’s location

All of the above will be encoded (not encrypted!) in BASE64 and save in GitHub Actions secrets. It must be encrypted for consistency, for example services.json contains a log of tabs and whitespaces that might be incorrectly stored in secrets.

To encode in BASE64:

cat <filename> | base64 -w 0

-w 0 parameter is being used to make sure the BASE64 encoded string is 1 string, and not multiple strings separated by newline.

After that copy the output and save it at GitHub Project -> Settings -> Secrets and Variables -> Actions -> New Repository Secret

Workflow

Now, let’s create a workflow step by step.

First, it will be fun every time a tag is created.

name: πŸ€– πŸ“¦ Build and release packages for Android
on:
  push:
    tags:        
      - '*'

Import actions. In this workflow 4 actions are used:

  • checkout - to pull and checkout the code
  • setup-java - java is needed to build Flutter apps. It should be the same version as used in local development.
  • flutter-action - and, of course, Flutter actions will be needed.
  • upload-artifact - used to upload created apk file to artifacts sections of the workflow. Use in the end of the workflow.
jobs:
  build-n-release:
    name: πŸ€– πŸ“¦ Build and release
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v2
        with:
          distribution: 'zulu'
          java-version: '11'
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.10.6'

Decode services.json and other sensitive information, and write it to files in the build environment:

      - name: πŸ”‘ Create Google Services JSON File
        env:
          GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
        run: echo $GOOGLE_SERVICES_JSON | base64 -di > ./android/app/google-services.json

      - name: πŸ”‘ Create the Keystore
        env:
          KEYSTORE_BASE64: ${{ secrets.KEYSTORE }}
        run: echo $KEYSTORE_BASE64 | base64 -di > android/upload-keystore.jks 

      - name: πŸ”‘ Create Key Properties
        env:
          KEY_PROPERTIES_BASE64: ${{ secrets.KEY_PROPERTIES }}
        run: echo $KEY_PROPERTIES_BASE64 | base64 -di > android/key.properties 

Update the dependency list and build APK file and an App Bundle:

      - name: πŸͺΊ Update Flutter dependency list
        run: flutter pub get

      - name: βš™οΈ Build apk
        run: flutter build apk

      - name: βš™οΈ Build bundle (aab)
        run: flutter build appbundle

Finally, upload APK and App Bundle to artifacts:

      - name: πŸ“¦ Save APK as artifact
        uses: actions/upload-artifact@v1
        with:
          name: "gentoo_update-${{  github.ref_name }}.apk"
          path: build/app/outputs/apk/release/app-release.apk

      - name: πŸ“¦ Save Bundle as artifact
        uses: actions/upload-artifact@v1
        with:
          name: "gentoo_update-${{  github.ref_name }}.aab"
          path: build/app/outputs/bundle/release/app-release.aab

Both APK and App Bundle will be uploaded to Actions -> <Job Name> -> Articats (bottom of the screen).

From here, I like to test the app on a device or emulator first, before adding it to release.

  • [Link] - Blog post on building and signing Flutter apps with Github Actions
  • [Link] - Github Secrets