Utilizing Build Flavors and Environments in Flutter

In Flutter development, managing different configurations for various environments such as development, staging, and production is a common requirement. Build flavors and build configurations come to the rescue, allowing you to tailor your app’s behavior, assets, and settings based on the environment it’s being built for. This article dives deep into how to effectively utilize build flavors and environments in Flutter.

What are Build Flavors in Flutter?

Build flavors are different versions of your app built from the same codebase. They are particularly useful when you want to have multiple variants of your app, such as a free version with limited features and a paid version with full access. In Flutter, build flavors are set up using Flutter’s build configuration system.

Why Use Build Flavors?

  • Environment-Specific Settings: Tailor configurations (API endpoints, app names, bundle IDs) for different environments (dev, staging, prod).
  • Feature Toggling: Enable or disable specific features based on the build flavor.
  • Multiple Branding: Support multiple brands or themes within a single codebase.
  • Simplified Testing: Streamline testing on different environments by pre-configuring settings.

How to Implement Build Flavors in Flutter

Step 1: Configure Flavors in flutter_config.yaml

Create or modify the flutter_config.yaml file in the root of your Flutter project to define your flavors. This file allows you to manage different build configurations for different environments.


flavors:
  development:
    app_name: "My App Dev"
    api_url: "https://dev.example.com/api"
  staging:
    app_name: "My App Staging"
    api_url: "https://staging.example.com/api"
  production:
    app_name: "My App"
    api_url: "https://example.com/api"

In this example, we define three flavors: development, staging, and production. Each flavor has an app_name and an api_url.

Step 2: Install the flutter_config Package

Add the flutter_config package to your pubspec.yaml file.


dependencies:
  flutter_config: ^2.0.0

Then run flutter pub get to install the package.

Step 3: Access Flavor-Specific Values in Code

Use the FlutterConfig class to access your flavor-specific values.


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 4: Run Your App with a Specific Flavor

To run your app with a specific flavor, use the --flavor option in the flutter run command.


flutter run --flavor development

Step 5: Define Build Configurations in Gradle (Android) or Xcode (iOS)

For Android, modify your build.gradle file to include different build configurations for each flavor. Here’s an example:


android {
    flavorDimensions "default"
    productFlavors {
        development {
            dimension "default"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
            resValue "string", "app_name", "My App Dev"
        }
        staging {
            dimension "default"
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            resValue "string", "app_name", "My App Staging"
        }
        production {
            dimension "default"
            resValue "string", "app_name", "My App"
        }
    }
}

For iOS, you can configure flavors using Xcode schemes. Duplicate your existing scheme and modify it for each flavor.

Managing Environments in Flutter

Besides flavors, managing environments involves using environment variables to configure different aspects of your app at runtime.

Step 1: Using Environment Variables

Add a .env file for each environment in your project root.


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

# .env.staging
APP_NAME=My App Staging
API_URL=https://staging.example.com/api

# .env.production
APP_NAME=My App
API_URL=https://example.com/api

Step 2: Loading Environment Variables

Use the flutter_dotenv package to load environment variables into your app. Add the package to your pubspec.yaml:


dependencies:
  flutter_dotenv: ^5.0.2

Then, load the environment variables in your main.dart file:


import 'package:flutter_dotenv/flutter_dotenv.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load(fileName: ".env.development"); // Load .env file

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: dotenv.env['APP_NAME'] ?? 'My App',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(dotenv.env['APP_NAME'] ?? 'My App'),
      ),
      body: Center(
        child: Text('API URL: ${dotenv.env['API_URL'] ?? 'N/A'}'),
      ),
    );
  }
}

Step 3: Setting up different entry points

Create different entry point dart files, such as main_dev.dart, main_stage.dart and main_prod.dart files, and have flavor dependent env files loading


//main_dev.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';

Future main() async {
  await dotenv.load(fileName: ".env.development"); // Load .env file
    runApp(MyApp());
}

Step 4: Build command with entry points and flavors

Run command for a specific entrypoint


flutter run -t main_dev.dart  --flavor development

Advanced Tips and Best Practices

  • CI/CD Integration: Automate builds for different flavors using CI/CD tools like Jenkins, GitLab CI, or GitHub Actions.
  • Secure Secrets: Store sensitive information (API keys, passwords) securely using tools like HashiCorp Vault or cloud provider secrets managers.
  • Dynamic Configuration: Use remote configuration services (Firebase Remote Config) for dynamic settings updates without requiring app updates.
  • Version Control: Manage your configuration files carefully with version control, and ensure sensitive data is never committed to the repository.

Conclusion

Utilizing build flavors and environments in Flutter is essential for managing different configurations across development, staging, and production. By setting up flavors with Flutter’s build configuration system and leveraging environment variables with packages like flutter_dotenv, you can efficiently tailor your app’s behavior and settings for each environment. Properly managing flavors and environments leads to more maintainable, scalable, and robust Flutter applications.