Distribute Flutter app to Firebase and stores from Codemagic

6 minute read

For last couple of weeks I was configuring a build pipeline for upcoming Flutter Europe conference app. I decided to use Codemagic to see if it’s possible to handle multiple flavors and fastlane builds there. After few discussions with Codemagic support on their Slack and many build minutes I came up with pretty decent configuration. This post will showcase how to prepare your workflow to build Flutter app in multiple flavors and distribute it to Firebase App Distribution or any of the stores.

Contents

Foreword

The first thing to have in mind is that I wanted my app pipeline be agnostic of CI environment. The app should be able to build on my computer, any of contributors and of course on CI/CD. The best way to achieve that is to use fastlane for as many steps as possible.

Fastlane is a very handy tool that automates almost every task a mobile developer can think of. In my case I use fastlane to handle signing and provisioning of iOS app and distribution for both platforms.

If you want to learn how to prepare your app to use flavors take a look at my other blog post. Flavors allow you to have subtly different configuration for each environment. Each flavor has its own bundle id, icon and can be connected to different Firebase account.

You can also investigate my Flutter configuration in the project repository. The app is still in its early stage, but overall template is already done.

We’ll be using Codemagic in this tutorial. It’s still a very young CI/CD platform but they came a long way and available tools allow to have complete pipeline with tests, QA distribution and continuous updates to production.

Environment variables

After you successfully connected your app to Codemagic you should set up all necessary environment variables. I like to store all secret values and files right there. Codemagic doesn’t have any generic file storage support (yet, but you can vote for the feature request here), so the only way to pass files to the machine is to encode them in base64, save as env variable, and then decode them during build.

List of all environment variables used in my setup:

ANDROID_KEYSTORE_ALIAS

ANDROID_KEYSTORE_PASSWORD

ANDROID_KEYSTORE_PATH

ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD

APK_PATH - required for Firebase step to find correct apk file

- required for Firebase step to find correct apk file APP_ID - bundle id of application e.g. dev.fluttereurope.conferenceapp

- bundle id of application e.g. APP_STORE_CONNECT_TEAM_ID

APPLE_DEVELOPER_TEAM_ID - used by fastlane match step, this is similar to U9XX9XXXX9

- used by fastlane match step, this is similar to SSH_KEY - key used to access private repository with Apple provisioning profiles and certificates

- key used to access private repository with Apple provisioning profiles and certificates CI - Codemagic theoretically has this variable set, but for some reason in my build fastlane had problems with respecting it, so I added it on my own with some dummy value

- Codemagic theoretically has this variable set, but for some reason in my build fastlane had problems with respecting it, so I added it on my own with some dummy value FASTLANE_PASSWORD - password used to log in into Apple Developer Console by fastlane

- password used to log in into Apple Developer Console by fastlane FASTLANE_USER - user used to log in into Apple Developer Console by fastlane

- user used to log in into Apple Developer Console by fastlane FCI_KEYSTORE_FILE - encoded Android keystore

- encoded Android keystore FIREBASE_ANDROID_TEST_APP_ID - take a look at docs here

- take a look at docs here FIREBASE_CLI_TOKEN - token used by Firebase CLI, learn more here

- token used by Firebase CLI, learn more here FIREBASE_IOS_TEST_APP_ID - take a look at docs here

- take a look at docs here FIREBASE_TESTERS - name of testers group that app should be distributed to

- name of testers group that app should be distributed to FLAVOR - in my case it’s tst and prod

- in my case it’s and GOOGLE_SERVICE_ACCOUNT_KEY_BASE64

GOOGLE_SERVICE_ACCOUNT_KEY

GOOGLE_SERVICES_JSON_BASE64

GOOGLE_SERVICES_JSON_PATH

GOOGLE_SERVICES_PLIST_BASE64

GOOGLE_SERVICES_PLIST_PATH

KEYCHAIN_NAME - we need to create temporary keychain to store Apple credentials

- we need to create temporary keychain to store Apple credentials MATCH_PASSWORD - password used to encrypt provisioning profiles and certificates in the private repository, learn more here

- password used to encrypt provisioning profiles and certificates in the private repository, learn more here TARGET_FILE - this is the entry point of the app e.g. main_tst.dart

- this is the entry point of the app e.g. VERSION_NUMBER - major and minor part of app version e.g. 1.0

Storing files in environment variables

In order to save some file as env variable you need to encode it in base64. To do this you can simply run:

openssl base64 -in ./GoogleService-Info_prod.plist -out ./GoogleService-Info_prod.plist_base64.txt

In order to decode it and save to file on the build machine just execute in custom post clone step:

echo $GOOGLE_SERVICES_JSON_BASE64 | base64 --decode > $GOOGLE_SERVICES_JSON_PATH

My full post-clone step looks like this:

#!/usr/bin/env sh set -e echo $FCI_KEYSTORE_FILE | base64 --decode > $ANDROID_KEYSTORE_PATH echo $GOOGLE_SERVICE_ACCOUNT_KEY_BASE64 | base64 --decode > $GOOGLE_SERVICE_ACCOUNT_KEY echo $GOOGLE_SERVICES_JSON_BASE64 | base64 --decode > $GOOGLE_SERVICES_JSON_PATH echo $GOOGLE_SERVICES_PLIST_TST_BASE64 | base64 --decode > $GOOGLE_SERVICES_PLIST_TST_PATH echo " ${ SSH_KEY } " > /tmp/bkey # adding custom ssh key to access private repository chmod 600 /tmp/bkey cp /tmp/bkey ~/.ssh/bkey ssh-add ~/.ssh/bkey # installing Firebase CLI curl -sL firebase.tools | bash # update fastlane and install all dependencies sudo gem update fastlane bundle install

Fastlane and Ruby 2.3.6

Codemagic machines have only one version of Ruby installed (2.3.6). This isn’t a problem as we have access to rbenv . However, switching to desired Ruby version takes time and that’s the valuable commodity on codemagic. Feature request for cached multiple Ruby versions is here.

Unfortunately some plugins and gems don’t support Ruby 2.3.6, so a workaround for this is to set some dependencies directly in fastlane’s Pluginfile [Source].

gem 'fastlane-plugin-firebase_app_distribution' gem 'signet' , '0.11.0' gem 'fastlane-plugin-badge' gem 'google-cloud-env' , '1.2.1' gem 'google-cloud-core' , '1.3.2'

Building the app

This process is pretty straightforward. I’m using the Codemagic step here. Few things to remember are:

Firebase App Distribution accepts only apk files at the moment. They might implement support for app bundles next year. AppCenter, however, supports them already.

You can use Android app bundles for production build where we deploy app to Google Play

I pass some custom parameters to flutter build related to flavor and build numbers, e.g.: Android: --target $TARGET_FILE --flavor $FLAVOR --build-name=$VERSION_NUMBER --build-number=$BUILD_NUMBER iOS: --no-codesign --target $TARGET_FILE --flavor $FLAVOR --build-name=$VERSION_NUMBER --build-number=$BUILD_NUMBER - we’ll sign the app later with fastlane

related to flavor and build numbers, e.g.: I use gradle config to sign the app. Take a look at it here.

Distributing the app

Now it’s time for the grande finale. Unfortunately we’ll have to rebuild the iOS app. This is a known limitation of Flutter. We tried to find a workaround for that by building only AOT version of Flutter and then archive it with Xcode, but some problems with pods were arising very randomly. If you have any ideas on how to improve here, please let me know!

In the fastfile there are several lanes configured. In the iOS lane we need to create or update provisioning profiles, set signing style to manual, update the Xcode project and finally archive it. This may take several minutes depending on the plugins you use.

In case of Android we only deploy our app to Firebase or Google Play. Deployment to Google Play requires a service account. Learn more here.

My post-build script is simply:

#!/usr/bin/env sh set -e bundle exec fastlane android tst bundle exec fastlane ios tst

Wrapping up

I hope this post will save you some time while configuring the build pipeline on Codemagic. I know my approach is a bit unorthodox but it’s been successful for over a year now on various platforms like custom Mac Mini, Bitrise and now Codemagic.

Special thanks to Maciek for his support in configuring a platform-agnostic multi-flavor build pipeline.