Flutter Flavor: Separating Build Environments in Flutter Apps

When we are developing a large scale application inside a company with a team, the development phase goes around a well-defined process where the new feature goes through different stages, such as feature implementation by the development team (both front-end & back-end) and testing the feature by QA team to ensure the app is working flawlessly when it reaches to end-users. In such cases, there might be different back-end servers hosted with different URL’s which need to be implemented in the mobile app too to implement the new changes. If your app doesn’t contain a separate build configuration you need to manually change the server URL and build your app accordingly each time you debug or prepare for release.

In such scenarios, separating build environments with different build configurations can save you and your time providing an easy way to debug or prepare for release. So, in this article, we will learn how we can separate build environments in Flutter Apps using Flutter Flavor. It is commonly known as Flavors in Android and Schemes in iOS.

Getting started with Flutter Flavor

Before creating a Flavors for Flutter, let’s create a flutter project first. I have created a project with flavours_flutter. I will be adding three different build configurations i.e. dev, stage, and prod representing the development, staging and production environment.

App Configuration

Let us create a new file under our lib folder as flavour_config.dart which will hold the details about which flavor we are currently running.

Inside flavour_config.dart the file, we will create a enum name Environment to define the build configuration type, a set method to set the configuration and get method which will let us know which build configuration is being currently used. You can refer to the snippet below :

enum Environment {
  dev,
  stage,
  prod,
}

class Constants {
  static late Map<String, dynamic> _config;

  static void setEnvironment(Environment env) {
    switch (env) {
      case Environment.dev:
        _config = _Config.debugConstants;
        break;
      case Environment.stage:
        _config = _Config.stageConstants;
        break;
      case Environment.prod:
        _config = _Config.prodConstants;
        break;
    }
  }

  static String get whereAmI {
    return _config[_Config.flavour] as String;
  }
}

class _Config {
  static const flavour = 'flavour';

  static Map<String, dynamic> debugConstants = {
    flavour: 'dev',
  };

  static Map<String, dynamic> stageConstants = {
    flavour: 'stage',
  };

  static Map<String, dynamic> prodConstants = {
    flavour: 'prod',
  };
}

Now, let’s create three different files named as main_dev.dart, main_stage.dart and main_prod.dart which will contain our main method for individual build configuration.

Inside main_dev.dart the file we will use the set method defined under flavour_config.dart file to set the current build configuration to begin using. Refer to the snippet below :

void main() async {
  Constants.setEnvironment(Environment.dev);
  await initializeApp();
}

Similarly, under main_stage.dart and main_prod.dart file pass the parameter to setEnvironment the method as Environment. stage and Environment.prod respectively.

At last, we can use Constants.whereAmI method to find out which build configuration is our app using currently. I have used it to get the current configuration and an extension method that will return color and name based on flavor. You can find the code snippet below :


extension FlavourTypeExtension on String {
  Color getFlavourColor() {
    switch (this) {
      case 'dev':
        return Colors.yellow[800]!;
      case 'stage':
        return Colors.grey[600]!;
      case 'prod':
        return Colors.green[600]!;
      default:
        return Colors.blue[600]!;
    }
  }

  String getFlavourName() {
    switch (this) {
      case 'dev':
        return 'Development';
      case 'stage':
        return 'Staging';
      case 'prod':
        return 'Production';
      default:
        return 'Unknown';
    }
  }
}

Refer to the picture below about using it :

Generating App Icons :

Usually, we will use different app names to differentiate the variants of the app based on the build configuration, but in this article, we will also learn how to different app icons to make it more clear. There is an awesome tool that helps us to generate launcher icons for our apps easily and quickly, you can check to visit this site to access the tool. It has been developed by Roman Nurik . I have generated three different app icons for dev, stage and production. After you generate and download your desired icon you will get a folder containing the assets for iOS and android as shown below :

NOTE :

I have created the launcher icon for macos desktop app and web app, you can skip this option.

Android Flavors :

Now let’s continue with configurations of environment variants (a.k.a Flavors) on the Android platform.

In your project go to android > app > src the folder and create three folders named as dev, stage and prod.

Now, simply navigate to the folder where you have kept the downloaded launcher icon asset then from the android folder simply move the res folder to the respective folder you have just created. For instance, in my case I have kept the res folder containing my asset for dev configuration under the dev folder and so on.

Then under dev, stage and prod folder create another folder named as values and a file inside that folder named as strings.xml which will hold the app label or name for different build configurations.

For reference you can take a look at the picture attached below :

After that add the following line of code inside the strings.xml file :

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_name">{Your App Name}</string>
</resources>

Replace {Your App Name} with your desired name for your app for individual flavor. For example, I have kept [DEV] App , [STAGE] App and [PROD] App for dev, stage and prod respectively.

Now, let’s navigate to AndroidManifest.xml file, and under that let’s replace the value of android:label with @string/app_name . Changing this value will allow us to pick the individual app label or name that we have defiled under strings.xml the file for individual flavors.

Lastly, let’s define the flavor dimensions under the app level build.gradle file. For this go to android>app>build.gradle file, add the following code below the declaration of buildTypes :

flavorDimensions "default"
    productFlavors {
        dev {
            applicationIdSuffix ".dev"
            dimension "default"
        }
        stage {
            applicationIdSuffix ".stage"
            dimension "default"
        }
        prod {
            dimension "default"
        }
    }

And that’s all you need to do for setting up flavors in android. Now you can use the command below to build your app :

For building dev environment :

> flutter run --flavor dev -t lib/main_dev.dart

For building stage environment :

> flutter run --flavor stage -t lib/main_stage.dart

For building prod environment :

> flutter run --flavor prod -t lib/main_prod.dart

Here you can see three different apps with different names and icons installed on my device.

iOS Schemes :

Creating schemes for iOS is a bit tricky and confusing task, but in this article, we will learn how we can create different schemes for dev, stage and prod in an easy way.

Let’s get started, first open your project in Xcode and select the target Runner the file then creates three different schemes as shown below :

flutterguide-flutter-flavor
flutterguide-flutter-flavor

After that now select the project Runner file and you can now see the configuration option under the info tab, refer to the picture below :

There are three different pre-defined configuration available as Debug, Release, and Profile. Now what we need to do is click the + button just below Profile the option then you get a popup dialogue as :

Now, rename Debug copy, Release copy and Profile copy as Debug-dev, Release-dev and Profile-dev. Depending up-to how many build configurations you are setting up you need to repeat the same method. In my case, I have been setting up for dev, stage and prod, the final result was like this:

As we have duplicated the configurations now, we have to make sure that our individual schemes are connected with their respective configuration. You can take reference from below :

Above I have shown how I have done the configuration for the prod environment, similarly according to your types of build configuration you can copy those steps. For example for dev select release-dev, debug-dev and profile dev.

Finally, here we have our schemes connected with their respective configuration, now let’s continue with other customization for schemes. At first, let us change the app-bundle identifier. I will be keeping com.example.flavoursFlutter.dev, com.example.flavoursFlutter.stage and com.example.flavoursFlutter.prod for dev, stage and prod respectively. I will be showing the process for dev and you can copy the same steps for stage and prod. For the process look at the attached video below :

After that’s done we would like our app to display different names based on scheme, for that let first open the info.plist file and search for CFBundleName and replace the value for that key with $(APP_DISPLAY_NAME). I have attached a code snippet to show what has changed in my case please refer below :

// Previous data
 <key>CFBundleName</key>
 <string>flavours_flutter</string>

// Updated data
 <key>CFBundleDisplayName</key>
 <string>$(APP_DISPLAY_NAME)</string>

Now let’s jump back to Xcode and update the value for app name for which we need to add a user-defined setting as APP_DISPLAY_NAME. I will be keeping my app name as [DEV] App , [STAGE] App and [PROD] App for dev, stage and prod respectively as similar to what we have done for android.

NOTE

Repeat the same steps for profile and release too.

Now when you run your app you will be able to see a different app for dev, stage, and prod with a different name but we have one more task pending. As I have mentioned earlier and we have already added a separate launcher icon for android now it’s time for iOS. Let’s begin with that, for that make sure you have the generated app icon, if not please go to Generating App Icons section and follow the mentioned steps. After that you will have a folder called iOS inside the downloaded folder while generating the app icon which will look like :

Now let’s again jump back to Xcode adding these assets. after selecting the Assets option, right-click and select the iOS option which will create AppIcon-1, all you need to do is rename with a name for which scheme you are trying to add the launcher icon. For example, in my case, I have added for dev, stage, and prod so I have renamed it to AppIcon-dev, AppIcon-stage and AppIcon-prod respectively.

Now it’s time to drag and drop respective assets for the launcher icon as shown below :

And now we are almost done, for the final step let’s set the configuration which will pick the right icon asset for the right schemes while building an app. For this process check the attached image and gif :

Lastly, now build your app with the same command you used for android. The final result will look like this:

Bonus Tips :

For those who are using visual studio code it for debugging just copy and paste the following code snippet in your launch.json file.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "dev",
            "request": "launch",
            "type": "dart",
            "args": [
                "-t",
                "lib/main_dev.dart",
                "--flavor",
                "dev"
            ]
        },
        {
            "name": "stage",
            "request": "launch",
            "type": "dart",
            "args": [
                "-t",
                "lib/main_stage.dart",
                "--flavor",
                "stage"
            ]
        },
        {
            "name": "production",
            "request": "launch",
            "type": "dart",
            "args": [
                "-t",
                "lib/main_prod.dart",
                "--flavor",
                "prod"
            ]
        }
    ]
}

You can find the GitHub repository for this project here. I will be posting another article about integrating multiple firebase projects for separate build environments, until then keep reading awesome articles at Flutter Guide.

πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’» Thank You for reading this article, I hope you find it useful. πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»πŸ§‘πŸ»β€πŸ’»

Also Read: Flutter Version Management: Easiest Way To Manage Multiple Flutter SDK Version Through Simple CLI

Tech enthusiastic guy fluttering with dart. Busy with developing apps and websites but somehow manages to contribute to communities. Software Engineering Graduate πŸ‘¨β€πŸŽ“.