Launch Club

Add Flavors & Environment-Based App Icons to your Flutter Apps

Flutter Package Flavors

Published May 2, 2023

Marcus Ng

Marcus Ng

When developing apps, it’s very important to separate your development and production environments. App flavors allow us to create multiple versions of our app with the same codebase, making it easy to create and test new features without the risk of destroying production data.

You’ll learn how to create three Android and iOS flavors:

  • development - Build and test new features
  • staging - Test out the app in a production-like environment
  • production - For your users

Multiple main.dart files

We first have to create three entry points, one for each flavor: main_development.dart, main_staging.dart, and main_production.dart.

To run a specific main file, we can use $ flutter run.

E.g. $ flutter run --target lib/main_development.dart

Once we add our Android and iOS flavors configurations, we’ll also have to include the flavor we want to run with --flavor <flavor_name>

E.g. $ flutter run --flavor development --target lib/main_development.dart

However, there are two problems with this:

  1. It’s annoying to type this out whenever we need to run a different app build
  2. Running this command in the terminal won’t allow us to utilize VSCode’s debugger

Creating launch configurations in VSCode

Luckily, both of these are easily fixable with a launch.json file. At the root of our project, create a launch.json file inside a folder called .vscode.

.vscode/launch.json

We have three configurations, one for each flavor. Each configuration has a name, request, type, program, and arguments.

// .vscode/launch.json
{
	"version": "0.2.0",
	"configurations": [
		{
			"name": "Launch development",
			"request": "launch",
			"type": "dart",
			"program": "lib/main_development.dart",
			"args": [
				"--flavor",
				"development",
				"--target",
				"lib/main_development.dart"
			]
		},
		{
			"name": "Launch staging",
			"request": "launch",
			"type": "dart",
			"program": "lib/main_staging.dart",
			"args": ["--flavor", "staging", "--target", "lib/main_staging.dart"]
		},
		{
			"name": "Launch production",
			"request": "launch",
			"type": "dart",
			"program": "lib/main_production.dart",
			"args": ["--flavor", "production", "--target", "lib/main_production.dart"]
		}
	]
}

Now when we go to the Run tab, we see our three launch configurations.

VSCode run tab

When we tap run, we get an error about not being able to use the --flavor option, so let’s add our build flavors next.

Exception: The Xcode project does not define custom schemes. You cannot use the --flavor option.
Exited (sigterm)

Android Flavor Setup

Adding build flavors to Android is pretty straightforward. Inside of our app/build.gradle, we add our product flavors for production, staging, and development. The name of the app for each build is defined in the resValue line and our applicationId has a suffix for staging and development.

// android/app/build.gradle
flavorDimensions "default"
  productFlavors {
    prod {
      dimension "default"
      resValue "string", "app_name", "Flavor Example"
      applicationIdSuffix ""
    }
    stg {
      dimension "default"
      resValue "string", "app_name", "Stg Flavor Example"
      applicationIdSuffix ".stg"
    }
    dev {
      dimension "default"
      resValue "string", "app_name", "Dev Flavor Example"
      applicationIdSuffix ".dev"
    }
  }

Our current applicationId is com.example.flavorsExample.

This means our bundle identifiers will be:

  • com.example.flavorsExample - production
  • com.example.flavorsExample.stg - staging
  • com.example.flavorsExample.dev - development

In our android/app/src/main/AndroidManifest.xml, we set the android:label="@string/app_name" to set our app’s app name.

<!-- android/app/src/main/AndroidManifest.xml-->

<application
    android:label="@string/app_name"
    android:icon="@mipmap/ic_launcher">
...
</application>

iOS Flavor Setup

iOS build configurations are more complicated than Android and can only be done in Xcode, so be sure to follow along closely. In Xcode, let’s create two new schemes called development and staging that both target Runner. Rename the original Runner to production.

Xcode > Runner > Manage Schemes schemes

Next, we have to go into the Runner project and duplicate the Debug, Release, and Profile configurations for development and staging. Add the -production suffix to the original configuration.

Xcode configurations

Now let’s go into Manage Schemes and edit the development and staging schemes to point to the correct build configurations. The production scheme already points to our production configurations, which means we don’t have to change them.

Scheme configuration

In the Build Settings of Target Runner, make sure All and Combined are selected, and then search for bundle identifier. Here we set each configuration’s bundle identifier.

Bundle identifiers

Then set our app’s display name by creating a User-Defined variable called APP_DISPLAY_NAME. Note that if your app display name is longer than 12 characters, the spaces will be removed.

User defined APP_DISPLAY_NAME

We use this variable in our Info.plist by creating a new key CFBundleDisplayName and setting it to APP_DISPLAY_NAME wrapped in parentheses with a dollar sign.

bundle display name

Finally, set the FLUTTER_TARGET in the Build Settings to target each main.dart entry point based on the selected flavor.

Build Settings - flutter_target

Generate app icons with the flutter_launcher_icons package

icons

Drag your icons into assets/icons. My icons are 1024x1024.

icons

In our pubspec.yaml, add the flutter_launcher_icons package as a dev dependency. This package will generate all of the icon sizes for Android and iOS and put them into the correct folders.

We have to create a configuration file for each of our flavors. Each file is named flutter_launcher_icons- the flavor name .yaml and points to the flavor icon in the assets/icons/ folder.

Set android and ios to true to override the existing Flutter launcher icon for both platforms and define the icon image path.

When you submit your app to the iOS app store, make sure to remove the alpha channel from your app icons, so your app doesn’t get rejected. We can add remove_alpha_ios: true to the yaml files.

# flutter_launcher_icons-development.yaml
flutter_icons:
  android: true
  ios: true
  image_path: 'assets/icons/development-icon.png'
  remove_alpha_ios: true

# flutter_launcher_icons-staging.yaml
flutter_icons:
  android: true
  ios: true
  image_path: 'assets/icons/staging-icon.png'
  remove_alpha_ios: true

# flutter_launcher_icons-production.yaml
flutter_icons:
  android: true
  ios: true
  image_path: 'assets/icons/production-icon.png'
  remove_alpha_ios: true

Generate the icons with $ flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons*.

At this point, Android is all good to go as all the files were generated properly. For iOS, we see that our three icons were generated in the Assets.xcassets folder.

iOS Assets

Let’s go into the Target’s Build Settings and search for primary app icon. Here we set the correct app icon for each schema.

Build Settings - Primary App Icon Set Name

Wrap Up

I’ve run all three flavors on Android and iOS, and all three apps have the correct name, app icon, and app or bundle identifier.

app icons and names

Every app’s AppBar displays the flavor string we passed into each main file.

app bars

And that’s it! You just learned how to quickly set up flavors in your Flutter projects and generate a unique app icon for each flavor.

Flutter and Dart

made simple.

Everything you need to build production ready apps.

No spam. Just updates. Unsubscribe whenever.