This article will guide you on how you can automate the CI/CD workflow of your Flutter’s Android app.
If you are not familiar with the concept behind CI/CD, Github Actions, or Fastlane, you can read this article I wrote to help you get started.
Prerequisites
Before continue reading this,
- You should have at least tried building your Flutter app locally, and released it manually to Google Play Console.
- Make sure you have Fastlane installed on your development machine.
Workflow Setup
Setup Fastlane
a. Initialize Fastlane for Android
cd android
fastlane init
This will ask for the details of your app.
[01:00:00]: Package Name (com.krausefx.app): io.github.joshuadeguzman.fastlane_flutter_actions
Leave the Path to the json secret file
blank for now.
...
Feel free to press Enter at any time in order to skip providing pieces of information when asked
[01:00:00]: Path to the json secret file:
b. Install fastlane plugin to retrieve version code from Flutter
In android/Gemfile
, add the following plugin.
...
gem "fastlane"
# Add this plugin
gem "fastlane-plugin-flutter_version", git: "https://github.com/tianhaoz95/fastlane-plugin-flutter-version"
Install the plugin.
cd android/fastlane
fastlane install_plugins
c. Configure Fastfile
In android/fastlane/Fastfile
, replace everything with the following
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: 'Closed 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
NOTE: By default, when you set the
track
tobeta
, fastlane uploads your build to the Open beta testing track in Google Play Console. To create a custom and closed beta track namedClosed beta
, please follow the instructions here.
d. Configure Appfile
In android/fastlane/Appfile
, add the path for the service_account_key.json
json_key_file("service_account_key.json") # Add this
package_name("io.github.joshuadeguzman.demo_flutter_actions")
Setup Android App
a. Generate a Service Account Key
Google Developers Service Account Key (a.k.a. Service Account Key) will be used for authenticating requests sent to the Google Play Developer API. Fastlane then establishes a connection to this API to publish your app to Google Play.
1 . Open the Google Play Console
2 . In the Settings menu, select API access
, then click CREATE SERVICE ACCOUNT
3 . Navigate to the provided Google Developers Console link in the dialog
4 . Click CREATE SERVICE ACCOUNT
at the top of the Google Developers Console
5 . Provide the details required, then click CREATE
6 . Click Select a role
, select Service Accounts
, then click Service Account User
7 . In the Service Accounts dashboard, navigate to the Actions
column, tap the menu for the service account that you created, then click Create Key
8 . Select JSON
as the key type, then click SAVE
9 . Back on the Google Play Console, click DONE
to close the dialog
10 . Click on Grant Access
for the newly added service account
11 . Make sure that the role of this service account will have the permission upload builds
12 . Click ADD USER
to close the dialog
b. Encrypt sensitive files
Encrypting files such as *.jks
, key.properties
, or the service account key adds additional layer of security to prevent any unauthorized access to view sensitive credentials or regain access to the services you use in your app.
First, add these files to your root .gitignore
...
# Ignore Android keys
key.jks
key.properties
service_account_key.json
android_keys.zip
In key.properties
, you should set the correct path of the storeFile
relative to the app
directory
storePassword=<YOUR_STORE_PASSWORD>
keyPassword=<YOUR_PASSWORD>
keyAlias=<YOUR_STORE_KEY>
storeFile=../key.jks
Next, zip the following files, then name the zip file as android_keys.zip
android
- app/
- fastlane/
- ...
- android_keys.zip 👈
Lastly, encrypt the files
Encrypt files using GPG.
You will be prompted to enter a passphrase. Remember it correctly because it will be used later by Github Actions to decrypt the files.
cd android
gpg --symmetric --cipher-algo AES256 android_keys.zip
Test if you can decrypt and unzip the file locally
cd android
rm android_keys.zip key.jks key.properties service_account_key.json
gpg --output android_keys.zip --decrypt android_keys.zip.gpg && jar xvf android_keys.zip
Setup Github Actions
a. Create a Github Action script to decrypt the files
Create a scripts directory
md .github/scripts
Inside the scripts
folder, create a file named decrypt_android_secrets.sh
, and add the following
#!/bin/sh
# --batch to prevent interactive command
# --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt --passphrase="$ANDROID_KEYS_SECRET_PASSPHRASE" \
--output android/android_keys.zip android/android_keys.zip.gpg && cd android && jar xvf android_keys.zip && cd -
b. Configure Github secrets
In Github repository’s settings, add the passphrase you assigned earlier as a secret variable named ANDROID_KEYS_SECRET_PASSPHRASE
.
c. Configure Github workflow file
Create a Github workflow directory.
md .github/workflow
Inside the workflow
folder, create a file named cd.yml
, and add the following.
# cd.yml
name: CD
on:
push:
branches:
- "v*"
jobs:
# CI
build_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_keys.sh
env:
ANDROID_KEYS_SECRET_PASSPHRASE: ${{ secrets.ANDROID_KEYS_SECRET_PASSPHRASE }}
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: 1.17.5
- name: Install Flutter dependencies
run: flutter pub get
# Add build runner commands here if you have any
- name: Format files
run: flutter format --set-exit-if-changed .
- name: Analyze files
run: flutter analyze .
- name: Run the tests
run: flutter test
- 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:
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_android_keys.sh
env:
ANDROID_KEYS_SECRET_PASSPHRASE: ${{ secrets.ANDROID_KEYS_SECRET_PASSPHRASE }}
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: 1.17.5
- name: Install Flutter dependencies
run: flutter pub get
run: flutter build apk --release
- name: Run Fastlane
uses: maierj/fastlane-action@v1.4.0
with:
lane: closed_beta
subdirectory: android
That’s it! Now for the moment of truth, test the workflow on Github Actions.
Test Workflow
a. Create a branch
Create a branch, eg. v0.0.3
.
git checkout -b v0.0.3
b. Trigger the workflow
Push the new commits to the newly created branch to trigger the workflow.
git push origin v0.0.3
Bonus
You can also create other Github workflows such as running CI jobs whenever there’s a new pull request to the master
branch.
Here’s an example of a CI only workflow for a pull request
# ci.yml
name: CI
on:
pull_request:
branches:
- master
jobs:
build_apk:
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_keys.sh
env:
ANDROID_KEYS_SECRET_PASSPHRASE: $
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: 1.17.5
- name: Install Flutter dependencies
run: flutter pub get
- name: Format files
run: flutter format --set-exit-if-changed .
- name: Analyze files
run: flutter analyze .
- name: Run the tests
run: flutter test
- name: Build the APK
run: flutter build apk --release
- name: Upload artifact
uses: actions/upload-artifact@v1
with:
name: release-apk
path: build/app/outputs/apk/release/app-release.apk
This triggers the workflow to run and display its status on the pull request.
Awesome!
That’s it for your Flutter’s Android app.
Take note, the setup of jobs may vary depending on the tools or dependencies your project requires. For example, if it uses Firebase Cloud services, you might need to encrypt the google_services.json
file as well.