When developing Flutter applications, managing different environments and configurations can become a challenge. Different builds (e.g., development, staging, production) often require varying sets of parameters like API endpoints, feature flags, and logging levels. In Flutter, build configurations allow you to tailor your app for various deployment environments without altering the core codebase. This article dives into how you can effectively use different build configurations in Flutter to streamline your development workflow.
Understanding Build Configurations
Build configurations, also known as build flavors or schemes, provide a way to compile your application with different settings. This enables you to run and test different versions of your application without manually changing code. Key advantages include:
- Environment Management: Easily switch between development, staging, and production environments.
- Feature Toggling: Enable or disable features based on the environment.
- API Endpoint Flexibility: Use different API endpoints for testing and live versions.
- Improved Testing: Deploy test builds with specific logging or debug settings.
Setting Up Build Configurations in Flutter
Flutter uses flavors along with schemes and targets (on iOS) to achieve different build configurations. Let’s walk through setting this up for both Android and iOS platforms.
1. Configuring Android Build Flavors
Step 1: Modify build.gradle
Open your android/app/build.gradle file and locate the android block. Add the flavorDimensions and productFlavors blocks as shown:
android {
...
flavorDimensions "environment"
productFlavors {
dev {
dimension "environment"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
buildConfigField "String", "API_URL", ""https://dev.example.com/api""
}
prod {
dimension "environment"
buildConfigField "String", "API_URL", ""https://example.com/api""
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
}
Here’s what the configuration does:
flavorDimensions "environment": Defines the flavor dimension, grouping the flavors logically.productFlavors: Defines the individual flavors (devandprodin this case).applicationIdSuffixandversionNameSuffix: Adds a suffix to the application ID and version name for clarity.buildConfigField: Defines a build configuration fieldAPI_URLwith different values for each flavor.
Step 2: Access the Build Configuration
In your Flutter code, access the API_URL using:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:your_app_name/build_config.dart'; // Add this import
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Build Config',
home: Scaffold(
appBar: AppBar(
title: Text('API URL'),
),
body: Center(
child: Text(BuildConfig.API_URL), // Using the API_URL here
),
),
);
}
}
Note: If you’re unable to find `BuildConfig.API_URL`, regenerate the build configuration. You can do this by cleaning and rebuilding your project, or by syncing your project with Gradle files. A common resolution is to execute the following commands in your terminal:
flutter clean
flutter pub get
flutter build apk --flavor dev --target lib/main_dev.dart
Replace `dev` with your desired flavor and `lib/main_dev.dart` with the appropriate entry point if needed. If prompted by Android Studio, “Invalidate Caches / Restart…” to ensure all build configurations are properly recognized.
Step 3: Running the App
To run the app with a specific flavor, use the following command:
flutter run --flavor dev --target lib/main_dev.dart
Replace dev with the desired flavor and lib/main_dev.dart with the appropriate entry point if needed.
2. Configuring iOS Build Schemes
Step 1: Define Schemes in Xcode
Open your Flutter project’s ios/Runner.xcworkspace file in Xcode. Go to Product > Scheme > Edit Scheme...
Create new schemes by duplicating the existing “Runner” scheme. Name them something descriptive, like “Runner-Dev” and “Runner-Prod.”

Step 2: Set Preprocessor Definitions
For each scheme:
- Select “Run” in the left panel.
- Go to the “Arguments” tab.
- In the “Arguments Passed On Launch” section, add
--dart-define=ENVIRONMENT=devfor the “Runner-Dev” scheme and--dart-define=ENVIRONMENT=prodfor the “Runner-Prod” scheme.

Step 3: Access the Scheme in Flutter
In your Flutter code, retrieve the environment variable using String.fromEnvironment:
const String environment = String.fromEnvironment('ENVIRONMENT', defaultValue: 'prod');
void main() {
String apiUrl;
if (environment == 'dev') {
apiUrl = 'https://dev.example.com/api';
} else {
apiUrl = 'https://example.com/api';
}
runApp(MyApp(apiUrl: apiUrl));
}
class MyApp extends StatelessWidget {
final String apiUrl;
MyApp({required this.apiUrl});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Build Config',
home: Scaffold(
appBar: AppBar(
title: Text('API URL'),
),
body: Center(
child: Text('API URL: $apiUrl'),
),
),
);
}
}
Step 4: Run the App
Select the desired scheme in Xcode and run your app.
Using Command Line with iOS
You can also build for iOS from the command line using flutter build ios or flutter run, and passing the scheme as a parameter. You’ll typically use this approach in CI/CD scenarios. Here’s an example:
flutter run --flavor dev --target lib/main_dev.dart -t ios/Runner/lib/main_dev.dart
Advanced Configuration Techniques
1. Environment-Specific Assets
You may need different assets (images, configuration files) for different environments. Organize them into directories that correspond to your flavors and load them conditionally.
String getAssetPath(String assetName) {
if (environment == 'dev') {
return 'assets/dev/$assetName';
} else {
return 'assets/prod/$assetName';
}
}
2. Feature Flags
Implement feature flags to enable or disable features based on the build configuration. This is useful for testing new features in a staging environment.
bool isFeatureEnabled() {
return environment == 'dev'; // Enable feature only in the dev environment
}
3. Using .env Files
Consider using .env files to manage environment variables, especially sensitive information like API keys. Packages like flutter_dotenv simplify loading environment variables from a .env file.
dependencies:
flutter_dotenv: ^5.1.0
import 'package:flutter_dotenv/flutter_dotenv.dart';
void main() async {
await dotenv.load(fileName: ".env");
String apiKey = dotenv.env['API_KEY'] ?? 'default_api_key';
runApp(MyApp(apiKey: apiKey));
}
Conclusion
Effectively using different build configurations in Flutter enhances your app development workflow by making it easier to manage environments, toggle features, and use different API endpoints. By following the steps outlined in this guide, you can set up Android flavors and iOS schemes to tailor your app for development, staging, and production environments. Using environment variables and feature flags further streamlines this process, enabling a robust and efficient development cycle.