Setting up Flutter flavors: dev, staging, and production
By Ann Tech · 8 September 2024
Flutter flavors let you run the same codebase as three completely separate apps — dev, staging, and production — each with its own app ID, Firebase project, API URL, and app icon.
What flavors solve
Without flavors:
- Dev and production share the same Firebase project (logs pollute analytics)
- You can't install dev and production simultaneously on a device
- Testers might accidentally use the dev version in production
With flavors, each environment is a distinct app to both the OS and the user.
Setting up flavors on Android
android/app/build.gradle.kts:
android {
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationId = "com.example.myapp.dev"
versionNameSuffix = "-dev"
resValue("string", "app_name", "MyApp Dev")
}
create("staging") {
dimension = "environment"
applicationId = "com.example.myapp.staging"
versionNameSuffix = "-staging"
resValue("string", "app_name", "MyApp Staging")
}
create("production") {
dimension = "environment"
applicationId = "com.example.myapp"
resValue("string", "app_name", "MyApp")
}
}
}
Setting up flavors on iOS
- In Xcode → project → Configurations, add
Debug-dev,Debug-staging,Release-staging,Release-production(duplicate existing). - Create Schemes:
dev,staging,production. Each scheme points to the corresponding configuration. - Set bundle IDs per scheme in Build Settings:
PRODUCT_BUNDLE_IDENTIFIER = com.example.myapp.dev.
Or use flutter_flavorizr to automate all of this:
# pubspec.yaml
flavorizr:
flavors:
dev:
app:
name: "MyApp Dev"
android:
applicationId: "com.example.myapp.dev"
ios:
bundleId: "com.example.myapp.dev"
staging:
app:
name: "MyApp Staging"
android:
applicationId: "com.example.myapp.staging"
ios:
bundleId: "com.example.myapp.staging"
production:
app:
name: "MyApp"
android:
applicationId: "com.example.myapp"
ios:
bundleId: "com.example.myapp"
flutter pub run flutter_flavorizr
Entry points per flavor
// lib/main_dev.dart
void main() {
AppConfig.init(environment: Env.dev, apiBaseUrl: 'http://localhost:3000');
runApp(const MyApp());
}
// lib/main_staging.dart
void main() {
AppConfig.init(environment: Env.staging, apiBaseUrl: 'https://api.staging.example.com');
runApp(const MyApp());
}
// lib/main.dart (production)
void main() {
AppConfig.init(environment: Env.production, apiBaseUrl: 'https://api.example.com');
runApp(const MyApp());
}
Running a specific flavor
# Dev
flutter run --flavor dev -t lib/main_dev.dart
# Staging
flutter run --flavor staging -t lib/main_staging.dart
# Production build
flutter build appbundle --flavor production -t lib/main.dart
flutter build ipa --flavor production -t lib/main.dart
Firebase per flavor
Each flavor needs its own google-services.json / GoogleService-Info.plist:
android/app/src/dev/google-services.json
android/app/src/staging/google-services.json
android/app/src/production/google-services.json
ios/Runner/Firebase/dev/GoogleService-Info.plist
ios/Runner/Firebase/staging/GoogleService-Info.plist
ios/Runner/Firebase/production/GoogleService-Info.plist
For iOS, a pre-build script in Xcode copies the right file:
# Xcode build phase (Run Script)
cp "${SRCROOT}/Runner/Firebase/${FLAVOR}/GoogleService-Info.plist" \
"${SRCROOT}/Runner/GoogleService-Info.plist"
App icons per flavor
Place flavor-specific icons in:
android/app/src/dev/res/mipmap-hdpi/ic_launcher.png
android/app/src/staging/res/mipmap-hdpi/ic_launcher.png
For iOS, use Xcode asset catalogs with AppIcon-dev, AppIcon-staging, AppIcon and set the correct one per scheme.
flutter_flavorizr handles icon setup if you provide source images in your pubspec.yaml flavorizr config.
Fastlane + flavors
# Fastfile
lane :deploy_staging do
build_flutter(
flavor: 'staging',
target: 'lib/main_staging.dart',
build_mode: 'release',
)
upload_to_testflight
end
Common pitfalls
Forgetting -t lib/main_flavor.dart. Running flutter run --flavor dev without specifying the target will run lib/main.dart with the dev flavor — which might initialize the production config. Always pair --flavor with -t.
All flavors sharing one Firebase project. Dev events in Firebase Analytics pollute production metrics. Always use separate Firebase projects per environment.
Not having a staging environment. Going directly from dev to production means the first time production config is tested is in front of real users. staging with production-equivalent config catches environment-specific issues before release.
Sign in to like, dislike, or report.