Part 1: Setting Up Dev and Prod Flavors for Flutter app (on iOS and Android platforms)

Danis Ziganshin
7 min readApr 15, 2024

--

Intro

As Flutter developers, we often face the challenge of efficiently managing development and production environments for our projects.
Flutter flavors help us manage these various configurations within the Flutter project.

Benefits

* Single project would build 2 different apps without any in-place modifications. One for staging environment and one for production.
* We would be able to add and deploy new features on dev app without interfering with the prod app installed on tester devices.

Tools and dependencies needed

* Xcode schemes and .xcconfig files will be used to set up the iOS project.
* Manifest placeholders and build.gradle flavors will be used for Android.
* The launch.json file will enable us to switch between flavors when debugging our apps in VSCode.

Let’s start!

Create a new project

I’ll create a test Flutter app to demonstrate everything build from scratch.
According to the Flutter dev portal I’ll need to run this command:

flutter create test_drive - platforms=ios,android

I’ve added — platforms option to exclude macos and linux platforms from being added, since this article is focused only on iOS and Android.
Make sure that it builds and can be launched. I use VSCode and iOS simulator as my default choice.

Configure Flutter Flavors

I want the app to support 2 flavors: dev and prod. Lets configure our flutter project so that it will support them.
First we need to create our launch.json file that will be used to build and run our app. This can be done using VSCode tools by pressing “Cmd+Shft+P” and entering “add configuration” to the text bar

adding launch configuration
adding launch configuration

This will create a .vscode folder at the root of the project and a launch.json file within it.
Let’s open it and add our custom configurations.

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "PROD",
"request": "launch",
"type": "dart",
"args": [
"--flavor=prod",
]
},
{
"name": "DEV",
"request": "launch",
"type": "dart",
"args": [
"--flavor=dev",
]
},
]
}

Replace all automatically generated configurations with our own.
Our project won’t launch on the iOS simulator because we haven’t configured our Xcode project yet. Let’s do that next.

Configure Xcode schemes and .xcconfig files

Open generated iOS project using Xcode. You can do this manually or by running the following command within the working directory.

open -a Xcode.app ios/Runner.xcworkspace

We are going to edit the configurations. Select Runner from the left menu -> Runner under the PROJECT menu -> Configurations. There are three default configurations: Debug, Release, and `Profile. We will replace them with six new ones: Debug-dev, Debug-prod, Release-dev, Release-prod, Profile-dev, and Profile-prod.

For each of these configurations, we will create a .xcconfig file, plus two additional .xcconfig files to eliminate duplicated variable declarations. This approach ensures CocoaPods won’t complain about missing .xcconfig files since it will generate its own .xcconfig files for each configuration. These files should be referenced by us later on.
You can delete existing Debug.xcconfig and Release.xcconfig files, as we will be using our own .xcconfig files.

Lets start with the dev.xcconfig and prod.xcconfig files.
1. Hit File -> New -> File… or press “Cmd+N” key.
2. Enter “Configuration” in the search field and select “Configuration Settings File”, then click “Next”.

adding xcconfig file
adding xcconfig file

3. Set “dev” as the filename, select the “test_drive/ios/Flutter” folder, leave the targets checkboxes unchecked (Runner and RunnerTests) and click the “Create” button.
Add the following lines to the created file:

#include "Generated.xcconfig"
IDENTIFIER=com.example.testdrive.dev
APP_NAME=Test Drive DEV

Here we define IDENTIFIER and APP_NAME that will be used in Info.plist file and Build Settings.

create dev configuration
create dev configuration

Now follow the same steps to create the prod.xcconfig file and insert these lines into it:

#include "Generated.xcconfig"
IDENTIFIER=com.example.testdrive
APP_NAME=Test Drive

Now we need to create debug-dev, debug-prod, release-dev, release-prod, profile-dev and profile-prod .xcconfig files.
Here is the content to be inserted into them:

debug-dev.xcconfig

#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-dev.xcconfig"
#include "dev.xcconfig"

debug-prod.xcconfig

#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-prod.xcconfig"
#include "prod.xcconfig"

release-dev.xcconfig

#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-dev.xcconfig"
#include "dev.xcconfig"

release-prod.xcconfig

#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release-prod.xcconfig"
#include "prod.xcconfig"

profile-dev.xcconfig

#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile-dev.xcconfig"
#include "dev.xcconfig"

profile-prod.xcconfig

#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile-prod.xcconfig"
#include "prod.xcconfig"

The next step is to configure the project configurations using the corresponding .xcconfig files, as shown in the screenshot:

setting up configurations
setting up configurations

Set up project schemes to use the configurations we created.
Replace the existing Runner scheme with dev and prod schemes.

edit dev scheme
edit dev scheme
edit prod scheme

Open Info.plist file and put $(APP_NAME) for the Bundle display name field.
Then, navigate to Runner -> Targets -> Runner -> Build Settings and enter “$(IDENTIFIER)” for the `Product Bundle Identifier` field (a.k.a. PRODUCT_BUNDLE_IDENTIFIER)

Since this is a fresh project without dependencies, CocoaPods is not installed yet.
Let’s manually add it.
Open Terminal, navigate to the ios folder `cd ios`, and run the `pod init` and `pod install` commands.

Now, you can run your Flutter project on an iOS simulator using either the dev or prod launch configurations.
Each configuration will have a separate bundle identifier, thus they won’t override each other on subsequent launches.

Configure Android build.gradle and AndroindManifest.xml files

Open “android/app/build.gradle” file and add following code inside `android {}` block:

...
flavorDimensions "app"


productFlavors{
dev {
dimension "app"
resValue "string", "app_name", "Test Drive DEV"
applicationIdSuffix ".dev"
}

prod {
dimension "app"
resValue "string", "app_name", "Test Drive"
}
}
...

Open “android/app/src/main/AndroidManifest.xml” and change the following declarations in the beginning of the file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testdrive">
<application
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">

Configure app icons for `dev` and `prod` apps

Our app icons right now look identical on the user phones, we can fix that.

iOS part

Open iOS project in Xcode. Run `open -a Xcode.app ios/Runner.xcworkspace`.
Open Assets on the left menu, click the plus icon on the bottom and select “iOS” -> “iOS App Icon”, set a name to “AppIcon-dev”.

add new image set

Drag and drop your image for the dev app to the field for an image.

dev app icon ios

Add following line into dev.xcconfig file:

APP_ICON_NAME=AppIcon-dev

Add following line into prod.xcconfig file:

APP_ICON_NAME=AppIcon

Go to Runner on the left menu, select Runner target, go to Build Settings, find “Primary App Icon Set Name” line and replace AppIcon string with ${APP_ICON_NAME}

setting app icon name variable

Android part

Generate Android images. No need to use Adaptive images for the dev app, but the idea should be similar. We create images for the dev app and set approriate image names for the dev and prod flavors appropriately.

I’ve used this Figma plugin to generate Android icon images.
I’ve put my image from the iOS dev app to the foreground layer template, I don’t need to have a fancy app icon on my dev app, so I exported only ic_launcher_foreground layer.

figma plugin screenshot

Open android/app/src/main/res folder and move exported ic_launcher_foreground_*.png images into corresponding mipmap-* folders. Remove last part of the filename for every moved file. Each file should be named ic_launcher_foreground.png within its enclosed folder.

inserting android ic_launcher_foreground images

Create mipmap-anydpi-v26 folder within android/app/src/main/res and add ic_launcher_dev.xml file inside it with the following content:

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Insert ic_launcher_background.xml file inside android/app/src/main/res/values folder with the following content:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

Open android/app/src/main/AndroidManifest.xml and replace `android:icon=”@mipmap/ic_launcher”` with `android:icon=”${appIcon}”` string.

And the final step, open android/app/build.gradle and set appIcon variable for the flavors that we created earlier. We are going to use manifestPlaceholders for that purpose. productFlavors section should look like this:

    productFlavors{
dev {
dimension "app"
resValue "string", "app_name", "Test Drive DEV"
manifestPlaceholders = [
appIcon: "@mipmap/ic_launcher_dev",
]
applicationIdSuffix ".dev"
}

prod {
dimension "app"
resValue "string", "app_name", "Test Drive"
manifestPlaceholders = [
appIcon: "@mipmap/ic_launcher",
]
}
}

Conclusion

Now you’ve learned how to configure your Flutter project to support multiple flavors, enabling the seamless publication of both staging and production versions of your app with minimal hassle. This setup not only facilitates a more organized development process but also ensures that new features can be tested thoroughly before they reach your end-users. Moving forward, consider exploring advanced CI/CD pipelines to automate builds and deployments for different flavors, further enhancing your development workflow.

--

--