Flavor Architecture
Config JSON Structure
- Store per-flavor JSON config files in a
config/ directory at project root:config/
βββ dev.json
βββ staging.json
βββ prod.json
- Example
config/dev.json:{
"FLAVOR": "dev",
"BASE_URL": "https://api-dev.example.com",
"APP_NAME": "MyApp Dev",
"ENABLE_LOGGING": "true",
"ENABLE_CRASHLYTICS": "false"
}
- NEVER put secrets (API keys, signing credentials) in these JSON files β use CI-injected env vars or
flutter_secure_storage
- Add
config/*.json to .gitignore if they contain any environment-specific secrets; otherwise commit them for team convenience
Entry Point Pattern
// lib/main.dart
void main() {
const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'dev');
AppConfig.init(flavor: Flavor.fromString(flavor));
runApp(const App());
}
- Read all compile-time values via
String.fromEnvironment('KEY') or bool.fromEnvironment('KEY')
- Use a sealed
Flavor enum: sealed class Flavor { dev, staging, prod }
AppConfig is a singleton holding flavor, base URL, feature flags, and Firebase config
Environment Configuration
- Store per-flavor config in a centralized
AppConfig class:
baseUrl β API endpoint per environment
enableLogging β verbose logging for dev only
enableCrashlytics β disabled in dev
appName β display name per flavor (e.g., βMyApp Devβ, βMyAppβ)
- All values come from the JSON file via
--dart-define-from-file β NO hardcoded per-flavor logic in Dart code
- NEVER put secrets in the JSON config files β use
flutter_secure_storage or CI-injected env vars
Android
- Define
flavorDimensions and productFlavors in android/app/build.gradle:flavorDimensions "environment"
productFlavors {
dev { dimension "environment"; applicationIdSuffix ".dev"; resValue "string", "app_name", "MyApp Dev" }
staging { dimension "environment"; applicationIdSuffix ".staging"; resValue "string", "app_name", "MyApp Staging" }
prod { dimension "environment"; resValue "string", "app_name", "MyApp" }
}
iOS
- Create Xcode schemes for each flavor (Dev, Staging, Prod).
- Use xcconfig files for per-flavor bundle ID, display name, and signing.
- Map Flutter flavors to Xcode schemes in
ios/Runner.xcodeproj.
- Swift Package Manager Integration: When configuring iOS flavors, Xcode schemes map package dependency configurations automatically. No CocoaPods
Podfile changes are required for SPM. Ensure all dependency overrides are defined under Package Dependencies inside Xcode Runner project settings.
Firebase Per-Flavor
- Use separate Firebase projects per flavor (dev, staging, prod)
- Place
google-services.json (Android) and GoogleService-Info.plist (iOS) in flavor-specific directories
- Use
flutterfire configure with --project flag for each environment
Build Commands
# Development
flutter run --flavor dev --dart-define-from-file=config/dev.json
# Staging
flutter run --flavor staging --dart-define-from-file=config/staging.json
# Production release
flutter build appbundle --flavor prod --dart-define-from-file=config/prod.json --release
flutter build ipa --flavor prod --dart-define-from-file=config/prod.json --release
Rules
- NEVER hardcode environment-specific values (URLs, API keys, feature flags)
- NEVER commit production secrets to the repository
- Every team member MUST be able to run any flavor locally with a single command
- CI/CD pipelines MUST specify both
--flavor and --dart-define-from-file explicitly
- Use a single
main.dart β NEVER create separate entry points per flavor