Using Build Flavors for Different App Configurations in Flutter

In Flutter development, managing different versions or configurations of an application, such as a free and paid version, or development and production environments, can be a challenge. Build flavors provide a structured and maintainable solution. This guide explains how to effectively use build flavors in Flutter to create distinct configurations of your app with ease.

What are Build Flavors in Flutter?

Build flavors, also known as product flavors, are a way to create different builds of the same application from a single codebase. Each flavor can have unique features, resources, configurations, and build settings, making it ideal for different environments (development, staging, production), app tiers (free, premium), or branding needs.

Why Use Build Flavors?

  • Code Reusability: Maintain a single codebase for multiple app versions.
  • Configuration Management: Easily switch between different environments.
  • Customization: Tailor the app’s behavior and appearance for different user segments.
  • Maintainability: Reduce code duplication and improve long-term project health.

How to Implement Build Flavors in Flutter

To implement build flavors in Flutter, you need to modify both the android and ios parts of your Flutter project.

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

Open android/app/build.gradle and add flavorDimensions and productFlavors within the android block.

android {
    // ...
    flavorDimensions "default"
    productFlavors {
        dev {
            dimension "default"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
            resValue "string", "app_name", "MyApp Dev"
        }
        prod {
            dimension "default"
            applicationId "com.example.myapp"
            resValue "string", "app_name", "MyApp"
        }
    }
    // ...
}

In this configuration:

  • flavorDimensions specifies a common dimension for all flavors.
  • productFlavors defines each flavor, such as dev (development) and prod (production).
  • applicationIdSuffix adds a suffix to the app’s package name.
  • versionNameSuffix appends a suffix to the app version name.
  • resValue overrides resource values, allowing you to set different app names for each flavor.

Step 2: Configure Build Schemes in ios/Runner.xcodeproj/xcshareddata/xcschemes

Open your iOS project in Xcode, navigate to ios/Runner.xcodeproj/xcshareddata/xcschemes, and duplicate the existing scheme for each flavor.

  1. Right-click on the Runner scheme and select “Duplicate.”
  2. Rename the duplicated scheme to “Runner-dev” for the development flavor.
  3. Edit the “Runner-dev” scheme:
    • Under “Run,” set “Build Configuration” to “Debug.”
    • Under “Profile,” set “Build Configuration” to “Release.”
    • Under “Analyze,” set “Build Configuration” to “Debug.”
    • Under “Archive,” set “Build Configuration” to “Release.”
    • Add --flavor dev to the “Arguments Passed On Launch” in the “Run” and “Profile” sections.
  4. Repeat the process for the “prod” flavor if needed, setting the “Build Configuration” to “Release” and adding --flavor prod to the “Arguments Passed On Launch.”

Step 3: Access Build Flavors in Flutter Code

Use the flutter_config package to access build flavor configurations within your Flutter code. First, add the flutter_config dependency to your pubspec.yaml:

dependencies:
  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('Hello, ${FlutterConfig.get('API_URL')}!'),
      ),
    );
  }
}

Next, create environment-specific .env files (e.g., .env.dev, .env.prod) in your project root:

# .env.dev
APP_NAME=MyApp Dev
API_URL=https://dev.example.com
# .env.prod
APP_NAME=MyApp
API_URL=https://example.com

Update your pubspec.yaml to include the assets:

flutter:
  assets:
    - .env
    - .env.dev
    - .env.prod

Step 4: Building Flavored Apps

To build your app with a specific flavor, use the following command in the terminal:

flutter build apk --flavor dev -t lib/main_dev.dart

For iOS, specify the scheme when building:

flutter build ios --flavor dev --scheme Runner-dev

Advanced Usage

  • Flavor-Specific Sources: Use different entry points for each flavor (e.g., main_dev.dart, main_prod.dart).
  • Conditional Imports: Import different modules based on the current flavor.
  • Dynamic Configuration: Load different sets of configurations based on the build flavor.

Best Practices

  • Consistent Naming: Use a consistent naming convention for flavors.
  • Separate Configurations: Keep environment-specific configurations separate.
  • Automate Builds: Use CI/CD tools to automate the build process for different flavors.
  • Secure Secrets: Never hardcode sensitive information; use environment variables and secure storage.

Conclusion

Build flavors in Flutter provide a robust solution for managing multiple versions and configurations of your application. By following this comprehensive guide, you can efficiently customize your app for different environments, user segments, or branding needs, ensuring a scalable and maintainable codebase. Properly implementing build flavors streamlines development workflows, reduces code duplication, and enhances the overall quality of your Flutter applications.