You're building a Flutter app. Your client wants three versions: development for testing, staging for final checks, and production for real users. Each needs different settings—different API URLs, app names, even colors.
Your first thought might be: "I'll create three separate projects."
Don't. There's a better way.
What Are Flutter Flavors?
Flutter flavors let you build multiple versions of your app from one codebase. Think of it like a restaurant kitchen making different dishes from the same ingredients.
One codebase → Multiple app versions
Each flavor can have its own:
- App name and icon
- API endpoints
- Colors and branding
- Feature toggles
- Configuration settings
The Problem Flavors Solve
Without flavors, developers face these headaches:
Environment Mix-ups: Accidentally testing against production data, breaking real user experiences.
Manual Switching: Constantly changing API URLs and settings in code, leading to errors and messy commits.
Code Duplication: Maintaining separate codebases for different clients or environments.
Deployment Confusion: Forgetting which configuration is active before releasing.
The Solution in Action
With flavors, switching environments becomes effortless:
# Launch development version
flutter run --flavor dev
# staging version
flutter run --flavor staging
# production version
flutter run --flavor prod
Each command launches a completely different app configuration. Same code, different behavior.
Real Benefits You'll See Immediately
Faster Development: No more manually changing configurations. Switch environments with one command.
Fewer Bugs: Each environment is isolated. No more production accidents during development.
Easier Testing: QA team gets their own staging environment. Developers can experiment freely in dev.
Client Customization: Build white-label apps for multiple clients without duplicating code.
Setting Up Flavors: Choose Your Approach
Option 1: Manual Setup (Full Control)
Perfect when you need complete customization or want to understand the underlying mechanics.
Step 1: Configure Android Flavors
Create android/app/build.gradle
:
android {
flavorDimensions "default"
productFlavors {
dev {
dimension "default"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
resValue "string", "app_name", "MyApp Dev"
}
staging {
dimension "default"
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
resValue "string", "app_name", "MyApp Staging"
}
prod {
dimension "default"
resValue "string", "app_name", "MyApp"
}
}
}
Step 2: Configure iOS Flavors
Create build configurations in Xcode:
- Open
ios/Runner.xcworkspace
- Select Runner project → Info tab
- Duplicate existing configurations
- Create: Debug-dev, Debug-staging, Debug-prod, Release-dev, Release-staging, Release-prod
Step 3: Create Dart Configuration Files
Create lib/config/app_config.dart
:
class AppConfig {
static const String appName = String.fromEnvironment('APP_NAME');
static const String apiUrl = String.fromEnvironment('API_URL');
static const String flavor = String.fromEnvironment('FLAVOR');
static bool get isDevelopment => flavor == 'dev';
static bool get isStaging => flavor == 'staging';
static bool get isProduction => flavor == 'prod';
}
Create flavor-specific entry points:
lib/main_dev.dart
:
import 'main.dart' as app;
void main() {
app.main();
}
Option 2: Very Good CLI (Quick Setup)
Great for rapid prototyping and standard configurations.
# Install the CLI
dart pub global activate very_good_cli
# Create project with flavors pre-configured
very_good create flutter_app my_app
You get development, staging, and production flavors ready to use with proper native configurations.
Option 3: Flavorizr Package (Automated Setup)
Ideal for existing projects that need flavors added quickly.
# Add to pubspec.yaml
dev_dependencies:
flavorizr: ^2.2.1
# Create flavorizr.yaml configuration
flutter packages pub run flavorizr
Adding Custom Flavors
Need more than dev/staging/prod? Here's how to add custom flavors:
Adding a "Demo" Flavor
Update Android Configuration
Add to android/app/build.gradle
:
productFlavors {
// existing flavors...
demo {
dimension "default"
applicationIdSuffix ".demo"
versionNameSuffix "-demo"
resValue "string", "app_name", "MyApp Demo"
}
}
Update iOS Configuration
- In Xcode, duplicate existing configurations
- Create Debug-demo and Release-demo
- Update build settings for demo-specific values
Create Dart Entry Point
Create lib/main_demo.dart
:
import 'package:flutter/material.dart';
import 'main.dart' as app;
void main() {
const String.fromEnvironment('FLAVOR', defaultValue: 'demo');
app.main();
}
Update Launch Configuration
Create .vscode/launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "Demo",
"request": "launch",
"type": "dart",
"program": "lib/main_demo.dart",
"args": ["--flavor", "demo", "--dart-define", "FLAVOR=demo"]
}
]
}
Advanced Flavor Configurations
Environment-Specific Features
class FeatureFlags {
static bool get debugMode => AppConfig.isDevelopment;
static bool get analyticsEnabled => !AppConfig.isDevelopment;
static bool get crashReporting => AppConfig.isProduction;
}
Different Themes Per Flavor
class FlavorTheme {
static ThemeData get theme {
switch (AppConfig.flavor) {
case 'dev':
return ThemeData(primarySwatch: Colors.red);
case 'staging':
return ThemeData(primarySwatch: Colors.orange);
case 'demo':
return ThemeData(primarySwatch: Colors.purple);
default:
return ThemeData(primarySwatch: Colors.blue);
}
}
}
Client-Specific Configurations
class ClientConfig {
static const String clientName = String.fromEnvironment('CLIENT_NAME');
static const String brandColor = String.fromEnvironment('BRAND_COLOR');
static Color get primaryColor {
switch (clientName) {
case 'client_a':
return Colors.blue;
case 'client_b':
return Colors.green;
default:
return Colors.grey;
}
}
}
Running Your Flavored App
Command Line
# Development
flutter run --flavor dev --dart-define=FLAVOR=dev
# Staging
flutter run --flavor staging --dart-define=FLAVOR=staging
# Custom demo flavor
flutter run --flavor demo --dart-define=FLAVOR=demo
Building for Release
# Build production APK
flutter build apk --flavor prod --dart-define=FLAVOR=prod
# Build staging iOS
flutter build ios --flavor staging --dart-define=FLAVOR=staging
Best Practices
Keep Configurations Organized
lib/
├── config/
│ ├── app_config.dart
│ ├── dev_config.dart
│ ├── staging_config.dart
│ └── prod_config.dart
├── main.dart
├── main_dev.dart
├── main_staging.dart
└── main_prod.dart
Use Build-Time Constants
class AppConstants {
static const String apiUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'https://api.example.com',
);
static const bool enableLogging = bool.fromEnvironment(
'ENABLE_LOGGING',
defaultValue: false,
);
}
Version Control Considerations
Never commit sensitive data like API keys directly. Use environment variables:
class Secrets {
static const String apiKey = String.fromEnvironment('API_KEY');
static const String databaseUrl = String.fromEnvironment('DATABASE_URL');
}
Your Development Workflow Transformed
Before flavors:
- Change API URL in code
- Update app name manually
- Switch configuration files
- Hope you didn't miss anything
- Commit changes (cluttering git history)
With flavors:
- Run
flutter run --flavor dev
- Done.
Troubleshooting Common Issues
iOS Build Errors
Make sure all configurations exist in Xcode and schemes are properly configured.
Android Namespace Conflicts
Ensure each flavor has a unique applicationIdSuffix
.
Missing Dependencies
Some flavors might need specific dependencies. Use flavor-specific dependency injection.
Start Using Flavors Today
If you're managing multiple environments or building for different clients, flavors aren't optional—they're essential.
Set them up once, save time forever.
Your codebase stays clean. Your deployments become predictable. Your team stays focused on building features, not managing configurations.
Choose the setup method that works best for your project and team—whether that's manual configuration for maximum control, Very Good CLI for quick setup, or Flavorizr for existing projects.
Top comments (0)