Flutter CI/CD with Github Actions & Fastlane — Part 1(Android)

Publishing your Android & iOS build manually on store is often a tedious and time consuming task. On top of that it affect your productivity because you’re spending lots of hours doing same repetitive tasks. I’ve had lot of struggle implementing CI/CD on my flutter project with Github actions due to lack of good articles so I thought to have an article with complete steps to add CI/CD in your flutter project. I’ve been using Github actions and Fastlane for my native projects since long and now also doing same for my flutter projects.

This article will cover all the nitty gritty details to implement CI/CD into your flutter project with help of Github Actions & fastlane. This is going to be a series of two articles, current article will cover steps to perform CI/CD for your flutter Android apps and to build and deploy app on Playstore. You can check same setup on my app Expense-Manager on Github.

At the end of this two articles, you’ll be able to launch your flutter app on both Playstore & Appstore with Github actions.

This article is for you if you’re

  • Flutter developer, want to integrate CI/CD in your project.
  • Don’t have any previous knowledge on implementing CI/CD for mobile apps.
  • Don’t have any previous experience on Github Actions or Fastlane.

Prerequisites

  • git cli
  • fastlane cli
  • GnuPG cli (download here) `gpg` command should work in your terminal.

Note: I’m going to cover every details of Github Actions and Fastlane that we’re going to use in this setup. So it’d be better if you know a bit about github action’s details like jobs, workflow and few commands of Fastlane as well.

Let’s Do it.

We’ll do it for ANDROID platform first. I’ve divided this process into six steps.

  1. Fastlane setup for Android platform.
  2. Configuration of Project on Google cloud console and Playstore.
  3. Encryption of jks, properties & service_account_key.json file.
  4. Write decryption script for the files of step 3.
  5. Setup of Github workflow file.
  6. Upload APK on playstore with github actions.

Step 1 : Fastlane setup for Android platform

Let’s setup Fastlane inside your flutter project. Hope you’ve already downloaded fastlane CLI. Go to your flutter project and then

cd android
fastlane init

Explanation: This will create fastlane folder insider your ./android root folder.

Now fastlane will ask you for your package name which I’ve highlighted in below screenshot. Enter your package name and hit next
.

Okay, Now you might get an output like below screenshot. Fastlane is asking you for path of “json secret file”.

Question : What’s this secret json file?

Answer : hmm, so we want our APK to be deployed automatically on our play store right. But how would fastlane know where to upload this APK, On whose account? By providing this secret file to fastlane, we’re giving necessary information & permission to fastlane to upload APK on our play console. But we’ll skip adding secret file right now. We can add it anytime later on.(In step 3). Hit Enter
.

Fastlane is asking whether you want to add metadata, screenshots and builds!!. We’ll just say “no” for now. Hit Enter
.

You can see two files Fastfile & Appfile created inside ./fastlane folder and Gemfile inside ./android root directory.

Open Gemfile in any editor and add following plugin.

gem "fastlane-plugin-flutter_version", git: "https://github.com/tianhaoz95/fastlane-plugin-flutter-version"

This plugin helps Fastlane to fetch build’s version code from your flutter’s “pubspec.yml” . This version_code will be used to create build number on play console while deployment. Now install plugin by running following command inside ./android/fastlane directory.

fastlane install_plugins

Note: You may be prompted with some fastlane instruction to modify gem file. Say yes and move ahead, you’ll see message of successful plugin installation.

Now open Fastfile and copy paste below code

default_platform(:android)platform :android do
desc "Deploy to closed beta track"
lane :closed_beta do
begin
gradle(task: "clean")
gradle(
task: "bundle",
build_type: 'Release'
)
upload_to_play_store(
track: 'beta',
aab: '../build/app/outputs/bundle/release/app-release.aab',
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,
release_status: "draft",
version_code: flutter_version()["version_code"],
)
end
end
end

Fastlane helps us with too many options like uploading builds, adding screenshots, images, track promotion, metadata, release notes etc. To keep everything simple we’re just going to upload our aab/apk with Fastlane.

Step 2 : Configuration of Project on Google cloud console and PlayStore

Remember we skipped step of secret_json file while setting up Fastlane. We need to provide that file to Fastlane, it’s like an address on envelope. So Fastlane would know where to deliver that build.

secret_json is a Google Developers Service Account Key which will be used for authentication of requests made to Google Play Developer API. Then Fastlane could establish a connection to this API to publish our app to Google Playstore.

Now Open Google Play Console go to Settings > Developer Account > API Access and link your cloud console project with play console.

Note: If you find difficulty with linking project, Then go to Linked Accounts > Firebase Project and link your project there first.

Once you linked your cloud console project (or firebase project) you’ll see something like below screenshot or an empty screen to create service account.

Now let’s create Service Account. Go to your Google Cloud > IAM & Admin > Service Accounts > Select Your Project > “CREATE SERVICE ACCOUNT”

On step 2 you should grant access to this account via selecting option for role as a “service account admin” or “basic”. Once your service account has been created, go to “keys” and click “create new key”.

Select file type `json` and a json file will be downloaded on your PC. Copy It somewhere safe. You won’t be able to download same via console again.

Go back to your “Play Console” and “Refresh service account”. Your newly created service account should be added in last position.

Click on “grant Access” for that service account, and allow an app that you are configuring for CI/CD flow. If you are doing it for a fresh app then you also need to create an app first.

Open your ./android/fastlane/Appfile and add the name of downloaded json file. I’ve named it as “service_account_key.json”.

json_key_file("service_account_key.json")
package_name("com.nividata.expense_manager")

Step 3 : Encryption of jks, properties & service_account_key.json file

I’m going to use public Github repo for my project, so our keystore.jks, key.properties and service_account_key.json file should not be placed directly in our Github project. We’re going encrypt this files and add it at runtime of build. Keep this files handy before you proceed for this step.

  1. key.jks (you can use keytool to generate it, if not present already)
  2. key.properties (ideally located in root ./android/key.properties)
  3. service_account_key.json (that we got from google cloud console, Step 2)

Check in your app/build.gradle for the following lines for key.properties file. If not present, then add it.

Create key.properties and add following code

storePassword={storePassword}
keyPassword={keyPassword}
keyAlias={keyAlias}
storeFile=../{key_name}.jks
#you need to fill values of password, alias and .jks file name

compress and make a zip of this 3 file and name it as key_files.zip

Now let’s encrypt this file with help of GnuPG

gpg --symmetric --cipher-algo AES256 key_files.zip

You’ll be prompted with a dialog for password, remember this password because it’ll be used to decrypt the same file. Now you’ll have an encrypted file named as key_files.zip.gpg. See the graphical representation below.

Now copy this key_files.zip.gpg into your flutter projects ./android root directory.

Step 4 : Write decryption script for the files of step 3

Okay, so we’ve encrypted zip file at android/key_files.zip.gpg, we’re going to follow this steps via shell scripts to decrypt it on remote machine.

  • decrypt key_files.zip.gpg and get our key_files.zip
  • unzip key_files.zip and get .jks, key.properties and service_account_key.json
#!/bin/sh
# --batch to prevent interactive command
# --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt --passphrase="$ANDROID_KEYS_ZIP_PASSPHRASE" \
--output android/key_files.zip android/key_files.zip.gpg && cd android && jar xvf key_files.zip && cd -
ls -d $PWD/android/*
# mv ./android/expensemanager.jks ./android/app
# move your file according to path in key.properties

Go to Github > Settings > Secrets > New Repository Secret
Name = ANDROID_KEYS_ZIP_PASSPHRASE,
Value = “password of zip file you’ve used while gpg encryption”

Step 5 : Setup of Github workflow file

In this step we’re going to create workflow file for github actions. I’m not going deeper in this article about workflow files and their commands but you can refer this article for basic understanding.

Create a file named android-deploy.yml inside ./.github/workflows/ and paste below code.

name: Playstore deploymenton:
push:
branches:
- master
jobs:
#CI
build_android:
name: Building Android
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 12.x
- name: Decrypt Android keys
run: sh ./.github/scripts/decrypt_android_secrets.sh
env:
ANDROID_KEYS_SECRET_PASSPHRASE: ${{ secrets.ANDROID_KEYS_SECRET_PASSPHRASE }}
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: 2.0.1
- name: Install Flutter dependencies
run: flutter pub get
# Add build runner commands here if you have any
- name: Build the APK
run: flutter build apk --release
- name: Upload artifact to Github
uses: actions/upload-artifact@v1
with:
name: release-apk
path: build/app/outputs/apk/release/app-release.apk
#CD
deploy_android:
name: Deploying to playstore
runs-on: ubuntu-latest
needs: [build_android]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 12.x
- name: Decrypt Android keys
run: sh ./.github/scripts/decrypt_secret.sh
env:
ANDROID_KEYS_ZIP_PASSPHRASE: ${{ secrets.ANDROID_KEYS_ZIP_PASSPHRASE }}
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: 2.0.6
- run: flutter pub get
- run: flutter build apk --release
- name: Upload artifact to Github
uses: actions/upload-artifact@v1
with:
name: release-apk
path: build/app/outputs/apk/release/app-release.apk
- name: Run Fastlane
uses: maierj/fastlane-action@v1.4.0
with:
lane: closed_beta
subdirectory: android

Now commit it and push it into master branch, if everything goes correct then it’ll start building your app and then try to upload app on playstore, with failure. Yes, I bet you already have faced & fixed too many errors before coming to this step :) . You’re likely to see something like below screenshot in your actions.
Note : You can completely ignore first job, it’s just to generate & upload APK to artifacts. You can combine it with second job easily.

Okay so we built an APK successfully but failed to upload it on Playstore because Fastlane is throwing an error saying that no package found on playstore. Yes we haven’t created or registered any app on Play console right.
You do not need Step 6 if you are configuring CI/CD for an app which is already deployed on store.

Step 6 : Upload on playstore with workflow

Last step would be to register our app on Google play console and upload a build manually for the first time. Yes, you heard it correct, upload it manually for first time. Because Fastlane would throw an error of package not found if we do not register package name initially.

There is a way that you can do everything via Fastlane without uploading first build manually, but that would be too much for beginners who don’t know anything about Fastlane. If you already know bit about Fastlane then you already know how to do this.

Go to your play console and create an app with name and other information. Once done, download the APK from artifacts and just drop that APK on your console in open / close track.

Go back to Github Repo > Action > Select last failed workflow > Re-run jobs

And Yesssss, Voila !!! You’ve successfully deployed your app on playstore with Github Actions & Fastlane.

Note: I tried to keep workflow and fastlane file as simple as possible, but you can configure in your own way like formating codes, running test cases before making release build. You can completely remove first job from above workflow file because it’s not needed unless you’re doing mentioned things or not want to upload APK file. Fastlane is very powerful tool, you can also experiment with Fastfile for uploading screenshots, metadata, release notes etc.

This CI/CD steps belongs to one of our flutter app named “Expense-Manager” & I’ve deployed both Android & iOS app on their respective store with Github actions. This app Expense-Manager is available on our open repository on Github. You can check workflow files and Fastlane setup files there as well.

If you find any query during setup of CI/CD in your flutter app following above steps, drop your comments below. Always happy to help.

You can check part-2 in this series, which covers CI/CD flow for your flutter iOS apps.

iPhone & Android Developer @Nividata