Utilizing Build Flavors to Manage Different Configurations of Your Flutter App

In Flutter development, managing different versions or configurations of your app can become complex, especially when dealing with varying APIs, branding, or features for different environments (like development, staging, and production). Build flavors are a powerful way to handle these scenarios. This blog post will guide you through utilizing build flavors in Flutter to manage different configurations of your app efficiently.

What are Build Flavors?

Build flavors allow you to create different versions of your Flutter app from the same codebase. Each flavor can have its own set of configurations, such as different app icons, names, API endpoints, and features. This is especially useful when you need to manage multiple environments (e.g., development, staging, production) or offer different versions of your app with distinct branding.

Why Use Build Flavors?

  • Configuration Management: Easily manage different configurations for different environments.
  • Code Reusability: Maintain a single codebase while creating multiple versions.
  • Branding: Customize app branding (e.g., app icons, names) for each version.
  • Feature Toggles: Enable or disable specific features based on the build flavor.

How to Implement Build Flavors in Flutter

Here’s a step-by-step guide on implementing build flavors in your Flutter project:

Step 1: Configure Build Flavors in android/app/build.gradle

Open the android/app/build.gradle file and add the flavorDimensions and productFlavors blocks inside the android block.

android {
    ...
    flavorDimensions "environment"

    productFlavors {
        dev {
            dimension "environment"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
            resValue "string", "app_name", "My App Dev"
            buildConfigField "String", "API_URL", ""https://dev.example.com/api/""
        }
        staging {
            dimension "environment"
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            resValue "string", "app_name", "My App Staging"
            buildConfigField "String", "API_URL", ""https://staging.example.com/api/""
        }
        prod {
            dimension "environment"
            applicationIdSuffix ".prod"
            resValue "string", "app_name", "My App"
            buildConfigField "String", "API_URL", ""https://example.com/api/""
        }
    }
    ...
}

In this example:

  • flavorDimensions "environment" defines a flavor dimension named “environment.”
  • dev, staging, and prod are the product flavors representing the development, staging, and production environments.
  • applicationIdSuffix appends a suffix to the application ID.
  • versionNameSuffix adds a suffix to the version name.
  • resValue sets a string resource value (app name).
  • buildConfigField defines a build configuration field (API URL).

Step 2: Access Build Configuration Fields in Dart Code

To access the build configuration fields in your Dart code, you need to add the flutter_config dependency to your pubspec.yaml file.

dependencies:
  flutter:
    sdk: flutter
  flutter_config: ^2.0.0

Then, initialize FlutterConfig in your main.dart file.

import 'package:flutter/material.dart';
import 'package:flutter_config/flutter_config.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await FlutterConfig.loadEnvVariables();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: FlutterConfig.get('APP_NAME'),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(FlutterConfig.get('APP_NAME')),
      ),
      body: Center(
        child: Text('API URL: ${FlutterConfig.get('API_URL')}'),
      ),
    );
  }
}

Step 3: Configure iOS Flavors

For iOS, you need to configure schemes and build configurations in Xcode. Open ios/Runner.xcworkspace with Xcode.

Step 3.1: Add Build Configurations
  1. Go to Project > Runner > Info > Configurations.
  2. Duplicate the Debug and Release configurations for each flavor (e.g., Debug-dev, Release-dev, Debug-staging, Release-staging, etc.).
Step 3.2: Add Schemes
  1. Go to Product > Scheme > New Scheme.
  2. Name the scheme after your flavor (e.g., dev, staging).
  3. Edit the scheme and configure the Build, Run, Test, and Profile actions to use the appropriate build configurations (e.g., Debug-dev, Release-dev).
Step 3.3: Define Preprocessor Definitions

In the Build Settings for each configuration, define preprocessor definitions:

DEV=1
STAGING=1
PROD=1
Step 3.4: Add User-Defined Settings

Also in Build Settings, add user-defined settings:

APP_NAME = My App Dev
API_URL = https://dev.example.com/api/

Step 4: Access Flavors in iOS Dart Code

To access environment variables in Dart code for iOS, use the dart-define flag. This requires configuring build scripts to inject these variables at compile time.

Install CocoaPods dependencies with flutter pub get then configure Build Phases.

  1. Go to Target > Runner > Build Phases.
  2. Click + > New Run Script Phase

Run script with configuration to insert the API_URL variable. This solution does not need a plugin dependency because API_URL variable is included when the Flutter build occurs.

    echo "API_URL: $API_URL"
    /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME" "${INFOPLIST_FILE}"

    echo "Running flutter build..."
    flutter build ios --release 
        --build-name=1.0.0 
        --build-number=1 
        --dart-define=API_URL=$API_URL

Now get env variables via the code:


  String apiUrl = const String.fromEnvironment('API_URL', defaultValue: 'https://example.com');
  Text('API URL: $apiUrl'),

Step 5: Run Your App with Different Flavors

To run your Flutter app with a specific flavor, use the following commands:

For Android:
flutter run --flavor dev --target lib/main_dev.dart
flutter run --flavor staging --target lib/main_staging.dart
flutter run --flavor prod --target lib/main_prod.dart
For iOS:
flutter run --flavor dev --target lib/main_dev.dart --scheme dev
flutter run --flavor staging --target lib/main_staging.dart --scheme staging
flutter run --flavor prod --target lib/main_prod.dart --scheme prod

Advanced Usage and Tips

  • Separate Main Files: Use separate main.dart files (e.g., main_dev.dart, main_staging.dart) to initialize flavor-specific configurations.
  • Conditional Logic: Use conditional logic in your Dart code based on the build flavor.
  • Environment Variables: Utilize environment variables for sensitive information like API keys.

Conclusion

Build flavors are an essential tool for managing different configurations of your Flutter app efficiently. By configuring build flavors for Android and iOS, you can maintain a single codebase while creating multiple versions with distinct configurations, branding, and features. This approach simplifies development, testing, and deployment, making your Flutter projects more manageable and scalable.