Lifecycle Automation of a Flutter Project With GitHub Actions
By: Ugurcan Yildirim
Tags: Programming, Github Actions, Continuous Deployment, Automation, Flutter

In this article, I’m going to show you how I use GitHub Actions to automate the building and distribution of a Flutter app.

First of all, I want to explain what GitHub Actions is. It’s basically a CI/CD tool from GitHub. It allows us to easily build, test, and deploy our code in GitHub. Our workflow utilizes reusable units called actions, which are developed by GitHub itself or a third party to perform one specific task in a workflow. So our workflow consists of a set of these actions.

Let’s jump into what we need to do to automate the lifecycle of our Flutter project. GitHub Actions requires a YAML file to specify everything related to a workflow. So first, we create a xxx.yml file in the .github/workflows directory under our project. Here, xxx can be anything we want.

The first thing we’ll specify in this file is the name of our workflow:

name: app workflow

Afterward, we need to specify in which cases this workflow should be triggered. It can be done as follows:

on:

push:

branches:

- develop

- master

pull_request:

branches:

- develop

In this example, we want our workflow to be triggered when there’s a code push made to the develop or master branch and also when a pull request is made to the develop branch.

Now it’s time to define our job. A job basically specifies a list of action steps and where these actions run. A workflow might contain multiple jobs, but for our simple case, we only have one job:

jobs:

build_and_deploy:

runs-on: ubuntu-latest steps:

...

For our job called build_and_deploy , we first specify on which machine this job should run. Here we use a Ubuntu machine with the latest stable version.

Let’s now list the actions required for a complete CI/CD flow under the steps of our job. First, we need to prepare the environment on the device:

steps:

- uses: actions/checkout@v2

- uses: actions/setup-java@v1

with:

java-version: 1.8 - name: Install NDK

run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT} - name: Set up Flutter environment

uses: subosito/flutter-action@v1.3.2

with:

channel: stable ...

I want to go step by step here:

Check out our repository, and make it ready to use in the machine by upcoming actions. Set up the Java environment with Java version 8. As I’ve encountered errors related to the required Android NDK version not being found, I’ve added the step called “Install NDK,” which installs the required version specified in the error logs. You might not need this step at all. So try it yourself, and if that’s the case, you can simply remove it. Set up the Flutter environment with the latest stable version. This step uses an action developed by a third party. Read more here for details.

If we recheck the initial steps given above, we set name only for some of them. If a step/action isn’t clear enough for us, we can provide a name to clearly explain what the purpose of that step is.

The environment setup is now completed. With step 4, we have the flutter tool installed on our device. Now, we can build the APK using Flutter commands:

- run: flutter pub get

- run: flutter test

- run: flutter build apk --release --build-number=${GITHUB_RUN_NUMBER}

env:

KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}

...

Here, we first install the package dependencies. Then, we run the tests (if any). And finally, we build a release APK with build number set to GITHBU_RUN_NUMBER , which is an environment variable provided by GitHub. GitHub defines it as: “A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow’s first run, and increments with each new run. This number does not change if you re-run the workflow run.”

Moreover, we need to provide two more environment variables, KEY_PASSWORD and STORE_PASSWORD , to be used while signing the APK. To be more specific, they’re used in the app/build.gradle file on the Android side as follows:

...

signingConfigs {

release {

storeFile file("../keystore.jks")

storePassword = "$System.env.STORE_PASSWORD"

keyAlias = "myapp"

keyPassword = "$System.env.KEY_PASSWORD"

}

} buildTypes {

release {

signingConfig signingConfigs.release

}

}

...

Both of these variables should be added as secrets under our GitHub repository’s “Settings/Secrets” page. These secrets (environment variables) are automatically passed to our workflow while running.

After these steps, we should have a release APK ready to be deployed with the following file path: build/app/outputs/apk/release/app-release.apk . We’ll deploy this file to Firebase, and using its App Distribution service, we’ll distribute it to our testers. To do this, we have one last step remaining:

- name: Upload artifact to Firebase App Distribution

uses: wzieba/Firebase-Distribution-Github-Action@v1.2.2

with:

appId: ${{ secrets.FIREBASE_APP_ID }}

token: ${{ secrets.FIREBASE_TOKEN }}

groups: testers

file: build/app/outputs/apk/release/app-release.apk

Here, we again use a third-party action, this time for Firebase App Distribution. Action details can be found here. Basically, to make this action run successfully, we need to provide a couple of inputs:

appId can be found under the “Your apps” section on the project settings page of the Firebase dashboard. token can be generated on the local machine by using the following command: firebase login:ci . However, first we need to install the Firebase CLI on our machine. See here. groups is the name(s) of the tester group(s) we created on Firebase App Distribution and want to distribute our app to. file is the path for our APK to be distributed.

Similar to what we saw above while building the app, FIREBASE_APP_ID and FIREBASE_TOKEN also need to be set as repository secrets, as they’re used by this action. So our Secrets page looks like this at the end:

Secrets page of our repository on GitHub

To keep the article short, I didn’t go into the details of Firebase App Distribution, but it’s basically another service of Firebase that allows us to distribute prerelease versions of our app easily to tester groups, notifying them via email. If you want to know more about it, read more here.