skip to content

In Flutter, how does Dart code learn which flavor it was built with, and what does appFlavor hold when no --flavor is passed?

level: juniorimportance: should knowfreq 38%

answer

  1. import package:flutter/services.dart
  2. const String? appFlavor
  3. null without a flavor
  4. default-flavor in pubspec.yaml
  5. flavor-only assets list

basics

~10 s

Import 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 lines
dart
import '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

for a junior

Recall that appFlavor comes from package:flutter/services.dart, equals the --flavor name, and is null when no flavor was given.

for a middle

Explain that appFlavor is a const built on String.fromEnvironment, how default-flavor fills it, and how flavor-only assets are declared.

for a senior

Resolve configuration once from appFlavor or defines and inject it, handle the null case in tests, and keep flavor names consistent across platforms.

for a principal

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