okaryo.log

Automatically Deploying Android Apps Built with Flutter to Google Play Using GitHub Actions | okaryo.log

Automatically Deploying Android Apps Built with Flutter to Google Play Using GitHub Actions

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

Introduction

Recently, I released an Android app called subskun.

For the initial release, I built the app locally and manually uploaded it to the store. However, doing this manually every time became tedious, so I decided to automate the process.

I set up a workflow using GitHub Actions.

Workflow

Here’s the overall workflow I created. It is triggered by a push to the main branch, which is used for releases.

name: Deploy to Google Play

on:
  push:
    branches:
      - main

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: Create service_account.json
        run: echo '${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}' > service_account.json
      - name: Upload artifact to Google Play
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJson: service_account.json
          packageName: ${{ secrets.PACKAGE_NAME }}
          releaseFiles: build/app/outputs/bundle/productionRelease/app-production-release.aab
          track: production
          status: completed

Step-by-Step Explanation

The workflow up to the Build aab step is the same as the one I introduced in a previous article, so if you’re already familiar with that, feel free to skip to the Create service_account.json step.

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

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.

I 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

Create service_account.json

To upload to the store via API, you need a service account, and this step creates the necessary file.

echo '${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}' > service_account.json

I referred to the following article for instructions on creating the service account:

How to Create a Service Account for Google Play Store

Upload artifact to Google Play

Now that everything is ready, I finally upload the build to Google Play.

I used the r0adkll/upload-google-play@v1 action for this.

- name: Upload artifact to Google Play
  uses: r0adkll/upload-google-play@v1
  with:
    serviceAccountJson: service_account.json
    packageName: ${{ secrets.PACKAGE_NAME }}
    releaseFiles: build/app/outputs/bundle/productionRelease/app-production-release.aab
    track: production
    status: completed

You can also set the update priority, user rollout percentage, release notes, and more when uploading the app. For more information, refer to the README.

Conclusion

With this setup, I was able to automate the deployment to the Play Store triggered by a merge to the main branch.

A word of caution from my own experience: while debugging the workflow, it succeeded and unintentionally submitted my app for review on the Play Store. Since there was an actual update and I was planning to submit it soon anyway, I left it for review. However, if an app is accidentally submitted for review, you cannot cancel it, which was a bit problematic. So, be careful with your workflow.


Related Posts
Related Posts
Promotion

This site uses Google Analytics.