When developing apps, it’s very important to separate your development and production environments. App flavors allow us to create multiple versions of our app with the same codebase, making it easy to create and test new features without the risk of destroying production data.
You’ll learn how to create three Android and iOS flavors:
development- Build and test new featuresstaging- Test out the app in a production-like environmentproduction- For your users
Multiple main.dart files
We first have to create three entry points, one for each flavor: main_development.dart, main_staging.dart, and main_production.dart.
To run a specific main file, we can use $ flutter run.
E.g. $ flutter run --target lib/main_development.dart
Once we add our Android and iOS flavors configurations, we’ll also have to include the flavor we want to run with --flavor <flavor_name>
E.g. $ flutter run --flavor development --target lib/main_development.dart
However, there are two problems with this:
- It’s annoying to type this out whenever we need to run a different app build
- Running this command in the terminal won’t allow us to utilize VSCode’s debugger
Creating launch configurations in VSCode
Luckily, both of these are easily fixable with a launch.json file. At the root of our project, create a launch.json file inside a folder called .vscode.
We have three configurations, one for each flavor. Each configuration has a name, request, type, program, and arguments.
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch development",
"request": "launch",
"type": "dart",
"program": "lib/main_development.dart",
"args": [
"--flavor",
"development",
"--target",
"lib/main_development.dart"
]
},
{
"name": "Launch staging",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": ["--flavor", "staging", "--target", "lib/main_staging.dart"]
},
{
"name": "Launch production",
"request": "launch",
"type": "dart",
"program": "lib/main_production.dart",
"args": ["--flavor", "production", "--target", "lib/main_production.dart"]
}
]
} Now when we go to the Run tab, we see our three launch configurations.
When we tap run, we get an error about not being able to use the --flavor option, so let’s add our build flavors next.
Exception: The Xcode project does not define custom schemes. You cannot use the --flavor option.
Exited (sigterm) Android Flavor Setup
Adding build flavors to Android is pretty straightforward. Inside of our app/build.gradle, we add our product flavors for production, staging, and development. The name of the app for each build is defined in the resValue line and our applicationId has a suffix for staging and development.
// android/app/build.gradle
flavorDimensions "default"
productFlavors {
prod {
dimension "default"
resValue "string", "app_name", "Flavor Example"
applicationIdSuffix ""
}
stg {
dimension "default"
resValue "string", "app_name", "Stg Flavor Example"
applicationIdSuffix ".stg"
}
dev {
dimension "default"
resValue "string", "app_name", "Dev Flavor Example"
applicationIdSuffix ".dev"
}
} Our current applicationId is com.example.flavorsExample.
This means our bundle identifiers will be:
com.example.flavorsExample- productioncom.example.flavorsExample.stg- stagingcom.example.flavorsExample.dev- development
In our android/app/src/main/AndroidManifest.xml, we set the android:label="@string/app_name" to set our app’s app name.
<!-- android/app/src/main/AndroidManifest.xml-->
<application
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher">
...
</application> iOS Flavor Setup
iOS build configurations are more complicated than Android and can only be done in Xcode, so be sure to follow along closely. In Xcode, let’s create two new schemes called development and staging that both target Runner. Rename the original Runner to production.
Next, we have to go into the Runner project and duplicate the Debug, Release, and Profile configurations for development and staging. Add the -production suffix to the original configuration.
Now let’s go into Manage Schemes and edit the development and staging schemes to point to the correct build configurations. The production scheme already points to our production configurations, which means we don’t have to change them.
In the Build Settings of Target Runner, make sure All and Combined are selected, and then search for bundle identifier. Here we set each configuration’s bundle identifier.
Then set our app’s display name by creating a User-Defined variable called APP_DISPLAY_NAME. Note that if your app display name is longer than 12 characters, the spaces will be removed.
We use this variable in our Info.plist by creating a new key CFBundleDisplayName and setting it to APP_DISPLAY_NAME wrapped in parentheses with a dollar sign.
Finally, set the FLUTTER_TARGET in the Build Settings to target each main.dart entry point based on the selected flavor.
Generate app icons with the flutter_launcher_icons package
Drag your icons into assets/icons. My icons are 1024x1024.
In our pubspec.yaml, add the flutter_launcher_icons package as a dev dependency. This package will generate all of the icon sizes for Android and iOS and put them into the correct folders.
We have to create a configuration file for each of our flavors. Each file is named flutter_launcher_icons- the flavor name .yaml and points to the flavor icon in the assets/icons/ folder.
Set android and ios to true to override the existing Flutter launcher icon for both platforms and define the icon image path.
When you submit your app to the iOS app store, make sure to remove the alpha channel from your app icons, so your app doesn’t get rejected. We can add remove_alpha_ios: true to the yaml files.
# flutter_launcher_icons-development.yaml
flutter_icons:
android: true
ios: true
image_path: 'assets/icons/development-icon.png'
remove_alpha_ios: true
# flutter_launcher_icons-staging.yaml
flutter_icons:
android: true
ios: true
image_path: 'assets/icons/staging-icon.png'
remove_alpha_ios: true
# flutter_launcher_icons-production.yaml
flutter_icons:
android: true
ios: true
image_path: 'assets/icons/production-icon.png'
remove_alpha_ios: true Generate the icons with $ flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons*.
At this point, Android is all good to go as all the files were generated properly. For iOS, we see that our three icons were generated in the Assets.xcassets folder.
Let’s go into the Target’s Build Settings and search for primary app icon. Here we set the correct app icon for each schema.
Wrap Up
I’ve run all three flavors on Android and iOS, and all three apps have the correct name, app icon, and app or bundle identifier.
Every app’s AppBar displays the flavor string we passed into each main file.
And that’s it! You just learned how to quickly set up flavors in your Flutter projects and generate a unique app icon for each flavor.