When planning for a Flutter app release, it's important to setup different development environments:
- development: used by developers while building the app
- staging: used to distribute builds to the Q/A team and other product stakeholders/clients
- production: used by all the users that download the live app from the App Store
Setting things up this way makes everyone's life easier, and ensures that data in the production environmnent is not altered by mistake during development. 👍
As far as Flutter goes, we can use flavors to setup different environments.
But if our Flutter app uses a Firebase backend, things get a bit more involved and setting things correctly used to require a lot of manual configuration steps.
Luckily, things got a lot easier with the introduction of tools such as Very Good CLI and FlutterFire CLI.
And that's what this article is about: how to setup up a Flutter & Firebase app with multiple flavors and environments, in a few simple steps.
sponsor

Add subscriptions to your Flutter apps. RevenueCat gives you everything you need to build, analyze, and grow IAPs with just a few lines of code.
What are Flutter Flavors?
Flavors let you define different build configurations and change them as needed. For example, you may want to call different API endpoints, use different API keys, or even have different app icons for each configuration.
Flavors are easy to use when building your Flutter app from the command line:
flutter build --flavor development
On Android, this determines which product flavor is used during the Gradle build process.
On iOS, things can be configured so that a different Xcode scheme is used for each flavor.
And as of December 2021, the whole process can be (mostly) automated. Let's see how.
Flutter & Firebase Apps with Multiple Flavors using Very Good CLI
Very Good Ventures released Very Good CLI, a tool that we can use to create Flutter apps pre-configured with build flavors as well as many other useful features.
All we have to do is to install it:
dart pub global activate very_good_cli
Then we can use it to create a new Flutter app like so:
very_good create --org-name com.codewithandrea my_test_app_flavors
If we look at the generated Flutter app, we'll find the following files inside lib
:
lib/
main_development.dart
main_production.dart
main_staging.dart
And we can run the generated app by passing the correct arguments on the command line:
# Run development
flutter run --flavor development --target lib/main_development.dart
# Run staging
flutter run --flavor staging --target lib/main_staging.dart
# Run production
flutter run --flavor production --target lib/main_production.dart
Alternatively, we can choose the flavor directly from VSCode, as Very Good CLI has already generated multiple launch configurations for us:
But the best part is that the Xcode and Android projects are already configured to run with multiple flavors, meaning that we don't have to mess about with Xcode schemes or gradle files. 💯
Setting up multiple Firebase environments
Very Good CLI helps us with the Flutter side of things.
But what about Firebase?
Well, we can use the FlutterFire CLI tool to generate correct Firebase configuration for our project, as I have explained in my previous article:
But this time, we have to create one Firebase project for each Flutter flavor. For consistency, we could name the Firebase projects like so:
my-test-app-flavors-dev
my-test-app-flavors-stg
my-test-app-flavors-prod
And then, we can use the FlutterFire CLI generate the correct Dart initialization file for each Flutter flavor, with this command:
# Dev environment (note: do the same for Stg and Prod)
flutterfire config \
--project=my-test-app-flavors-dev \
--out=lib/firebase_options_dev.dart \
--ios-bundle-id=com.codewithandrea.my-test-app-flavors.dev \
--macos-bundle-id=com.codewithandrea.my-test-app-flavors.dev \
--android-app-id=com.codewithandrea.my_test_app_flavors.dev
When we do this, we have to use the correct bundle ID on iOS and macOS. This can be found by opening the project build settings in Xcode:
And on Android, the correct applicationId
can be found inside android/app/build.gradle
, along with the applicationIdSuffix
used for each flavor:
android {
...
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.codewithandrea.my_test_app_flavors"
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
...
flavorDimensions "default"
productFlavors {
production {
dimension "default"
applicationIdSuffix ""
manifestPlaceholders = [appName: "My Test App Flavors"]
}
staging {
dimension "default"
applicationIdSuffix ".stg"
manifestPlaceholders = [appName: "[STG] My Test App Flavors"]
}
development {
dimension "default"
applicationIdSuffix ".dev"
manifestPlaceholders = [appName: "[DEV] My Test App Flavors"]
}
}
...
}
Dart initialization for each Flutter flavor
Once we have run flutterfire config
for each Flutter flavor, the last step is to use the correct Firebase options for each target:
// main_development.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:my_test_app_flavors/app/view/app.dart';
import 'package:my_test_app_flavors/firebase_options_dev.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const App());
}
And once this is done, we can choose a flavor and run the app on iOS, Android, or web:
Sweet, isn't it? 😀
Note about using Firebase Analytics & Crashlytics
Certain Firebase services still require some platform-specific configuration to work correctly.
In particular, see this comment from this open issue in the Flutterfire client.
Conclusion
As we have seen, we can use the new FlutterFire CLI to configure our Firebase project with just one command.
And if we want to add flavors to the mix, Very Good CLI makes our life easier.
While the entire process can't be completely automated (yet), using FlutterFire and Very Good CLI together can save us a ton of time (and mistakes)! 🚀
Happy coding!