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_cli
comes 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
,staging
andproduction
flavors.
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 theapplication
tag inAndroidManifest.xml
file.
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
, andProfile
adding-production
suffixes to each.
- Once done, we will now create these 3 files for each
staging
anddevelopment
as 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
Runner
scheme toproduction
, and duplicateproduction
to two newdevelopment
andstaging
.
- 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
production
scheme configuration.
- Now, let's add the bundle identifier for each configuration. In
Target
->Runner
, clickBuild Settings
and 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 Variable
calledAPP_DISPLAY_NAME
which 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.xcassets
we 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)