okaryo.log

Automatically Deploying Android Apps Built with Flutter to Firebase App Distribution Using GitHub Actions | okaryo.log

Automatically Deploying Android Apps Built with Flutter to Firebase App Distribution Using GitHub Actions

    #Flutter#Firebase#Android#GitHubActions#GitHub#CI/CD

Introduction

Recently, I released an Android app called subskun.

I use App Distribution to debug the app on a real device when adding features or making fixes, but I wanted to automate the delivery to App Distribution whenever there was a merge to the develop branch.

So, I decided to set up a workflow using GitHub Actions.

Workflow

Here’s the overall workflow I created. The workflow is triggered by a push to the develop branch, which is used for development.

name: Deploy to App Distribution

on:
  push:
    branches:
      - develop

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.0.1'
          channel: 'stable'
          cache: true
      - name: Run gen-l10n
        run: flutter gen-l10n
      - name: Build aab
        run: |
          mkdir android/app/src/productionRelease
          echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > android/app/src/productionRelease/google-services.json
          echo '${{ secrets.ANDROID_JKS_BASE64 }}' | base64 -d > android/app/release.keystore
          export KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
          export KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
          export KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'
          flutter build appbundle --release --flavor production --dart-define=FLAVOR=production --build-number='${{ secrets.ANDROID_BUILD_NUMBER }}' --obfuscate --split-debug-info=obfuscate/android
      - name: Upload artifact to Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          appId: ${{secrets.FIREBASE_APP_ID}}
          token: ${{secrets.FIREBASE_TOKEN}}
          groups: testers
          file: build/app/outputs/bundle/productionRelease/app-production-release.aab

Step-by-Step Explanation

actions/checkout@v3

This step checks out the repository, so there’s not much to explain.

Set up Flutter

This sets up the Flutter environment. The subosito/flutter-action action is commonly used for this purpose.

For reference, subskun uses Flutter 3.

Run gen-l10n

Since subskun supports multiple languages, I need to run the following command to generate localization code before building:

flutter gen-l10n

Build aab

Now it’s time to start the build, but first, I need to do some preparation.

Since subskun uses Firebase, I generate configuration files from GitHub Actions secrets:

mkdir android/app/src/productionRelease
echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > android/app/src/productionRelease/google-services.json

The following code prepares the passwords and other details required to sign the Android app. Since the jks file is binary, I store its base64-encoded version in GitHub Actions secrets and decode it here:

echo '${{ secrets.ANDROID_JKS_BASE64 }}' | base64 -d > android/app/release.keystore
export KEY_ALIAS='${{ secrets.KEY_ALIAS }}'
export KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}'
export KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}'

As you can see above, passwords and other information are read from environment variables, so the signingConfigs section of android/app/build.gradle looks like this:

signingConfigs {
    release {
        keyAlias System.getenv('KEY_ALIAS')
        keyPassword System.getenv('KEY_PASSWORD')
        storeFile file('release.keystore')
        storePassword System.getenv('KEYSTORE_PASSWORD')
    }
}

Finally, the build process begins.

Since I want to test the app in the same environment it will be released in, I use a release build. I also use dart-define to handle environment settings and --obfuscate to obfuscate the code.

I’m currently providing the build number from secrets, but I’m considering automatically setting the build number based on the app’s version number. Personally, I prefer to provide the build number externally for both Android and iOS, but I might switch to a more convenient method if I find one.

flutter build appbundle --release --flavor production --dart-define=FLAVOR=production --build-number='${{ secrets.ANDROID_BUILD_NUMBER }}' --obfuscate --split-debug-info=obfuscate/android

Note (2022-10-05 update)

I’ve been informed of a way to set the build number based on the number of commits. If you’re interested, check out the following article:

Setting the Build Number Based on Commit Count When Building a Flutter App on GitHub Actions

End of note

Upload artifact to Firebase App Distribution

Finally, the built aab file is uploaded to App Distribution.

The following path contains the generated aab file:

build/app/outputs/bundle/productionRelease/app-production-release.aab

For this, I used the wzieba/Firebase-Distribution-Github-Action@v1 action.

It’s straightforward to upload the file by providing the Firebase appId and token (or serviceCredentialsFile).

Keep in mind that the appId is not the Firebase project ID, but the ID of the Android app within the Firebase project. You can obtain the token by running the firebase login:ci command.

To use the firebase command, you’ll need to install the Firebase CLI. For more information, refer to the official documentation:

Conclusion

Uploading to App Distribution was made easy by using an existing action.

In individual development, I believe that reducing “tedious tasks” is crucial to maintaining motivation. That’s why I’m glad I set up a CI/CD pipeline early on.


Related Posts
Related Posts
Promotion

This site uses Google Analytics.