Setting up Flavors/Schemes for Different Mobile App Versions
One repository, multiple applications. A white-label product for three clients. Free and paid versions with different packages and icons. A staging build for QA with a different bundle ID that can be installed alongside the production version. These are all tasks for Product Flavors (Android) and Schemes/Targets (iOS).
Android: Product Flavors
Product Flavors in Android create separate app variants with different applicationId, resources, code, and dependencies.
android {
flavorDimensions += listOf("environment", "tier")
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
resValue("string", "app_name", "MyApp Dev")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
resValue("string", "app_name", "MyApp Staging")
}
create("production") {
dimension = "environment"
}
create("free") {
dimension = "tier"
applicationIdSuffix = ".free"
}
create("paid") {
dimension = "tier"
}
}
}
The flavor matrix creates combinations like devFreeDebug, devPaidRelease, productionFreeRelease, etc. Typically only the necessary combinations are used, with others filtered via variantFilter.
Flavor-specific resources are stored in src/dev/res/, src/staging/res/, src/production/res/. Kotlin code specific to a flavor goes in src/dev/kotlin/, etc.
iOS: Targets and Schemes
In iOS, Flavors are achieved through a combination of Targets + Schemes + xcconfig.
One target, multiple Schemes + Build Configurations — for staging/production scenarios:
- Create configurations: Debug, Staging, Release
- Create Schemes: MyApp, MyApp-Staging
- Each Scheme runs the appropriate Configuration
Multiple Targets — for white-label or significantly different versions (different code, different capabilities):
- Each Target has its own
PRODUCT_BUNDLE_IDENTIFIER, icon, Info.plist - Shared code → shared framework or common files added to both Targets
- More complex to maintain, but provides maximum control
Targets:
MyApp → com.myapp.ios → AppIcon → Release.xcconfig
MyApp-ClientB → com.clientb.myapp → AppIconClientB → ClientB.xcconfig
MyApp-Staging → com.myapp.ios.staging → AppIconStaging → Staging.xcconfig
Flutter: Flavors
Flutter supports flavors natively, mapping them to Android Product Flavors and iOS Schemes under the hood:
flutter run --flavor staging -t lib/main_staging.dart
flutter build apk --flavor production -t lib/main_production.dart
main_staging.dart initializes the app with staging configuration, main_production.dart with production. Configuration is passed via --dart-define or through a separate app_config.dart for each flavor.
With flutter_flavorizr in pubspec.yaml, you can generate boilerplate — it creates the necessary Targets/Schemes on iOS and Product Flavors on Android from a single config file.
Common Mistakes
- Adding a new flavor without updating CI — the pipeline only builds old variants
- Duplicating code instead of using
src/<flavor>/overlay — maintainability suffers - Using different
versionCode/CFBundleVersionfor different flavors of the same release — causes testing confusion
Process
Audit flavor requirements → choose approach (Flavors vs Targets) → create resource structure → configure CI to build needed variants → test side-by-side installation → document.
Timeline: 2–3 days for a standard setup, up to 5 days for white-label with multiple brands. Cost is calculated individually.







