Flutter App distribution using Firebase and CircleCI
In this post, we will be configuring CircleCI build flow along with Firebase distribution for Flutter mobile application. Many of the above components have great documentation so I won’t be going through them, although I highly recommend reading everything and understanding every single step the configuration files have. Just in case something doesn’t work (which shouldn’t happen right?)
So let’s get started, we have lots to do to get the full pipeline working. Since there are so many different platforms we use, I will do my best to provide you the best sources for setup and some of my pain points during the setup.
Opposite of regular way of me explaining every step and line, I will let you do the honors for that since it’s an excellent lesson of gathering all necessary information to understand why. Again, please note that you need to go through all the steps to fill in the blanks on the code parts and getting proper keys and environment variables set up :) In the code blocks you will see few of XXXXXX:s, these are supercritical parts to understand and notice.
.circleci/config.yml
version: 2
branch_filters: &branch_filters
filters:
branches:
only:
- develop
- master
jobs:
build_android:
docker:
- image: cirrusci/flutter:v1.12.13-hotfix.8
steps:
- add_ssh_keys:
fingerprints:
- "XX:XX:XX"
- run:
name: Run Flutter doctor
command: flutter doctor
- checkout:
path: app
- run:
name: Run tests
command: cd app && flutter test
- run:
name: Build the Android version
command: cd app && flutter build apk --build-number=$CIRCLE_BUILD_NUM
- run:
name: Set Ruby version
command: echo 'chruby ruby-2.6' >> ~/.bash_profile
- run:
name: Install Bundler
command: sudo gem install bundler
- run:
name: Install Ruby
command: cd app/android && sudo bundle install
- run:
name: Install Firebase CLI
command: curl -sL https://firebase.tools | bash
- run:
name: Run Fastlane
command: cd app/android && sudo bundle exec fastlane upload_firebase
- store_artifacts:
path: app/build/app/outputs/apk/release/app-release.apk
build_ios:
macos:
xcode: "11.3.1"
environment:
FL_OUTPUT_DIR: output
steps:
- add_ssh_keys:
fingerprints:
- "XX:XX:XX"
- restore_cache:
keys:
- flutter-cache
- run:
name: Download Flutter SDK
command: if ! test -f "flutter_sdk.zip"; then curl -o flutter_sdk.zip https://storage.googleapis.com/flutter_infra/releases/stable/macos/flutter_macos_v1.12.13+hotfix.8-stable.zip; fi
- run:
name: Unzip Flutter SDK
command: unzip flutter_sdk.zip
- save_cache:
key: flutter-cache
paths:
- flutter_sdk.zip
- run:
name: Export flutter path
command: echo 'export PATH="$PATH:`pwd`/flutter/bin"' >> $BASH_ENV
- run:
name: Run Flutter doctor
command: flutter doctor
- checkout:
path: app
- run:
name: Run tests
command: cd app && flutter test
- run:
name: Build the iOS version
command: cd app && flutter build ios --release --no-codesign
- run:
name: Set Ruby version
command: echo 'chruby ruby-2.6' >> ~/.bash_profile
- run:
name: Install Bundler
command: gem install bundler
- run:
name: Install Ruby
command: cd app/ios && bundle install
- run:
name: Install Firebase CLI
command: curl -sL https://firebase.tools | bash
- run:
name: Run Fastlane
command: cd app/ios && bundle exec fastlane upload_firebase
- store_artifacts:
path: app/ios/output/gym/XXXXXX.ipa
workflows:
version: 2
build:
jobs:
- build_android:
<<: *branch_filters
- build_ios:
<<: *branch_filters
Few things you need to have is an Admin account in BitBucket and iTunes Connect, this is preferably not your personal account but could be somewhat generic company’s CI account made for this purpose.
Rough execution steps would be the following:
1. Log in with your CI account to BitBucket and after that create a new project in CircleCI. NOTE: In our case, we want to create a user key instead of a deploy key because iOS will have a separate repository for signing the application.
https://circleci.com/docs/2.0/gh-bb-integration/
In case you don’t see your organization in CircleCI, try clearing all browser cache, or using a different browser.
It took me a while to understand that for Android we can use CirrusCI docker image, which contains Android and Flutter build system (and runs on Linux Virtual Machine), but iOS needs to be built with MacOS, so we needed to download Flutter SDK in order to build iOS app.
2. Since we want the build for our testers/demo use we need to configure other services too, to keep it consistent we initialize Fastlane to both Android and iOS projects.
Fastlane has pretty good documentation to cover the flow: https://docs.fastlane.tools and iOS signing with Match: https://docs.fastlane.tools/actions/match/
android/fastlane/Fastfile
# fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Upload to Firebase"
lane :upload_firebase do
firebase_app_distribution(
app: "XXXXXX:android:XXXXXX",
apk_path: "../build/app/outputs/apk/release/app-release.apk",
firebase_cli_token: "XXXXXX",
groups: "qa, android",
release_notes: changelog_from_git_commits(
commits_count: "5",
merge_commit_filtering: "exclude_merges"
)
)
end
end
ios/fastlane/Fastfile
# fastlane/Fastfile
default_platform :ios
platform :ios do
before_all do
setup_circle_ci
end
desc "Upload to Firebase"
lane :upload_firebase do
increment_build_number(
build_number: "$CIRCLE_BUILD_NUM"
)
match(type: "adhoc")
gym(scheme: "Runner")
firebase_app_distribution(
app: "XXXXXX:ios:XXXXXX",
groups: "qa, ios",
release_notes: changelog_from_git_commits(
commits_count: "5",
merge_commit_filtering: "exclude_merges"
)
)
end
end
ios/fastlane/Matchfile
git_url("git@bitbucket.org:XXXXXX/XXXXXX.git")
storage_mode("git")
type("development") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier(["XXXXXX"])
username("XXXXXX@XXXXXX.XX") # Your Apple Developer Portal username
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match
3. Once Fastlane is successfully configured, the final step is Firebase. Firebase's own documentation seemed to work quite nice when using Fastlane along with Firebase CLI.
https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane
https://firebase.google.com/docs/app-distribution/android/distribute-fastlane
In case the official documentation doesn’t work (which unfortunately happens a lot), patience is the key. Take a break for an hour or two, or start again fresh the next day.
I had lot’s of issues with Fastlane because of multiple Ruby environments on my laptop. Removing all Ruby installations and reinstalling solved it. After that, the whole Fastlane setup with Match was butter smooth.
Adding debugging information to the build steps can also help to identify the issue, in my case using simple cd, pwd and ls -al between the steps saved the day by showing the directory structure and all files in CircleCI logs to get all the stuff right.
Oh and one thing with Yaml. You better be careful with the indentations, they can make all the difference. You don’t wanna spend 3 hours, like I did, trying to figure out where the problem is because CircleCI errors don’t give you the exact line of error. This tool can be very useful when verifying the Yaml output: https://yaml-online-parser.appspot.com/
That’s all for now :)
About The Author:
Niko Rehnbäck is Finlabs Lead Mobile Developer and resident mobile development guru. Working in a team, or as a one-man show, Niko’s skills in mobile development are some of the best and fastest around.
Niko has strong experience in modern development methods and technologies and has flexed his development muscles at the likes of Nokia and Osuuspankki, where he was a leading member of teams creating global, award-winning products.
On our projects, Niko is often the driving force. His passion to push himself to the edge encourages other team members to push themselves beyond their regular limits.