Flavors are build configurations in Flutter apps that allow developers to create separate environments using the same code base. They allow for customization at runtime based on the defined compile-time configurations.
- Flavors allow efficient management of different development environments (development, staging, production)
- Enables creation of multiple versions of the same app (free and paid versions, separate environments for feature development)
- Simplifies setting different parameters (API endpoints, API keys, app icons) for each configuration
- Makes it easier to manage different app versions
Apps created using the
very_good_clicomes with three flavors -development,staging, andproduction.
Adding Flavors to an Existing Flutter App
When it comes to adding flavors to an existing Flutter app, there are packages available that promise to automate the process, such as flutter_flavorizr.
It's common for these solutions to encounter issues when the app has already made extensive use of native plugins and custom configurations.
As a result, manually adding flavors to an app may be the best course of action. This process requires a bit of human touch, but it provides complete control over the configuration of your app's flavors.
Setting Up Flavors π οΈ
To get a hands-on experience, we will be adding flavors to an existing app from here
We will be setting up
development,stagingandproductionflavors.
Create Target Files π»
We will start by creating three main files for each flavor, main_<flavor>.dart and create a main function.
We can replace the outdated main.dart file with our new entry points. In the bootstrap.dart file, we'll create a new function called bootstrap which will accept the environment from each entry point and use it to launch the app.
-
main_production.dart
void main() => bootstrap('production');
-
main_development.dart
void main() => bootstrap('development');
-
main_staging.dart
void main() => bootstrap('staging');
-
bootstrap.dart
void bootstrap(String env) => runApp(MyApp(env: env));
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.env});
final String env;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
builder: (context, child) {
if (env == 'production') return child!;
return Banner(
message: env.toUpperCase(),
location: BannerLocation.topEnd,
child: child,
);
},
theme: ThemeData(
fontFamily: 'Montserrat',
),
home: const HomePage(),
);
}
}
Since setting up flavors in android is pretty straightforward, we will start with it so we can test it quickly.
Using flavors in Android π€
To add production, staging and development flavors, let's make some changes in the app level build.gradle in android/app/build.gradle file.
Here, let's specify a flavorDimensions and a productFlavors object.
flavorDimensions 'default'
productFlavors {
production {
dimension 'default'
resValue "string", "app_name", "FITNESS"
}
development {
dimension 'default'
applicationIdSuffix '.dev'
versionNameSuffix '.dev'
resValue "string", "app_name", "FITNESS.DEV"
}
staging {
dimension 'default'
applicationIdSuffix '.stg'
versionNameSuffix '.stg'
resValue "string", "app_name", "FITNESS.STG"
}
}
The flavorDimensions line defines the name of the flavor dimension, which is default in this case. Each flavor is defined within the productFlavors block. The production flavor is the base flavor of the app.
The development and staging flavors are similar, but with some differences. The applicationIdSuffix and versionNameSuffix lines add the .dev or .stg suffix to the application ID and version name, respectively. The resValue line sets the name of the app to FITNESS.DEV or FITNESS.STG, respectively.
NOTE: Don't forget to add
android:label="@string/app_name"to theapplicationtag inAndroidManifest.xmlfile.
Let's Test π§ͺ
Now, we can run the app using the following commands.
flutter run --flavor development --target lib/main_development.dart
flutter run --flavor staging --target lib/main_staging.dart
flutter run --flavor production --target lib/production.dart
Once done, you should have three different apps with different names, versions, and bundle identifiers.
iOS Schemas
We will now be creating schemas for each flavor in Xcode. We already have a Runner schema, which is the default schema. We will rename it to production and create two new schemas, development and staging.
- Open the ios folder in Xcode.
open ios/Runner.xcworkspace
- Let's make some changes to our configuration.
- In Project Runner, rename
Debug,Release, andProfileadding-productionsuffixes to each.
- Once done, we will now create these 3 files for each
staginganddevelopmentas well. You can use duplicate.
- Once done, we will have 9 configurations, 3 for each flavor.
- Select the Runner scheme and click the Manage Schemes button.
- Once, there rename
Runnerscheme toproduction, and duplicateproductionto two newdevelopmentandstaging.
- Make sure to add the correct configuration while duplicating.
Similarly, create a development scheme with the correct configuration.
Once you are done with schemes, you will have something like this.
NOTE: Make sure all three schemes have correct configuration, don't forget to recheck
productionscheme configuration.
- Now, let's add the bundle identifier for each configuration. In
Target->Runner, clickBuild Settingsand search forProduct Bundle Identifier.
Add the suffix as required.
Once you are done, you will have something like this.
- Finally, we will create a new
User Defined VariablecalledAPP_DISPLAY_NAMEwhich will have a different name for each configuration.
In same Target -> Runner -> Build Settings, click on the + button, and create a new user-defined variable.
Once, you are done with the user-defined variable, you need to change the Info.plist to use this variable.
<key>CFBundleName</key>
<string>$(APP_DISPLAY_NAME)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>
Let's Test π§ͺ
Similar to android, we can test iOS by running the following commands.
flutter run --flavor development --target lib/main_development.dart
flutter run --flavor staging --target lib/main_staging.dart
flutter run --flavor production --target lib/production.dart
App Icons for Android and iOS π¨
π Let's make it look pretty!
Now, let's have different icons based on the flavors. We will have 3 different icons for each flavor.
We will use flutter_launcher_icons to generate the launch icons. Install it as a dev dependency.
flutter pub add flutter_launcher_icons --dev
Once this is done, we will create flutter_launcher_icons-<flavor>.yaml files for each flavor.
-
flutter_launcher_icons-development.yaml
flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/icons/dev-icon.png"
-
flutter_launcher_icons-staging.yaml
flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/icons/stg-icon.png"
-
flutter_launcher_icons-production.yaml
flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/icons/prod-icon.png"
Now to generate the launch icons we need to run
flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons-*
This will generate all the icons.
Finally, we will set the icons to be dynamic in Xcode.
- In
Runner->Assets.xcassetswe can see that we have all the required icons. We can remove theAppIcon, which is unused.
- Now, in
Target->Runner->Build Settings, search forprimary app icon.
- Now, set the corresponding suffix to each one.
Final Result π
Now, if we run the app, for all the flavors, we will have different icons, names, versions, and bundle identifiers.
NOTE: Make sure to uninstall the previous builds before running the app.
π Bonus
For easy usage, we can create some launch configs for Visual Studio Code and Android Studio.
For Visual Studio Code
Create a new file in .vscode/launch.json with the following content.
{
"version": "0.2.0",
"configurations": [
{
"name": "DEV | DEBUG",
"request": "launch",
"type": "dart",
"program": "lib/main_development.dart",
"args": [
"--flavor",
"development",
"--target",
"lib/main_development.dart"
],
"flutterMode": "debug"
},
{
"name": "DEV | RELEASE",
"request": "launch",
"type": "dart",
"program": "lib/main_development.dart",
"args": [
"--flavor",
"development",
"--target",
"lib/main_development.dart"
],
"flutterMode": "release"
},
{
"name": "DEV | PROFILE",
"request": "launch",
"type": "dart",
"program": "lib/main_development.dart",
"args": [
"--flavor",
"development",
"--target",
"lib/main_development.dart"
],
"flutterMode": "profile"
},
{
"name": "STG | DEBUG",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": [
"--flavor",
"staging",
"--target",
"lib/main_staging.dart"
],
"flutterMode": "debug"
},
{
"name": "STG | RELEASE",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": [
"--flavor",
"staging",
"--target",
"lib/main_staging.dart"
],
"flutterMode": "release"
},
{
"name": "STG | PROFILE",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": [
"--flavor",
"staging",
"--target",
"lib/main_staging.dart"
],
"flutterMode": "profile"
},
{
"name": "PROD | DEBUG",
"request": "launch",
"type": "dart",
"program": "lib/main_production.dart",
"args": [
"--flavor",
"production",
"--target",
"lib/main_production.dart"
],
"flutterMode": "debug"
},
{
"name": "PROD | RELEASE",
"request": "launch",
"type": "dart",
"program": "lib/main_production.dart",
"args": [
"--flavor",
"production",
"--target",
"lib/main_production.dart"
],
"flutterMode": "release"
},
{
"name": "PROD | PROFILE",
"request": "launch",
"type": "dart",
"program": "lib/main_production.dart",
"args": [
"--flavor",
"production",
"--target",
"lib/main_production.dart"
],
"flutterMode": "profile"
},
]
}
This will create the launch config with all the args.
For Android Studio
For Android Studio, you can get the configuration files from here, for all the configs.
π Congrats! We've made it to the end of our exploration of Flutter Flavors! π We've successfully learned how to add different flavors to our flutter application, each with its own unique configurations and build targets.
It's just the tip of the iceberg when it comes to the use cases of flavors in Flutter. For instance, you can inject dependencies based on the flavor you are using, which can entirely change the behavior of your app. The possibilities are endless!
As always, you can refer back to the Github repository for this tutorial at https://github.com/saileshbro/fitness_app_clone if you need any help. Don't forget to give it a βοΈ and help spread the word about this amazing project. If you get stuck or need assistance, feel free to submit an issue or contribute a pull request.
Thank you for joining me on this journey; let's create even more amazing applications together. Have fun coding! π»π¨βπ»























Top comments (0)