skip to content

In Flutter, what is the difference between --flavor and --dart-define, and which do staging and production builds of a telemedicine app need?

level: middleimportance: must knowfreq 55%

answer

  1. native variant versus Dart constant
  2. productFlavors and Xcode schemes
  3. side-by-side installs need an app ID
  4. String.fromEnvironment reads defines
  5. usually both, in one command

basics

~20 s

--flavor selects a native build variant (an Android product flavor, an Xcode scheme) that can change the app ID, name and icon; --dart-define injects compile-time Dart constants such as the API URL. Two installable apps need a flavor; URLs can come from defines or appFlavor.

solid answer

~40 s

`--flavor staging` tells the Flutter tool which native variant to build: the Android product flavor `staging` from `productFlavors` in `build.gradle.kts`, and on iOS the Xcode scheme `staging` with its `Debug-staging`/`Release-staging` build configurations. That is where the application ID suffix, display name and icon differ, so staging and production can be installed side by side; the name also reaches Dart as the `appFlavor` constant. `--dart-define=API_URL=...` or `--dart-define-from-file=config/staging.json` only injects compile-time constants read with `String.fromEnvironment`; it touches no native project. A telemedicine app with two API URLs could get by with defines alone, but two installable apps need flavors, so teams typically pass both: `flutter build appbundle --flavor staging --dart-define-from-file=config/staging.json`.

code

bash · 8 lines
bash
# Staging app: its own application ID, name and icon, staging API constants
flutter build appbundle --flavor staging --dart-define-from-file=config/staging.json

# Production app
flutter build appbundle --flavor production --dart-define-from-file=config/production.json

# One-off override while debugging
flutter run --flavor staging --dart-define-from-file=config/staging.json --dart-define=API_URL=http://10.0.2.2:8080

go deeper

for a junior

Remember the split: --flavor picks the native build variant, --dart-define passes constants into Dart code, and both are fixed at build time.

for a middle

Explain what each changes: product flavors and Xcode schemes versus String.fromEnvironment constants, and why side-by-side installs require flavors.

for a senior

Show how you keep flavor and define file paired in CI and IDE launch configs, mirror flavors on every platform, and avoid mismatched builds.

for a principal

Weigh how many environments are worth a native flavor, versus configuration-only defines, given the build matrix and test cost each adds.

## Two different knobs Flutter gives you two build-time switches that are easy to confuse because both are often described as "environments": - **`--flavor <name>`** selects a **native build variant**. It is defined in the Android and Xcode projects, not in Dart. - **`--dart-define KEY=VALUE`** (short form `-D`) and **`--dart-define-from-file path`** inject **compile-time constants** into the Dart program. They are defined only on the command line or in a JSON or `.env` file. The scenario that makes the difference concrete: a telemedicine app has a **staging** backend at `https://staging.api.example.com` and a **production** backend at `https://api.example.com`. Testers want both apps on one phone. ## What `--flavor` does On **Android**, a Flutter flavor is a Gradle **product flavor**. You declare it in `android/app/build.gradle.kts`: ```kotlin flavorDimensions += "default" productFlavors { create("staging") { dimension = "default" applicationIdSuffix = ".staging" } create("production") { dimension = "default" } } ``` `flutter build apk --flavor staging --release` then builds the `stagingRelease` variant and writes `app-staging-release.apk`. Flavor-specific resources under `android/app/src/staging/res` give that variant its own `app_name` and launcher icon. On **iOS and macOS**, a Flutter flavor is an **Xcode scheme** with matching build configurations named `Debug-staging`, `Profile-staging` and `Release-staging`. Each configuration can set its own bundle identifier and display name. In **Dart**, the flavor name is available as the constant `appFlavor` from `package:flutter/services.dart` — `null` when no flavor was given and no `default-flavor` is set in `pubspec.yaml`. ## What `--dart-define` does A define becomes a value the compiler substitutes into your program: ```dart const apiUrl = String.fromEnvironment('API_URL'); ``` It changes nothing in the Android or Xcode project: same application ID, same name, same icon. `--dart-define-from-file=config/staging.json` loads many keys at once, and a `--dart-define` with the same key takes precedence over the file. ## Which one you need | Need | `--flavor` | `--dart-define` | |---|---|---| | Different API base URL | possible via `appFlavor` | yes, the natural fit | | Staging and production installed side by side | yes (application ID / bundle ID) | no | | Different launcher name or icon | yes | no | | Flavor-only bundled assets | yes (`flavors:` under `assets`) | no | | Value readable as a Dart `const` | only the name, via `appFlavor` | yes, any key | | Requires editing native projects | yes | no | So: 1. If the only difference is **configuration values** and one installed app is fine, `--dart-define-from-file` is enough. 2. If testers need **two installable apps**, or the builds differ in icon, name or native settings, you need **flavors**. 3. Most production teams use **both** in one command, so the native variant and the Dart configuration travel together: ```bash flutter build appbundle --flavor staging --dart-define-from-file=config/staging.json ``` ## Beyond Android and iOS - **macOS** uses Xcode schemes and configurations exactly as iOS does. - **Windows and Linux** gained built-in flavor support in Flutter 3.47: `--flavor` isolates the build output per flavor, fills `appFlavor`, filters flavor-only assets, and writes `FLUTTER_APP_FLAVOR` as a CMake variable that the runner's `CMakeLists.txt` can read for a window title or icon. - **Web** has no `--flavor` option on `flutter build web`; web builds differ by `--dart-define` values alone. ## Wiring it into daily work Nobody should type the flavor and the define file by hand. Typical setups: 1. **IDE launch configurations** per environment, each carrying `--flavor staging --dart-define-from-file=config/staging.json`, committed so the team shares them. 2. **CI jobs** per environment that call the same command with a fixed pair, so a staging job cannot pick up production values. 3. **Committed define files** for non-secret values, one per environment, reviewed like code. ## Pitfalls - **Mismatched pairs.** Nothing stops `--flavor production` with `config/staging.json`. Wrap the pairing in a script, a CI job or IDE launch configurations, or derive values from `appFlavor` so only one switch exists. - **Flavors on one platform only.** An Android-only flavor setup makes `flutter run --flavor staging` fail on iOS until matching schemes exist. - **`FLUTTER_APP_FLAVOR` is reserved.** The tool sets it from `--flavor`; passing it through `--dart-define` makes the tool exit with an error. - **Defines are not secrets.** Anything passed as a define is compiled into the app binary; secret handling is a separate topic.

  • Can a Flutter app switch between staging and production API URLs at runtime without rebuilding?
    Not through flavors or defines: both are fixed at build time, and `String.fromEnvironment` values are compile-time constants. A runtime switch needs app code, such as a hidden debug menu storing a choice locally, usually allowed only in non-production flavors.
  • What happens if you run flutter run --flavor staging on iOS when only Android defines the flavor?
    The tool looks for an Xcode scheme matching the flavor. If the project defines custom schemes but none matches it exits, listing the schemes; if it defines none it reports that `--flavor` cannot be used. Flavors must be mirrored on every platform you build.
  • Which wins when a key appears in both --dart-define-from-file and --dart-define?
    The `--dart-define` entry. The Flutter tool's help text states that entries from `--dart-define` with identical keys take precedence over entries from files, which is what makes one-off overrides on top of a shared config file work.

saying these in an interview costs you the question

  • Thinks --dart-define changes the Android application ID or iOS bundle ID
  • Believes --flavor is defined in Dart code or pubspec.yaml alone
  • Thinks flavors can be switched at runtime from a settings screen
  • Assumes an Android product flavor automatically works for iOS builds
  • Treats values passed with --dart-define as hidden from the shipped binary