Automatically Deploying Android Apps Built with Flutter to Firebase App Distribution Using GitHub Actions
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:
https://firebase.google.com/docs/cli
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.