Build for multiple environments with Flutter flavors

Thomas Gallinari
5 min readJul 8, 2020
You better get it right!

Hello and welcome to this new article of the Journey to Flutter series!

I will explain here how we can build multiple versions of the same Flutter app, each one with its own name, icon, and configuration variables, and how we can get all these versions installed together on a device.

At Intent Technologies, I push a new staging version of the mobile application to the QA team every time I finish a User Story. So before even starting to develop the first feature, I needed to be sure that the application could be distributed and tested. In addition to this test version, the QA team also needs to have the production version installed because they also deal with user feedbacks. And as a developer, I obviously need to switch between these environments as well — including the development version of course.

If you have ever been in this situation — which is very likely — you know how painful it can be to uninstall a version when you have to test something on a different environment, then install it back to continue your work.

So we need to install all these versions of the application together on a same device. We also need different names and icons, otherwise it will not help that much! Let’s see how we can solve this with Flutter.

Flutter Flavors

Flavors are the Flutter way to offer your application in multiple variants. If you are an Android developer it might sound familiar, and not surprisingly Flutter flavors will translate to Android Product Flavors. On iOS they will be defined with Schemes.

Android flavors

On Android it is pretty straightforward as flavors are already a part of the framework. They are defined in android/app/build.gradle:

Here I defined a group of flavors, named env, and three related flavors: dev, staging and prod. For dev and staging I added a suffix to the application ID. This is what will allow to install all three variants on a same device, because they will be seen as different apps by Android. The prod flavor will keep the default application ID as I did not define a suffix for it.

We can provide different resources for each variant by creating the related folders in under src:

Thus we can provide a different icon (mipmap-xxxxx/ic_launcher.png) and name (values/strings.xml) per flavor.

Important: do not forget to change the android:label in AndroidManifest.xml as well.

Note: again I did not provide a specific folder for prod as it will default to the main resources.

Now with these three flavors and the two default build types (debug and release), Gradle will generate under the hood a matrix of targets to build any possible configuration (runDevDebug, runStagingDebug, buildStagingRelease, buildProdRelease, and so on).

iOS Schemes

Things are a little bit trickier on iOS.

On Android, as stated above, Gradle will create all the possible targets by combining the build types and product flavors.

On iOS, we need to define manually all the targets we need.

First we need an .xcconfig file per flavor. If you open the project’s xcworkspace you will notice that Flutter has already created three such files in the Flutter folder: Debug, Release and Generated. The latter contains all the variables that Flutter will use when building the project, and the first two files just include Generated.

I created a .xcconfig file per flavor. Each of them includes Generated.xcconfig to inherit the variables defined by Flutter, plus a variable that I use in Info.plist to name each variant:

We also need to define different Bundle Identifiers, so we are able to install all the variants of the app on a same device:

But this is not enough. In order to use these files, we must now declare Build Configurations.

I used the “plus” button to duplicate the existing configurations (Debug and Release) for each variant I needed.

You can rename the default Configurations to match your flavors, for example DebugDebug-Dev and ReleaseRelease-Prod, then duplicate them for the other configurations.

Important: do not forget to select the appropriate Configuration File for each configuration!

Now we can assign a different icon to each configuration:

This is not over yet. For Flutter, a flavor is a custom Scheme on iOS, so we need to create a scheme for each flavor.

We create the Schemes we need with the “plus” button, then we need to edit them to select the appropriate Build Configuration for each action:

Important! You will notice that my schemes are capitalized. This is because Flutter passes the flavor capitalized to xcodebuild (e.g. “dev” becomes “Dev”). See https://github.com/flutter/flutter/issues/59029#issuecomment-648287841.

And here we are, up and running!

Usage

Now that we have our flavors configured for both platforms, we can either build / run them from Android Studio / Xcode, or pass them as a parameter of the flutter command line. Here are a few examples:

flutter run --flavor dev

flutter build --flavor prod --release

Once flavors are configured, you must pass the flavor parameter.

In the next article, I will explain how our flavors will be used when building and deploying via Fastlane and Travis CI.

--

--