
Flutter Flavors: How to Configure Dev, Staging, and Production Builds
In modern app development, managing different environments is a core requirement. In Flutter, a flavor is the standard way to define multiple versions of your application from a single codebase. Each flavor can have its own configuration, environment variables, app name, package identifiers, icons, and API endpoints.
Using flavors is critical when you need:
- Development (Dev): Connected to sandbox APIs with verbose logging.
- Staging: For QA testing in an environment that mimics production.
- Production (Prod): The stable version released to real users.
Think of flavors as "variants" or "schemes" that allow you to switch environments seamlessly without manual code changes.
Why Use Flavors Instead of hardcoding?
Hardcoding URLs and keys is error-prone and insecure. Flavors provide a clean separation of concerns, ensuring that development data never leaks into production. This is especially important when integrating with services like Firebase, where you often want separate projects for each environment.
🛠️ Step-by-Step Implementation Guide
Setting up flavors involves both Dart-side configuration and platform-specific (Android/iOS) setup.
1. Create Config Files
Create a folder like lib/config/ and inside it, add separate Dart files for each flavor:
lib/config/dev_config.dart
class Config {
static const String appName = "MyApp Dev";
static const String apiBaseUrl = "https://dev-api.example.com";
static const bool enableLogging = true;
}lib/config/staging_config.dart
class Config {
static const String appName = "MyApp Staging";
static const String apiBaseUrl = "https://staging-api.example.com";
static const bool enableLogging = true;
}lib/config/prod_config.dart
class Config {
static const String appName = "MyApp";
static const String apiBaseUrl = "https://api.example.com";
static const bool enableLogging = false;
}2. Create Entry Points for Each Flavor
You can have separate main.dart files for each flavor:
lib/main_dev.dart
import 'package:flutter/material.dart';
import 'config/dev_config.dart' as config;
import 'app.dart';
void main() {
runApp(App(config: config.Config()));
}lib/main_staging.dart
import 'package:flutter/material.dart';
import 'config/staging_config.dart' as config;
import 'app.dart';
void main() {
runApp(App(config: config.Config()));
}lib/main_prod.dart
import 'package:flutter/material.dart';
import 'config/prod_config.dart' as config;
import 'app.dart';
void main() {
runApp(App(config: config.Config()));
}3. Use Config in Your App
lib/app.dart
import 'package:flutter/material.dart';
class App extends StatelessWidget {
final dynamic config;
const App({super.key, required this.config});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: config.appName,
home: Scaffold(
appBar: AppBar(title: Text(config.appName)),
body: Center(
child: Text('API Base URL: ${config.apiBaseUrl}'),
),
),
);
}
}4. Running a Specific Flavor
Use the following commands to run or build your app with a specific flavor:
# Run Development
flutter run --flavor dev -t lib/main_dev.dart
# Run Staging
flutter run --flavor staging -t lib/main_staging.dart
# Run Production
flutter run --flavor prod -t lib/main_prod.dart🤖 Setup in Android (build.gradle)
On Android, we use Product Flavors. Open android/app/build.gradle and add:
android {
...
flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationId "com.example.myapp.dev"
resValue "string", "app_name", "MyApp Dev"
}
staging {
dimension "env"
applicationId "com.example.myapp.staging"
resValue "string", "app_name", "MyApp Staging"
}
prod {
dimension "env"
applicationId "com.example.myapp"
resValue "string", "app_name", "MyApp"
}
}
}🍎 Setup in iOS (Xcode)
For iOS, you need to create Custom Schemes:
- Open your project in Xcode.
- Go to Product > Scheme > Manage Schemes.
- Duplicate the default scheme and name them
dev,staging, andprod. - Create corresponding Build Configurations in Project Settings.
💡 Best Practices for Flutter Flavors
- Combine with Build Modes: Flavors define where the app runs (Dev vs Prod), while Build Modes define how it runs (Debug vs Release).
- Use .env Files: For sensitive keys, consider using
flutter_dotenvalongside flavors. - CI/CD Integration: Automate your builds using the
--flavorflag in your pipelines.
Conclusion
Implementing flavors early in your project lifecycle saves significant time and prevents deployment errors. It allows you to maintain a professional development workflow that scales with your team.
For more advanced configuration details, check out the Official Flutter Documentation on Flavors.
Happy Coding! 🚀