In Flutter, how does Dart code learn which flavor it was built with, and what does appFlavor hold when no --flavor is passed?
answer
- import package:flutter/services.dart
- const String? appFlavor
- null without a flavor
- default-flavor in pubspec.yaml
- flavor-only assets list
basics
~10 sImport package:flutter/services.dart and read the appFlavor constant; it equals the --flavor name, or the pubspec's default-flavor when none is passed, and is null when neither is set.
solid answer
~30 s`appFlavor` is a `const String?` in `package:flutter/services.dart`. The Flutter tool fills it from `--flavor`, so `flutter run --flavor staging` makes it `'staging'`. With no flag it falls back to `default-flavor` under `flutter:` in `pubspec.yaml`, and it is `null` when neither is set — so a telemedicine app switching on it must handle `null`. You typically switch on it once in `main()` to choose an API URL or theme. A related pubspec feature bundles assets only for certain flavors with a `flavors:` list under an `assets` entry.
code
dart · 11 linesimport 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
final apiUrl = switch (appFlavor) {
'production' => 'https://api.example.com',
'staging' => 'https://staging.api.example.com',
_ => 'http://localhost:8080',
};
runApp(TelemedicineApp(apiUrl: apiUrl, showStagingBanner: appFlavor == 'staging'));
}go deeper
Recall that appFlavor comes from package:flutter/services.dart, equals the --flavor name, and is null when no flavor was given.
Explain that appFlavor is a const built on String.fromEnvironment, how default-flavor fills it, and how flavor-only assets are declared.
Resolve configuration once from appFlavor or defines and inject it, handle the null case in tests, and keep flavor names consistent across platforms.
Decide whether flavor names or define files are the single source of environment truth, and how that choice scales to more environments.
## The `appFlavor` constant Flutter exposes the flavor a build was made with as a top-level constant: ```dart import 'package:flutter/services.dart'; // appFlavor is a const String? ``` Its framework source is short: ```dart const String? appFlavor = String.fromEnvironment('FLUTTER_APP_FLAVOR') != '' ? String.fromEnvironment('FLUTTER_APP_FLAVOR') : null; ``` So `appFlavor` is: - the **name passed to `--flavor`** on `flutter run` or `flutter build`, exactly as typed (`'staging'`); - **`null`** when no flavor was selected; - a **compile-time constant**, which means code guarded by `if (appFlavor == 'production')` can be tree-shaken. The tool writes `FLUTTER_APP_FLAVOR` itself; trying to set it with `--dart-define` or `--dart-define-from-file` makes the tool exit with an error saying the key is used by the framework. ## Using it A telemedicine app with staging and production backends typically resolves configuration once, at startup: ```dart void main() { final apiUrl = switch (appFlavor) { 'production' => 'https://api.example.com', 'staging' => 'https://staging.api.example.com', _ => 'http://localhost:8080', }; runApp(TelemedicineApp(apiUrl: apiUrl)); } ``` The wildcard arm matters: without a flavor the value is `null`, and a `switch` that only lists `'staging'` and `'production'` would need another answer for that case. ## `default-flavor` Since Flutter 3.24 you can name a **default flavor** in `pubspec.yaml`: ```yaml flutter: default-flavor: production ``` When `--flavor` is omitted, the tool uses this value both for the native build (the product flavor or Xcode scheme it selects) and for `appFlavor`. An explicit `--flavor staging` overrides it. In a project where every build is flavored, it saves typing `--flavor` on every run and keeps plain `flutter run` working. ## Flavor-specific assets The same `pubspec.yaml` can restrict assets to flavors, so a staging-only banner or test fixture does not ship in production: ```yaml flutter: assets: - assets/common/ - path: assets/staging/ flavors: - staging ``` Assets under `assets/staging/` are bundled only when building the `staging` flavor. ## Choosing between `appFlavor` and defines 1. **`appFlavor` alone** — one switch in the command line; configuration values live in Dart code keyed by flavor name. Simple, but every URL change is a code change. 2. **Defines alone** — values in `config/*.json` passed with `--dart-define-from-file`; no native flavors, so no side-by-side installs. 3. **Both** — the flavor controls native identity (ID, name, icon) and the define file carries values; `appFlavor` is then used for small behaviour differences like a "STAGING" ribbon. | Source | Set by | Type in Dart | When absent | |---|---|---|---| | `appFlavor` | `--flavor` or `default-flavor` | `const String?` | `null` | | `String.fromEnvironment(k)` | `--dart-define` / file | `const String` | `defaultValue` (`''`) | ## Testing flavor-dependent code `flutter test` accepts `--flavor` and `--dart-define` like `flutter run` does, but most test runs pass neither, so `appFlavor` is `null` unless `default-flavor` is set. Two habits keep tests independent of build flags: 1. Resolve the flavor into a plain configuration object in `main()` and **inject** that object; tests construct the object directly. 2. Keep a small, pure function such as `AppConfig.forFlavor(String? flavor)` and unit-test it with `'staging'`, `'production'` and `null`, so the mapping itself is covered without rebuilding. ## Pitfalls - Comparing against a differently cased name: `appFlavor` holds exactly what was passed, while Xcode scheme lookup in the tool is case-insensitive, so `--flavor Staging` can build the `staging` scheme yet make `appFlavor == 'staging'` false. - Reading `appFlavor` deep inside widgets instead of resolving configuration once and injecting it, which makes tests depend on build flags. - Forgetting the `null` case in tests: a plain `flutter test` run has no flavor unless `default-flavor` is set or `--flavor` is passed.
- Why can't you set FLUTTER_APP_FLAVOR yourself with --dart-define to fake a flavor?The Flutter tool reserves that key: it sets `FLUTTER_APP_FLAVOR` from `--flavor` (or `default-flavor`) and exits with an error if a `--dart-define` or define file tries to set it. That keeps `appFlavor` consistent with the native variant actually built.
- What is appFlavor during flutter test?A plain `flutter test` run passes no `--flavor`, so `appFlavor` is `null` unless the pubspec sets `default-flavor` or the run passes `--flavor`. Code that resolves configuration from `appFlavor` needs a fallback, and tests should inject configuration directly rather than depend on the flavor.
saying these in an interview costs you the question
- Thinks appFlavor defaults to 'main' or 'debug' when no flavor is passed
- Believes appFlavor can be changed at runtime from a settings screen
- Sets FLUTTER_APP_FLAVOR with --dart-define to choose a flavor
- Reads appFlavor inside many widgets instead of resolving config once
- Thinks default-flavor overrides an explicit --flavor argument