DEV Community

Sailesh Dahal
Sailesh Dahal

Posted on • Originally published at saileshdahal.com.np on

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

cover

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 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.

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

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
void main() => bootstrap('production');
Enter fullscreen mode Exit fullscreen mode
  • main_development.dart
void main() => bootstrap('development');
Enter fullscreen mode Exit fullscreen mode
  • main_staging.dart
void main() => bootstrap('staging');
Enter fullscreen mode Exit fullscreen mode
  • bootstrap.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(),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

    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"
        }
    }
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

android flavors working


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.
open ios/Runner.xcworkspace
Enter fullscreen mode Exit fullscreen mode
  • Let's make some changes to our configuration.

config-initial

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

config-production

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

config-duplicate-button

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

configuration

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

manage-scheme-button

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

duplicate-button

  • Make sure to add the correct configuration while duplicating.

staging-scheme

Similarly, create a development scheme with the correct configuration.

development-scheme

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

final-schemes

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-> Runner, click Build Settings and search for Product Bundle Identifier.

bundle-identifier

Add the suffix as required.

final-bundle-ids

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 -> Runner -> Build Settings, click on the + button, and create a new user-defined variable.

user_defined_variable

APP_DISPLAY_NAME

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

<key>CFBundleName</key>
<string>$(APP_DISPLAY_NAME)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>
Enter fullscreen mode Exit fullscreen mode

Let's Test πŸ§ͺ

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

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
Enter fullscreen mode Exit fullscreen mode

ios-successful


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

We will use flutter_launcher_icons to generate the launch icons. Install it as a dev dependency.

flutter pub add flutter_launcher_icons --dev
Enter fullscreen mode Exit fullscreen mode

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

  • flutter_launcher_icons-development.yaml
flutter_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icons/dev-icon.png"
Enter fullscreen mode Exit fullscreen mode
  • flutter_launcher_icons-staging.yaml
flutter_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icons/stg-icon.png"
Enter fullscreen mode Exit fullscreen mode
  • flutter_launcher_icons-production.yaml
flutter_icons:
  android: true
  ios: true
  remove_alpha_ios: true
  image_path: "assets/icons/prod-icon.png"
Enter fullscreen mode Exit fullscreen mode

Now to generate the launch icons we need to run

flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons-*
Enter fullscreen mode Exit fullscreen mode

This will generate all the icons.

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

  • In Runner -> Assets.xcassets we can see that we have all the required icons. We can remove the AppIcon , which is unused.

app-icons

  • Now, in Target-> Runner -> Build Settings, search for primary app icon.

app-icon-change

  • Now, set the corresponding suffix to each one.

final-app-icon


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


🎊 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.

{
  "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"
    },
  ]
}
Enter fullscreen mode Exit fullscreen mode

This will create the launch config with all the args.

vs-code-launch-settings

For Android Studio

For Android Studio, you can get the configuration files from here, for all the configs.

android-studio-launch-settings


πŸŽ‰ 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 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

Top comments (0)