How to Set Up Flavors for a Flutter Application

Engineering

Flutter

Flavors

Summary

This guide explores setting up and managing app flavors in Flutter, useful for different development environments like Development and Production. It details the default method and introduces a streamlined approach using the Very Good CLI, integrating Stacked for state management.

Key insights:
  • Flavor Management in Flutter: This article explores the implementation of different versions of a Flutter application, known as flavors, to facilitate the management of multiple environments like development, staging, and production within a single codebase.

  • Two Methods for Flavor Setup: The guide details two methods for setting up flavors in Flutter. The first is the default method, involving manual configurations within the Flutter project. The second is a streamlined approach using the Very Good CLI, which simplifies the process and integrates with Stacked for state management.

  • Default Method Configuration: In the default setup, specific configurations are required for both Android and iOS platforms, including adjustments in the build.gradle file for Android and settings in Xcode for iOS, to manage different application identifiers and properties for each flavor.

  • Streamlined Method Using Very Good CLI: The streamlined method with the Very Good CLI automates much of the flavor setup, generating a project structure that is pre-configured for managing multiple flavors and can be appended easily to work with the Stacked state management framework.

  • Stacked Integration: Instructions are provided for adjusting the Very Good CLI-generated project to use Stacked instead of the default Bloc for state management, involving changes to the project's directory structure and main initialization files.

  • Practical Benefits: Utilizing flavors in Flutter development offers practical benefits such as environment-specific configurations, simplified testing, and streamlined deployments, enhancing overall development efficiency and maintaining consistency across different application versions.

Introduction

In mobile application development, managing multiple environments (such as development, staging, and production) is essential for testing, debugging, and releasing apps efficiently. Flutter supports creating different versions of your app - known as flavors. 

Flavors allow you to configure different environments with specific settings, resources, and API endpoints, all within a single codebase. This article aims to provide a detailed guide to creating and managing app flavors in Flutter.

In this article, we will explore two methods: the default way of setting up flavors in Flutter, and a streamlined hack that simplifies the process using the Very Good CLI. Furthermore, we provide instructions on how to make the relevant adjustments to use Stacked as the state management solution. 

By the end of this guide, you will have a deep understanding of how to implement and manage flavors in your Flutter project.

Flavors

1. Definition

Flavors refer to different versions of your application that can be configured to run in specific environments. Each flavor can have unique settings, assets, and configurations. This concept is particularly useful when you need to maintain multiple versions of your app, such as a development version with debugging tools enabled, a staging version for internal testing, and a production version for end users.

2. Importance of App Flavors

Environment-Specific Configurations: Flavors allow you to configure different API endpoints, keys, or UI elements depending on the environment.

Simplified Testing: By isolating environments, flavors help you test and debug specific configurations without interfering with the main production app.

Streamlined Deployment: Flavors enable automated deployment processes by ensuring that the correct settings are applied to each environment.

Setting Up App Flavors in Flutter: The Default Method

The default method of setting up flavors in Flutter involves configuring your project to support different build variants. This setup allows you to easily switch between environments with specific configurations for each flavor. This method is suitable for any state management solution as we manually configure each setting without relying on generated outcomes. 

1. Android Configuration

To begin, you will need to modify the android/app/build.gradle file to define your flavors. This file controls how your app is built on Android.

Find the android {} block and add the following flavorDimensions and productFlavors:

android {
    // Other configurations...

    flavorDimensions "default"

    productFlavors {
        development {
            dimension "default"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
            manifestPlaceholders = [appName: "[DEV] {app_name}"]
        }
        staging {
            dimension "default"
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            manifestPlaceholders = [appName: "[STG] {app_name}"]
        }
        production {
            dimension "default"
            applicationIdSuffix "" // No suffix for production
            manifestPlaceholders = [appName: "{app_name}"]
        }
    }
}

In the above configuration, the flavorDimensions is used to categorize flavors. Here, we are using a single dimension named “default.” The productFlavors defines each flavor (build variants). applicationIdSuffix adds the given suffix to the application ID. For example, if the application ID is “com.example.app”, the staging build would have the application ID “com.example.app.staging”. This allows you to install multiple flavors of the same app side-by-side on the same device. 

If you are following this tutorial, make sure to replace {app_name} in the manifestPlaceholders with your application name. manifestPlaceholders replaces the placeholder “{app_name}” in the AndroidManifest.xml file with a string that includes the version (for example, “STG {app_name}”. This is useful for visually distinguishing different versions of the app.

2. iOS Configuration

Setting up flavors for iOS requires a little more work. Begin by opening the ios directory in Xcode and following these steps:

1. In the project navigator, click on Runner.

2. Under the Info tab, you should see the configurations section where we can configure different flavors:

   

3. Click on the + sign and make two duplicates for each of the existing entries (Debug, Release, and Profile). These should be named {Debug/Release/Profile}-{Flavor}.

4. Add the suffix -production to the original three configurations. Now, you should have six configurations that look like this:

5. Go to the menu bar and select Product > Scheme > New Scheme. Create three Schemes named production, development, and staging

6. Assign each scheme to the corresponding build configuration by selecting Product > Scheme > Manage Schemes. Select a scheme and click on edit

Select the build configuration. For example, in staging, we select Debug-staging. Repeat this process for production and development, selecting the respective debug build configuration.

7. Each flavor requires a unique bundle identifier to differentiate it from other flavors. Select Runner under TARGETS, navigate to the Build Settings tab, and search for Product Bundle Identifier. For each build configuration, set up a unique bundle identifier by adding the respective suffix as shown below:

8. Navigate to Build Settings in Runner under PROJECT and search for Product Name. Here, change the product name for each flavor respectively. Make sure to change APP_NAME to your application name:

9. Set the Product Name in Runner under TARGETS to $(PRODUCT_NAME) to reflect the changes we made previously.

10. In the Info.plist file, change the values of the Bundle display name and Bundle name to $(PRODUCT_NAME):

Your app flavors for iOS should be ready to use now. However, make sure to set up signing and certificates for each flavor to avoid errors when building these projects. More information on signing certificates can be found in the official Flutter docs here.

To summarize the iOS flavors set up, you can refer to the checklist below:

3. Creating Bootstrap.dart

The bootstrap.dart file is essential for initializing your app with the correct flavor-specific settings. It centralizes the startup logic, making it easier to manage different configurations with minimal code repetitions. Create this file in a folder called main under the lib directory. 

Here’s an example implementation of bootstrap.dart:

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

void bootstrap({required Flavor flavor, required String baseUrl}) {
  // Initialize any flavor-specific services or configurations here
  WidgetsFlutterBinding.ensureInitialized();

  // Example: Initialize a service with the base URL
  // ApiService.initialize(baseUrl);

  runApp(MyApp(flavor: flavor));
}

4. Create Separate Main Files

For each flavor, you should have a separate main file that initializes the app with the appropriate settings. In the same main folder, create three files named main_production.dart, main_staging.dart, and main_development.dart. These files should utilize the bootstrap function created previously with varying arguments. For example, in main_development.dart, we might have:

import 'main/bootstrap.dart';

void main() {
  bootstrap(
    flavor: Flavor.development,
    baseUrl: const String.fromEnvironment(
      ‘BASE_URL’,
    ),
  );
}

Similarly, create main_staging.dart and main_production.dart with their respective setups. 

5. Running the Flavors

To build and run your flavors, you can use the following command:

For development, use flutter run --flavor development --target lib/main/main_development.dart --dart-define=BASE_URL=[enter development url here]

For staging, use flutter run --flavor staging --target lib/main/main_staging.dart --dart-define=BASE_URL=[enter staging url here]

For production, use flutter run --flavor production --target lib/main/main_production.dart --dart-define=BASE_URL=[enter production url here]

In the run commands above, the --flavor flag refers to the build configuration you are trying to run. If set up properly, this should automatically build and run the specified flavor on both Android and iOS. The --target flag determines the entry point of the application. This allows us to set up custom configurations for each flavor of the application. Lastly, the --dart-define flag passes custom run-time environment variables to your application. In this tutorial, we have kept it simple and utilized one environment variable (BASE_URL). To use multiple environment variables, you would just add additional lines to the command with the same format, starting with --dart-define followed by the variable name and its value. 

A Hack for Simplifying Flavor Setup: Using Stacked with Very Good CLI

If you have followed the article so far, you may have realized that the default process of setting up flavors for a Flutter application is quite tedious and prone to errors. To streamline this process, we will showcase a hack that leverages the Very Good CLI to automatically set up flavors. Furthermore, we provide instructions on how to make the necessary changes to use Stacked as the state management solution. 

1. Install Very Good CLI

First, install the Very Good CLI, a tool designed to generate Flutter projects with a clean architecture and best practices, by running the following command in your terminal: dart pub global activate very_good_cli

This CLI will help you generate a Flutter project with a pre-configured structure that includes flavor setup as well as localization:

2. Create a Flutter Project with Very Good CLI

Use the Very Good CLI to create a new Flutter project by running this command in the terminal: very_good create flutter_app {app_name}.

This command generates a new project with Bloc as the default state management solution. Steps 3 to 6 provide instructions on how to make the necessary changes to switch from Bloc to Stacked.

3. Modify pubspec.yaml for Stacked

Open the generated pubspec.yaml file and remove the Bloc dependencies. Then, add the required dependencies for stacked by running the following command: dart pub add stacked stacked_services dev:build_runner dev:stacked_generator. The pubspec.yaml file should contain the following dependencies if you have followed this step correctly:

dependencies:
 flutter:
   sdk: flutter
 flutter_localizations:
   sdk: flutter
 intl: ^0.19.0
 stacked: ^3.4.3
 stacked_services: ^1.5.0
dev_dependencies:
 build_runner: ^2.4.11
 flutter_test:
   sdk: flutter
 mocktail: ^1.0.4
 stacked_generator: ^1.6.1
 very_good_analysis

This setup includes the main Stacked package for state management, Stacked services for handling navigation, dialogs, and other services, and the Stacked generator for automatic code generation.

4. Rename Test Helpers

The Very Good CLI creates a test/helpers/helpers.dart file that might conflict with Stacked’s code generation. Rename this file to test_helpers.dart to avoid any issues. 

5. Configure stacked.json

In the project directory, add a file named stacked.json. This file contains settings that Stacked needs. For now, we will be using the default definition - however, this can be changed according to your preferences. The stacked.json file should look something like this:

{
    "bottom_sheets_path": "ui/bottom_sheets",
    "dialogs_path": "ui/dialogs",
    "line_length": 80,
    "locator_name": "locator",
    "prefer_web": false,
    "register_mocks_function": "registerServices",
    "services_path": "services",
    "stacked_app_file_path": "app/app.dart",
    "test_helpers_file_path": "helpers/test_helpers.dart",
    "test_services_path": "services",
    "test_views_path": "viewmodels",
    "test_widgets_path": "widget_models",
    "v1": false,
    "views_path": "ui/views",
    "widgets_path": "ui/widgets/common"
}

6. Configure `app.dart` for Stacked

In the lib/app/app.dart file, set up the Stacked configuration as follows:

import 'package:stacked/stacked_annotations.dart';

// @stacked-import

@StackedApp(
  routes: [
    // @stacked-route
  ],
  dependencies: [
    // @stacked-service
  ],
)
class App {}

This configuration tells Stacked where to insert the routes and services during code generation. 

7. Reorganize the Directory Structure

For better project organization, move the /lib/bootstrap.dart and the main files to a dedicated “main” directory within the lib folder:

This structure centralizes the flavor-specific entry points, making the project easier to navigate and maintain.

8. Customize bootstrap.dart

The bootstrap.dart file generated by Very Good CLI is tailored for Bloc. Remove the generated code and add a custom bootstrap process. A simple example of a bootstrap function that requires the current flavor and an additional baseUrl parameter is shown below:

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

void bootstrap({required Flavor flavor, required String baseUrl}) {
  // Initialize any flavor-specific services or configurations here
  WidgetsFlutterBinding.ensureInitialized();

  // Example: Initialize a service with the base URL
  // ApiService.initialize(baseUrl);

  runApp(MyApp(flavor: flavor));
}

9. Delegate Parameters to bootstrap

In each of the main files (e.g., main_development.dart), delegate the necessary parameters to the bootstrap function:

import 'main/bootstrap.dart';

void main() {
  bootstrap(
    flavor: Flavor.development,
    baseUrl: const String.fromEnvironment(
      'BASE_URL',
    ),
  );
}

This setup ensures that each flavor uses the appropriate configurations during runtime.

10. Running and Building Flavors with Very Good CLI

You can run and build flavors similarly to the default method. Another advantage of using Very Good CLI is that it automatically creates a detailed and visually appealing README file that contains instructions to run each flavor. However, you may need to edit these if you utilized environment variables and changed the directory structure as we did in this article:

# Development
$ flutter run --flavor development --target lib/main/main_development.dart --dart-define=BASE_URL=[BASE_URL_HERE]

# Staging
$ flutter run --flavor staging --target lib/main/main_staging.dart --dart-define=BASE_URL=[BASE_URL_HERE]

# Production
$ flutter run --flavor production --target lib/main/main_production.dart --dart-define=BASE_URL=[BASE_URL_HERE]

Additionally, if you are using VSCode, make sure to change the target files in launch.json under the .vscode directory.

If you have followed the steps, your flavors should be ready to use now. Using this approach allows for a more streamlined process of creating flavors as Very Good CLI automatically handles all manual work by setting up both Android and iOS according to the project. These changes can be observed if you inspect the android/app/build.gradle file and open the iOS directory in Xcode.

Conclusion

In conclusion, this article has demonstrated two methods for creating app flavors in Flutter: the default approach and an alternative using the Very Good CLI. Additionally, we provided detailed instructions on how to adjust the generated configuration from Very Good CLI to use Stacked instead of Bloc as the state management solution.

Transform Your App with Expert Flutter Consulting

Unlock the potential of your mobile applications with Walturn. We are now officially offering consulting services for Flutter development, providing expert guidance to bring your app ideas to life. Whether you're starting from scratch or need help refining your existing Flutter app, our industry-leading expertise at Walturn is here to support you every step of the way.

References

“Creating Flavors for Flutter.” Docs.flutter.dev, docs.flutter.dev/deployment/flavors. Accessed 19 Aug. 2024.

“Stacked CLI | the Production Flutter Framework.” Filledstacks.com, 2023, stacked.filledstacks.com/docs/tooling/stacked-cli.

Other Insights

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024