# 🍰 Simplifying flavor setup in the existing Flutter app: A comprehensive guide

Flavors are build configurations in Flutter apps that allow developers to create separate environments using the same code base. They allow for customization at runtime based on the defined compile-time configurations.

* Flavors allow efficient management of different development environments (development, staging, production)
    
* Enables creation of multiple versions of the same app (free and paid versions, separate environments for feature development)
    
* Simplifies setting different parameters (API endpoints, API keys, app icons) for each configuration
    
* Makes it easier to manage different app versions
    

> Apps created using the [`very_good_cli`](https://pub.dev/packages/very_good_cli) comes with three flavors - `development`, `staging`, and `production`.

---

# Adding Flavors to an Existing Flutter App

When it comes to adding flavors to an existing Flutter app, there are packages available that promise to automate the process, such as [`flutter_flavorizr`](https://pub.dev/packages/flutter_flavorizr).

It's common for these solutions to encounter issues when the app has already made extensive use of native plugins and custom configurations.

As a result, manually adding flavors to an app may be the best course of action. This process requires a bit of human touch, but it provides complete control over the configuration of your app's flavors.

---

# Setting Up Flavors 🛠️

To get a hands-on experience, we will be adding flavors to [**an existing app from here**](https://github.com/saileshbro/fitness_app_clone)

> We will be setting up `development`, `staging` and `production` flavors.

## Create Target Files 💻

We will start by creating three main files for each flavor, `main_<flavor>.dart` and create a `main` function.

We can replace the outdated `main.dart` file with our new entry points. In the `bootstrap.dart file`, we'll create a new function called `bootstrap` which will accept the environment from each entry point and use it to launch the app.

* `main_production.dart`
    

```dart
void main() => bootstrap('production');
```

* `main_development.dart`
    

```dart
void main() => bootstrap('development');
```

* `main_staging.dart`
    

```dart
void main() => bootstrap('staging');
```

* `bootstrap.dart`
    

```dart
void bootstrap(String env) => runApp(MyApp(env: env));

class MyApp extends StatelessWidget {
  const MyApp({super.key, required this.env});
  final String env;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      builder: (context, child) {
        if (env == 'production') return child!;
        return Banner(
          message: env.toUpperCase(),
          location: BannerLocation.topEnd,
          child: child,
        );
      },
      theme: ThemeData(
        fontFamily: 'Montserrat',
      ),
      home: const HomePage(),
    );
  }
}
```

Since setting up flavors in android is pretty straightforward, we will start with it so we can test it quickly.

## Using flavors in Android 🤖

To add `production`, `staging` and `development` flavors, let's make some changes in the app level `build.gradle` in `android/app/build.gradle` file.

Here, let's specify a `flavorDimensions` and a `productFlavors` object.

```apache
    flavorDimensions 'default'
    productFlavors {
        production {
            dimension 'default'
            resValue "string", "app_name", "FITNESS"
        }
        development {
            dimension 'default'
            applicationIdSuffix '.dev'
            versionNameSuffix '.dev'
            resValue "string", "app_name", "FITNESS.DEV"
        }

        staging {
            dimension 'default'
            applicationIdSuffix '.stg'
            versionNameSuffix '.stg'
            resValue "string", "app_name", "FITNESS.STG"
        }
    }
```

The `flavorDimensions` line defines the name of the flavor dimension, which is `default` in this case. Each flavor is defined within the `productFlavors` block. The `production` flavor is the base flavor of the app.

The `development` and `staging` flavors are similar, but with some differences. The `applicationIdSuffix` and `versionNameSuffix` lines add the `.dev` or `.stg` suffix to the application ID and version name, respectively. The `resValue` line sets the name of the app to `FITNESS.DEV` or `FITNESS.STG`, respectively.

> NOTE: Don't forget to add `android:label="@string/app_name"` to the `application` tag in `AndroidManifest.xml` file.

---

## Let's Test 🧪

Now, we can run the app using the following commands.

```bash
flutter run --flavor development --target lib/main_development.dart
flutter run --flavor staging --target lib/main_staging.dart
flutter run --flavor production --target lib/production.dart
```

Once done, you should have three different apps with different names, versions, and bundle identifiers.

![android flavors working](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685697832/2b172457-0674-4066-89dc-cb6c1755d472.png align="center")

---

## iOS Schemas

We will now be creating schemas for each flavor in Xcode. We already have a `Runner` schema, which is the default schema. We will rename it to `production` and create two new schemas, `development` and `staging`.

* Open the ios folder in Xcode.
    

```bash
open ios/Runner.xcworkspace
```

* Let's make some changes to our configuration.
    

![config-initial](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685767350/9193336b-1b16-4e9e-acde-158c3d3b3f1c.png align="center")

* In Project Runner, rename `Debug`, `Release`, and `Profile` adding `-production` suffixes to each.
    

![config-production](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685793314/58f491d2-f3dd-4795-bf5f-2f5229f1f616.png align="center")

* Once done, we will now create these 3 files for each `staging` and `development` as well. You can use duplicate.
    

![config-duplicate-button](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685814141/8854c543-13a8-4f36-b679-5f27b2dfcc3e.png align="center")

* Once done, we will have 9 configurations, 3 for each flavor.
    

![configuration](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685828283/2c1b695b-5aa4-40e1-a916-3c585dbb1796.png align="center")

* Select the Runner scheme and click the Manage Schemes button.
    

![manage-scheme-button](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685852673/f2be4c69-88ec-4bfa-aedd-257b327bd970.png align="center")

* Once, there rename `Runner` scheme to `production`, and duplicate `production` to two new `development` and `staging`.
    

![duplicate-button](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685906070/131c1a60-3d98-411e-9b86-5db4fba1d6b9.png align="center")

* Make sure to add the correct configuration while duplicating.
    

![staging-scheme](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685935904/cf4fe682-11b5-458c-88aa-50cd3360cf29.jpeg align="center")

Similarly, create a `development` scheme with the correct configuration.

![development-scheme](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685963735/70050902-c61c-4920-b9bd-71bd11905135.png align="center")

Once you are done with schemes, you will have something like this.

![final-schemes](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685978858/81dc9e70-2888-47f3-b86a-2cb5c37d8b74.png align="center")

> **NOTE**: Make sure all three schemes have correct configuration, don't forget to recheck `production` scheme configuration.

* Now, let's add the bundle identifier for each configuration. In `Target`\-&gt; `Runner`, click `Build Settings` and search for `Product Bundle Identifier`.
    

![bundle-identifier](https://cdn.hashnode.com/res/hashnode/image/upload/v1675685992723/f8b3b271-b6f6-49ef-93c7-14977e69f8ef.jpeg align="center")

> Add the suffix as required.

![final-bundle-ids](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686084412/1a9e836a-13fb-408b-9504-5032e7bf78b7.png align="center")

Once you are done, you will have something like this.

* Finally, we will create a new `User Defined Variable` called `APP_DISPLAY_NAME` which will have a different name for each configuration.
    

In same `Target` -&gt; `Runner` -&gt; `Build Settings`, click on the `+` button, and create a new user-defined variable.

![user_defined_variable](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686104706/8d88f4d4-2ccb-4723-bd53-12c030d5b47f.png align="center")

![APP_DISPLAY_NAME](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686120917/c0d571c2-5b53-49d0-86a3-a1bd0cdfd43f.png align="center")

Once, you are done with the user-defined variable, you need to change the `Info.plist` to use this variable.

```xml
<key>CFBundleName</key>
<string>$(APP_DISPLAY_NAME)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>
```

---

## Let's Test 🧪

Similar to android, we can test iOS by running the following commands.

```bash
flutter run --flavor development --target lib/main_development.dart
flutter run --flavor staging --target lib/main_staging.dart
flutter run --flavor production --target lib/production.dart
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675696750786/806598ad-33cc-4e3b-8c85-df0e578cf60b.png align="center")

---

## App Icons for Android and iOS 🎨

> 🎆 Let's make it look pretty!

Now, let's have different icons based on the flavors. We will have 3 different icons for each flavor.

![launch-icons](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686180483/ae0ed32b-1b2e-43c9-8d23-32194e23ad45.png align="center")

We will use [`flutter_launcher_icons`](https://pub.dev/packages/flutter_launcher_icons) to generate the launch icons. Install it as a dev dependency.

```bash
flutter pub add flutter_launcher_icons --dev
```

Once this is done, we will create `flutter_launcher_icons-<flavor>.yaml` files for each flavor.

* `flutter_launcher_icons-development.yaml`
    

```yaml
flutter_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icons/dev-icon.png"
```

* `flutter_launcher_icons-staging.yaml`
    

```yaml
flutter_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icons/stg-icon.png"
```

* `flutter_launcher_icons-production.yaml`
    

```yaml
flutter_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icons/prod-icon.png"
```

Now to generate the launch icons we need to run

```bash
flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons-*
```

This will generate all the icons.

Finally, we will set the icons to be dynamic in Xcode.

* In `Runner` -&gt; `Assets.xcassets` we can see that we have all the required icons. We can remove the `AppIcon` , which is unused.
    

![app-icons](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686204624/7b92cc30-14d2-46b1-8454-575049fa104d.png align="center")

* Now, in `Target`\-&gt; `Runner` -&gt; `Build Settings`, search for `primary app icon`.
    

![app-icon-change](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686216871/01802647-e968-4bdb-9cfa-0cec4f07e1fa.png align="center")

* Now, set the corresponding suffix to each one.
    

![final-app-icon](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686232157/77259f1f-7b3f-4bef-93c1-0b488881e650.png align="center")

---

## Final Result 🎉

Now, if we run the app, for all the flavors, we will have different icons, names, versions, and bundle identifiers.

> NOTE: Make sure to uninstall the previous builds before running the app.

![final-preview](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686316246/d5f7c534-ee4a-49dc-a990-bb1957ffbc8c.png align="center")

---

# 🎊 Bonus

For easy usage, we can create some launch configs for `Visual Studio Code` and `Android Studio`.

## For Visual Studio Code

Create a new file in `.vscode/launch.json` with the following content.

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "DEV | DEBUG",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_development.dart",
      "args": [
        "--flavor",
        "development",
        "--target",
        "lib/main_development.dart"
      ],
      "flutterMode": "debug"
    },
    {
      "name": "DEV | RELEASE",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_development.dart",
      "args": [
        "--flavor",
        "development",
        "--target",
        "lib/main_development.dart"
      ],
      "flutterMode": "release"
    },
    {
      "name": "DEV | PROFILE",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_development.dart",
      "args": [
        "--flavor",
        "development",
        "--target",
        "lib/main_development.dart"
      ],
      "flutterMode": "profile"
    },
    {
      "name": "STG | DEBUG",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_staging.dart",
      "args": [
        "--flavor",
        "staging",
        "--target",
        "lib/main_staging.dart"
      ],
      "flutterMode": "debug"
    },
    {
      "name": "STG | RELEASE",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_staging.dart",
      "args": [
        "--flavor",
        "staging",
        "--target",
        "lib/main_staging.dart"
      ],
      "flutterMode": "release"
    },
    {
      "name": "STG | PROFILE",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_staging.dart",
      "args": [
        "--flavor",
        "staging",
        "--target",
        "lib/main_staging.dart"
      ],
      "flutterMode": "profile"
    },
    {
      "name": "PROD | DEBUG",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_production.dart",
      "args": [
        "--flavor",
        "production",
        "--target",
        "lib/main_production.dart"
      ],
      "flutterMode": "debug"
    },
    {
      "name": "PROD | RELEASE",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_production.dart",
      "args": [
        "--flavor",
        "production",
        "--target",
        "lib/main_production.dart"
      ],
      "flutterMode": "release"
    },
    {
      "name": "PROD | PROFILE",
      "request": "launch",
      "type": "dart",
      "program": "lib/main_production.dart",
      "args": [
        "--flavor",
        "production",
        "--target",
        "lib/main_production.dart"
      ],
      "flutterMode": "profile"
    },
  ]
}
```

This will create the launch config with all the args.

![vs-code-launch-settings](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686338649/11fa8f57-66cb-4452-8ba2-d7f8dfa74b98.png align="center")

## For Android Studio

For Android Studio, you can get the [configuration files from here](https://github.com/saileshbro/fitness_app_clone/tree/master/.idea/runConfigurations), for all the configs.

![android-studio-launch-settings](https://cdn.hashnode.com/res/hashnode/image/upload/v1675686357238/a3b68a58-d26d-4eb6-bd59-c0ce6d6a42a5.png align="center")

---

🎉 Congrats! We've made it to the end of our exploration of Flutter Flavors! 🚀 We've successfully learned how to add different flavors to our flutter application, each with its own unique configurations and build targets.

It's just the tip of the iceberg when it comes to the use cases of flavors in Flutter. For instance, you can inject dependencies based on the flavor you are using, which can entirely change the behavior of your app. The possibilities are endless!

As always, you can refer back to the Github repository for this tutorial at [https://github.com/saileshbro/fitness\_app\_clone](https://github.com/saileshbro/fitness_app_clone) if you need any help. Don't forget to give it a ⭐️ and help spread the word about this amazing project. If you get stuck or need assistance, feel free to submit an issue or contribute a pull request.

Thank you for joining me on this journey; let's create even more amazing applications together. Have fun coding! 💻👨‍💻

---

## References

* [Flutter Flavors, App Icons, and Firebase Tutorial - Marcus Ng](https://www.youtube.com/watch?v=Vhm1Cv2uPko)
    
* [Creating flavors for Flutter](https://docs.flutter.dev/deployment/flavors)
    
* [Android Developers - Build Variants: Flavor Dimensions](https://developer.android.com/studio/build/build-variants#flavor-dimensions)
