Flutter Flavors
Adds dev/stg/prod (or custom) flavors to a Flutter project, or audits and repairs an existing
flavor setup that is partial or broken. Never runs dart run flutter_flavorizr bare — the bare
command overwrites lib/main.dart and silently destroys all app-initialization logic. Every native
change goes through targeted processors or hand-written patches, never a full regenerate. Every
flavorizr invocation also always carries -f/--force — without it the tool's confirm prompt
crashes outright in a non-interactive shell (see references/flavorizr-processors.md); piping
input does not help, only -f does.
Rule and reference sources
Rules for the AUDIT branch are bundled in skills/flutter-flavors/rules/CATALOG.md.
Deep how-to context for the INIT branch is bundled in skills/flutter-flavors/references/.
This skill does not delegate to ai_toolkit/ — it is self-contained.
Version baselines cited below (flutter_flavorizr: 2.6.0, flutterfire_cli: 1.4.1) are the
latest verified on pub.dev at the time this skill was written — verify current versions via
context7 or pub.dev before pinning a version in the target project's pubspec.yaml.
Phase 0 — Detect & classify
Goal: know the project's actual flavor state before asking a single question or touching a file.
Read pubspec.yaml and grep the project for, recording found/missing for each:
| Signal |
Where |
flavorizr: block |
pubspec.yaml |
productFlavors / flavorDimensions |
android/app/build.gradle.kts |
| Per-flavor source sets |
android/app/src/<flavor>/ |
| Per-flavor xcconfig |
ios/Flutter/*.xcconfig |
| Per-flavor scheme |
ios/**/xcshareddata/xcschemes/*.xcscheme |
| Multiple entry points |
lib/main_*.dart |
| Flavor enum / getter |
getFlavor() or enum Flavor anywhere in lib/ |
| VSCode launch config |
.vscode/launch.json |
| Android Studio run config |
.idea/runConfigurations/ |
| Firebase |
firebase_core in pubspec.yaml |
Monorepo hard gate: if melos.yaml exists at the root, or the root pubspec.yaml has a
workspace: key, stop immediately:
✗ This project is a Melos/pub workspace monorepo.
flutter-flavors targets a single Flutter app root and does not yet resolve which
package to flavor. Point it directly at the app package root instead
(e.g. apps/<name>/), or treat this as a separate effort.
Classify the project:
- NONE — no signal found at all → INIT branch (Phase 1 onward).
- PARTIAL or COMPLETE — at least one signal found → AUDIT branch (jump to
AUDIT branch below).
Print the detection matrix before proceeding either way — the user should see what was found,
not just the resulting branch choice.
Phase 1 — Intake (INIT branch only)
Goal: settle every decision that isn't safely inferable, before writing anything. This is the
core of the skill — a wrong answer here (especially on applicationId/bundleId) is expensive to
undo once builds have shipped.
Ask, with explicit defaults, using AskUserQuestion where the options are enumerable:
- Flavor names — default
dev, stg, prod. Confirm the list and order.
- App name per flavor — e.g.
Flutter Ship Dev, Flutter Ship Stg, Flutter Ship.
applicationId (Android, snake_case, from android/app/build.gradle.kts →
defaultConfig.applicationId) and bundleId (iOS, camelCase, from Xcode →
Runner → General → Identity, or infer camelCase from the existing applicationId if the user
has no Mac) per flavor.
Hard rule, state it explicitly and get an explicit yes: if this app is already published,
its production applicationId/bundleId must stay byte-for-byte unchanged — app stores
use it as the app's permanent identity. Only dev/stg get a suffix (.dev, .stg).
- Per-flavor icons — yes/no. If yes, ask where the flavor-specific icon assets already live
(or note they must be added before Phase 3 runs the icon processor).
- Entry points — single
main.dart with runtime flavor detection, or multiple
main_<flavor>.dart files. State the rule: multiple entry points are required if Firebase
is in scope (different firebase_options_<flavor>.dart per flavor cannot be selected at
runtime from a single entry point). See references/dart-layer.md.
- Web — yes/no. If yes, note that
--flavor is Android/iOS/macOS-only; web will use the
--dart-define WEB_FLAVOR=<flavor> workaround. See references/web-flavors.md.
- IDE target — VSCode, Android Studio, or both.
- Firebase — yes/no (auto-suggested
yes if Phase 0 found firebase_core). If yes, this
pulls in Phase 7.
- Strategy —
flutter_flavorizr with targeted processors (default, faster, handles iOS
pbxproj mechanically) vs fully manual (no external tool, smaller diffs, required if Ruby/Gem/
Xcodeproj aren't available for iOS — see Phase 3 prerequisite check).
Print the full decision table and ask for one final confirmation before Phase 2. Do not proceed on
an assumed "yes."
Phase 2 — Safety gate
Goal: guarantee a clean rollback point exists before any operation that can touch 100+ files.
Run git status --porcelain in the target project. If it is not empty, stop:
✗ Working tree is not clean.
flutter_flavorizr (and the manual native patches) touch many files at once.
Without a clean commit, a bad run cannot be safely rolled back with:
git reset --hard HEAD && git clean -fd
Commit or stash your changes, then re-run this skill.
If clean, print the rollback command anyway so the user has it on hand, then proceed.
Phase 3 — Native setup (Android + iOS)
Two branches per the Phase 1 strategy answer. Read the relevant reference file(s) in full before
writing anything: references/flavorizr-processors.md for branch A, references/manual-android.md
and references/manual-ios.md for branch B.
Branch A — flutter_flavorizr (default)
- Add
dev:flutter_flavorizr: ^2.6.0 (verify current via context7/pub.dev) to
pubspec.yaml dev_dependencies if missing; flutter pub get.
- Write the
flavorizr: block at the end of pubspec.yaml from the Phase 1 answers (flavor
names, app names, applicationId/bundleId, icon paths if provided).
- iOS prerequisite check: verify Ruby, Gem, and the
xcodeproj gem are available. If any is
missing, skip iOS processors below, emit the manual iOS checklist from
references/manual-ios.md, and continue with Android only.
- Run processors targeted, never bare — see
references/flavorizr-processors.md for the
full rationale and the trap (bare run overwrites lib/main.dart):dart run flutter_flavorizr -f -p android:buildGradle,android:flavorizrGradle,android:androidManifest,android:icons
dart run flutter_flavorizr -f -p assets:download,assets:extract,ios:podfile,ios:xcconfig,ios:buildTargets,ios:schema,ios:plist,ios:dummyAssets,ios:icons,assets:clean
(Skip the second command entirely if the iOS prerequisite check failed.)
AndroidManifest.xml comes out with attributes inlined on one line — reformat it (XML Tools
extension + SHIFT+OPTION+F in VSCode, or an equivalent formatter) so the diff is reviewable.
- Confirm
lib/main.dart still contains the original main() body untouched — the targeted
processor list above does not include flutter:main, so it should be. State this check
explicitly in the phase output; if main.dart was touched, stop and surface it before Phase 4.
Branch B — Manual
Follow references/manual-android.md and references/manual-ios.md step by step:
- Android:
flavorDimensions + productFlavors (each with a per-flavor resValue(type = "string", name = "app_name", value = "...")) in android/app/flavors.gradle.kts, included from
android/app/build.gradle.kts; android:label="@string/app_name" in AndroidManifest.xml
(one attribute, resolves the per-flavor resource); per-flavor source sets under
android/app/src/<flavor>/.
- iOS: 9 duplicated build configurations and 3 shared schemes created in Xcode (cannot be scripted
— present as a checklist), per-flavor bundle id suffixes, and
Info.plist using
$(APP_DISPLAY_NAME) (the manual-path variable name — differs from the flavorizr path's
BUNDLE_NAME/BUNDLE_DISPLAY_NAME; never mix the two conventions in one project).
Phase 4 — Dart layer
Read references/dart-layer.md first.
- In
lib/main.dart, rename main() → Future<void> runMainApp(), preserving every line of
existing initialization logic (this is the step most likely to be done carelessly — verify
nothing was dropped, not just renamed).
- If multiple entry points were chosen in Phase 1, create
lib/main_<flavor>.dart for each
flavor:import 'main.dart';
// * Entry point for the <flavor> flavor
void main() async {
await runMainApp();
}
- Create
lib/env/flavor.dart (or the project's existing env/config directory) with:import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
enum Flavor { dev, stg, prod }
/// Global function to return the current flavor
Flavor getFlavor() {
const webFlavor = String.fromEnvironment('WEB_FLAVOR');
const flavor = kIsWeb ? webFlavor : appFlavor;
return switch (flavor) {
'prod' => Flavor.prod,
'stg' => Flavor.stg,
'dev' => Flavor.dev,
null || '' => Flavor.dev,
_ => throw UnsupportedError('Invalid flavor: $flavor'),
};
}
Adjust the enum values and switch arms to the flavor names chosen in Phase 1.
Mandatory warning to print once entry points exist: from this point on, -t lib/main_<flavor>.dart is required on every flutter run/flutter build. Omitting it defaults to
lib/main.dart, which no longer has a main() function, and the app hangs on the splash screen
with Could not prepare isolate in the console. Print this warning verbatim so the user
recognizes the failure mode if it happens later.
Phase 5 — Web
Only if Phase 1 chose web. Read references/web-flavors.md.
Document, do not silently assume:
flutter run --flavor <f> on web only warns (--flavor is only supported for Android, macOS, and iOS devices) and still runs.
flutter build web --flavor <f> fails outright (Could not find an option named "flavor").
- The workaround is always
--dart-define WEB_FLAVOR=<f>, which getFlavor() (Phase 4) already
reads via the kIsWeb branch.
Print the full per-platform run matrix so the user has every command in one place:
flutter run --flavor dev -t lib/main_dev.dart # Android/iOS
flutter run -d chrome --dart-define WEB_FLAVOR=dev -t lib/main_dev.dart # Web
flutter run --flavor dev --dart-define WEB_FLAVOR=dev -t lib/main_dev.dart # combined
Phase 6 — IDE config
Read references/ide-config.md.
VSCode
dart run flutter_flavorizr -f -p ide:config to regenerate .vscode/launch.json.
- The output is minified JSON — reformat it:
jq '.' .vscode/launch.json > .vscode/formatted_launch.json && mv .vscode/formatted_launch.json .vscode/launch.json
If jq is unavailable, reformat by editing the file directly and note the manual step.
- Patch every configuration to add
--dart-define, WEB_FLAVOR=<flavor> to args (only if web
was chosen) and set program to the correct lib/main_<flavor>.dart. All <flavor count> × 3
build-mode configurations need this — do not stop after the first one.
Android Studio
- Set
ide: "idea" in the flavorizr: pubspec block, then dart run flutter_flavorizr -f -p ide:config — this only produces Debug configurations.
- Profile and Release configurations, and the web-aware
additionalArgs, must be written by hand
per references/ide-config.md — there is no processor for these. Generate all of them, do not
leave Profile/Release as a manual TODO for the user.
Phase 7 — Firebase (optional)
Only if Phase 1 chose Firebase. Read references/firebase-flavors.md in full before touching
anything — this phase has the most moving parts and the most native-build gotchas.
- Generate
flutterfire-config.sh filled in with the real Firebase project id, applicationId,
and bundleId per flavor (from Phase 1). Do not execute flutterfire configure — it is
interactive and requires the user's own Firebase login; hand them the ready-to-run script and
the exact commands instead:dart pub global activate flutterfire_cli # ^1.4.1 — verify current via pub.dev
./flutterfire-config.sh dev
./flutterfire-config.sh stg
./flutterfire-config.sh prod
flutter pub add firebase_core (+ any other Firebase packages already in use elsewhere in the
project).
- Verify (and patch if missing) Android build settings:
com.google.gms.google-services plugin
in both android/settings.gradle.kts and android/app/build.gradle.kts; ndkVersion and
Java/Kotlin target 17.
- Verify (and patch if needed)
ios/Podfile: platform :ios, '16.0' or higher; same for
macos/Podfile if the project supports macOS.
- Patch each
lib/main_<flavor>.dart to import the matching firebase_options_<flavor>.dart and
pass DefaultFirebaseOptions.currentPlatform into runMainApp; patch runMainApp in
lib/main.dart to accept {required FirebaseOptions firebaseOptions} and call
Firebase.initializeApp(options: firebaseOptions) before the rest of existing init logic.
Note the re-run trigger from the KB: flutterfire configure must be re-run whenever a new
platform is added or a new Firebase product (Crashlytics, Performance Monitoring, Google Sign-In,
Realtime Database) is first used — this is not a one-time step.
Phase 8 — Verification
Run, and report pass/fail for each (skip any platform not in scope from Phase 1):
flutter analyze
flutter build apk --debug --flavor <first-flavor> -t lib/main_<first-flavor>.dart
flutter build web --dart-define WEB_FLAVOR=<first-flavor> -t lib/main_<first-flavor>.dart # if web
If iOS processors ran (Branch A with prerequisites met), note that a real build check requires
Xcode and is out of scope for a non-Mac session — instruct the user to run
flutter build ios --flavor <f> -t lib/main_<f>.dart --no-codesign themselves and report back.
Print the final per-flavor, per-platform run command table (same shape as Phase 5's matrix, now
covering every configured flavor).
Phase 9 — Summary
Print, grouped:
- Files created — path + one-line purpose.
- Files modified — path + one-line description of the change.
- Packages added — package + version + command used.
- Manual checklist — items that cannot be automated: create the Firebase projects themselves
(if Firebase in scope), open Xcode to visually confirm schemes and icons, upload store listing
assets per flavor, verify
.xcscheme files are marked "Shared" so CI can see them.
AUDIT branch (partial / complete projects)
Entered from Phase 0 when any flavor signal already exists.
Load rules/CATALOG.md in full before scanning — it contains every heuristic needed; do not
open individual reference docs unless a violation needs a deeper fix explanation.
Scan the project against every rule in the catalog. For folder-scale scans (native config +
lib/), spawn an Explore subagent to enumerate candidate files first, the same pattern as
skills/audit-domain-layer/SKILL.md Phase 2 folder mode — list .dart files under lib/,
android/app/build.gradle.kts, ios/Flutter/*.xcconfig, ios/**/xcschemes/*.xcscheme,
.vscode/launch.json, .idea/runConfigurations/*.xml.
Emit a violations table exactly like audit-domain-layer's Phase 4 format:
## Audit Results — Flutter Flavors
### android/app/build.gradle.kts
| Line | Rule ID | Severity | Message |
|------|---------|----------|---------|
| 42 | FLAVOR-AND-04 | error | android/app/src/stg/ source set missing |
### lib/env/flavor.dart
| Line | Rule ID | Severity | Message |
|------|---------|----------|---------|
| 9 | FLAVOR-DART-02 | error | getFlavor() has no kIsWeb/WEB_FLAVOR branch — web always resolves to default |
---
**Summary**: 2 violations across 2 files (2 errors, 0 warnings, 0 info)
If nothing is found, say so explicitly: No violations found. Flavor setup matches the catalog.
Ask which rule IDs to fix (all, comma-separated list, or none) — same pattern as
audit-domain-layer Phase 5. For each selected violation:
autofix_safe: true → apply directly, show the diff.
autofix_safe: false → show the exact change, get explicit confirmation before editing.
FLAVOR-GIT-01 is a hard gate, not a fix target — if it fires, stop and point back to
Phase 2 instead of offering to "fix" it.
Route each fix through the matching INIT phase above (e.g. a missing Android source set is a
Phase 3 fix, a broken getFlavor() is a Phase 4 fix) rather than re-deriving the logic here —
apply only the touched piece, never re-run a full phase against an already-partial project.
Re-scan touched files only, confirm which violations were resolved.
Never rewrite a working, unflagged part of the project just because the AUDIT branch touched a
neighboring file.
Notes
- Paths inside this skill are relative to the target Flutter project root, not this toolkit
repo — consistent with every other skill here.
- Out of scope: Melos/pub-workspace monorepos (detected and blocked in Phase 0, not resolved),
macOS as a flavored target,
--dart-define-from-file per-flavor .env files, and whitelabel
app patterns (different codebase-sharing model — a separate skill's concern).
- This skill does not overlap with
sentry-init, which reads flavor entry points
(lib/main_*.dart) once they already exist but does not create them.
1---2name: flutter-flavors3description: Initialize flavors (dev/stg/prod) in a Flutter project, or audit and fix an existing partial/broken flavor setup — Android (build.gradle.kts, AndroidManifest), iOS (xcconfig, xcscheme, Info.plist), Web (--dart-define WEB_FLAVOR workaround), multiple entry points (main_*.dart), IDE config (VSCode launch.json, Android Studio .idea/runConfigurations), and optional multi-project Firebase (flutterfire configure per flavor). Detects project state first and branches into an INIT flow (flutter_flavorizr with targeted processors, or manual fallback) or an AUDIT+FIX flow against a bundled rule catalog. Use proactively when the user says "aggiungi flavor a questa app", "inizializza flavors", "setup dev/stg/prod", "flutter_flavorizr", "audit flavors", "i miei flavor sono rotti", "add flavors to this Flutter app", "set up flavors", "fix my flavor setup", "flavor configuration is broken", or asks to distinguish flavors from dart-defines.4---56# Flutter Flavors78Adds `dev`/`stg`/`prod` (or custom) flavors to a Flutter project, or audits and repairs an existing9flavor setup that is partial or broken. Never runs `dart run flutter_flavorizr` bare — the bare10command overwrites `lib/main.dart` and silently destroys all app-initialization logic. Every native11change goes through targeted processors or hand-written patches, never a full regenerate. Every12flavorizr invocation also always carries `-f`/`--force` — without it the tool's confirm prompt13crashes outright in a non-interactive shell (see `references/flavorizr-processors.md`); piping14input does not help, only `-f` does.1516## Rule and reference sources1718Rules for the AUDIT branch are bundled in `skills/flutter-flavors/rules/CATALOG.md`.19Deep how-to context for the INIT branch is bundled in `skills/flutter-flavors/references/`.20This skill does not delegate to `ai_toolkit/` — it is self-contained.2122Version baselines cited below (`flutter_flavorizr: 2.6.0`, `flutterfire_cli: 1.4.1`) are the23latest verified on pub.dev at the time this skill was written — verify current versions via24context7 or `pub.dev` before pinning a version in the target project's `pubspec.yaml`.2526---2728## Phase 0 — Detect & classify2930**Goal**: know the project's actual flavor state before asking a single question or touching a file.3132Read `pubspec.yaml` and grep the project for, recording found/missing for each:3334| Signal | Where |35|---|---|36| `flavorizr:` block | `pubspec.yaml` |37| `productFlavors` / `flavorDimensions` | `android/app/build.gradle.kts` |38| Per-flavor source sets | `android/app/src/<flavor>/` |39| Per-flavor xcconfig | `ios/Flutter/*.xcconfig` |40| Per-flavor scheme | `ios/**/xcshareddata/xcschemes/*.xcscheme` |41| Multiple entry points | `lib/main_*.dart` |42| Flavor enum / getter | `getFlavor()` or `enum Flavor` anywhere in `lib/` |43| VSCode launch config | `.vscode/launch.json` |44| Android Studio run config | `.idea/runConfigurations/` |45| Firebase | `firebase_core` in `pubspec.yaml` |4647**Monorepo hard gate**: if `melos.yaml` exists at the root, or the root `pubspec.yaml` has a48`workspace:` key, stop immediately:4950```51✗ This project is a Melos/pub workspace monorepo.52 flutter-flavors targets a single Flutter app root and does not yet resolve which53 package to flavor. Point it directly at the app package root instead54 (e.g. apps/<name>/), or treat this as a separate effort.55```5657Classify the project:58- **NONE** — no signal found at all → **INIT branch** (Phase 1 onward).59- **PARTIAL or COMPLETE** — at least one signal found → **AUDIT branch** (jump to60 [AUDIT branch](#audit-branch-partial--complete-projects) below).6162Print the detection matrix before proceeding either way — the user should see what was found,63not just the resulting branch choice.6465---6667## Phase 1 — Intake (INIT branch only)6869**Goal**: settle every decision that isn't safely inferable, before writing anything. This is the70core of the skill — a wrong answer here (especially on `applicationId`/`bundleId`) is expensive to71undo once builds have shipped.7273Ask, with explicit defaults, using `AskUserQuestion` where the options are enumerable:74751. **Flavor names** — default `dev`, `stg`, `prod`. Confirm the list and order.762. **App name per flavor** — e.g. `Flutter Ship Dev`, `Flutter Ship Stg`, `Flutter Ship`.773. **`applicationId`** (Android, snake_case, from `android/app/build.gradle.kts` →78 `defaultConfig.applicationId`) and **`bundleId`** (iOS, camelCase, from Xcode →79 Runner → General → Identity, or infer camelCase from the existing `applicationId` if the user80 has no Mac) per flavor.81 **Hard rule, state it explicitly and get an explicit yes**: if this app is already published,82 its production `applicationId`/`bundleId` must stay **byte-for-byte unchanged** — app stores83 use it as the app's permanent identity. Only `dev`/`stg` get a suffix (`.dev`, `.stg`).844. **Per-flavor icons** — yes/no. If yes, ask where the flavor-specific icon assets already live85 (or note they must be added before Phase 3 runs the icon processor).865. **Entry points** — single `main.dart` with runtime flavor detection, or multiple87 `main_<flavor>.dart` files. State the rule: multiple entry points are **required** if Firebase88 is in scope (different `firebase_options_<flavor>.dart` per flavor cannot be selected at89 runtime from a single entry point). See `references/dart-layer.md`.906. **Web** — yes/no. If yes, note that `--flavor` is Android/iOS/macOS-only; web will use the91 `--dart-define WEB_FLAVOR=<flavor>` workaround. See `references/web-flavors.md`.927. **IDE target** — VSCode, Android Studio, or both.938. **Firebase** — yes/no (auto-suggested `yes` if Phase 0 found `firebase_core`). If yes, this94 pulls in Phase 7.959. **Strategy** — `flutter_flavorizr` with targeted processors (default, faster, handles iOS96 `pbxproj` mechanically) vs fully manual (no external tool, smaller diffs, required if Ruby/Gem/97 Xcodeproj aren't available for iOS — see Phase 3 prerequisite check).9899Print the full decision table and ask for one final confirmation before Phase 2. Do not proceed on100an assumed "yes."101102---103104## Phase 2 — Safety gate105106**Goal**: guarantee a clean rollback point exists before any operation that can touch 100+ files.107108Run `git status --porcelain` in the target project. If it is **not** empty, stop:109110```111✗ Working tree is not clean.112 flutter_flavorizr (and the manual native patches) touch many files at once.113 Without a clean commit, a bad run cannot be safely rolled back with:114 git reset --hard HEAD && git clean -fd115 Commit or stash your changes, then re-run this skill.116```117118If clean, print the rollback command anyway so the user has it on hand, then proceed.119120---121122## Phase 3 — Native setup (Android + iOS)123124Two branches per the Phase 1 strategy answer. Read the relevant reference file(s) in full before125writing anything: `references/flavorizr-processors.md` for branch A, `references/manual-android.md`126and `references/manual-ios.md` for branch B.127128### Branch A — flutter_flavorizr (default)1291301. Add `dev:flutter_flavorizr: ^2.6.0` (verify current via context7/pub.dev) to131 `pubspec.yaml` dev_dependencies if missing; `flutter pub get`.1322. Write the `flavorizr:` block at the end of `pubspec.yaml` from the Phase 1 answers (flavor133 names, app names, `applicationId`/`bundleId`, icon paths if provided).1343. **iOS prerequisite check**: verify Ruby, Gem, and the `xcodeproj` gem are available. If any is135 missing, skip iOS processors below, emit the manual iOS checklist from136 `references/manual-ios.md`, and continue with Android only.1374. Run processors **targeted**, never bare — see `references/flavorizr-processors.md` for the138 full rationale and the trap (bare run overwrites `lib/main.dart`):139 ```bash140 dart run flutter_flavorizr -f -p android:buildGradle,android:flavorizrGradle,android:androidManifest,android:icons141 dart run flutter_flavorizr -f -p assets:download,assets:extract,ios:podfile,ios:xcconfig,ios:buildTargets,ios:schema,ios:plist,ios:dummyAssets,ios:icons,assets:clean142 ```143 (Skip the second command entirely if the iOS prerequisite check failed.)1445. `AndroidManifest.xml` comes out with attributes inlined on one line — reformat it (XML Tools145 extension + `SHIFT+OPTION+F` in VSCode, or an equivalent formatter) so the diff is reviewable.1466. Confirm `lib/main.dart` still contains the original `main()` body untouched — the targeted147 processor list above does **not** include `flutter:main`, so it should be. State this check148 explicitly in the phase output; if `main.dart` was touched, stop and surface it before Phase 4.149150### Branch B — Manual151152Follow `references/manual-android.md` and `references/manual-ios.md` step by step:153- Android: `flavorDimensions` + `productFlavors` (each with a per-flavor `resValue(type =154 "string", name = "app_name", value = "...")`) in `android/app/flavors.gradle.kts`, included from155 `android/app/build.gradle.kts`; `android:label="@string/app_name"` in `AndroidManifest.xml`156 (one attribute, resolves the per-flavor resource); per-flavor source sets under157 `android/app/src/<flavor>/`.158- iOS: 9 duplicated build configurations and 3 shared schemes created in Xcode (cannot be scripted159 — present as a checklist), per-flavor bundle id suffixes, and `Info.plist` using160 `$(APP_DISPLAY_NAME)` (the manual-path variable name — differs from the flavorizr path's161 `BUNDLE_NAME`/`BUNDLE_DISPLAY_NAME`; never mix the two conventions in one project).162163---164165## Phase 4 — Dart layer166167Read `references/dart-layer.md` first.1681691. In `lib/main.dart`, rename `main()` → `Future<void> runMainApp()`, preserving every line of170 existing initialization logic (this is the step most likely to be done carelessly — verify171 nothing was dropped, not just renamed).1722. If multiple entry points were chosen in Phase 1, create `lib/main_<flavor>.dart` for each173 flavor:174 ```dart175 import 'main.dart';176177 // * Entry point for the <flavor> flavor178 void main() async {179 await runMainApp();180 }181 ```1823. Create `lib/env/flavor.dart` (or the project's existing env/config directory) with:183 ```dart184 import 'package:flutter/foundation.dart';185 import 'package:flutter/services.dart';186187 enum Flavor { dev, stg, prod }188189 /// Global function to return the current flavor190 Flavor getFlavor() {191 const webFlavor = String.fromEnvironment('WEB_FLAVOR');192 const flavor = kIsWeb ? webFlavor : appFlavor;193 return switch (flavor) {194 'prod' => Flavor.prod,195 'stg' => Flavor.stg,196 'dev' => Flavor.dev,197 null || '' => Flavor.dev,198 _ => throw UnsupportedError('Invalid flavor: $flavor'),199 };200 }201 ```202 Adjust the enum values and switch arms to the flavor names chosen in Phase 1.203204**Mandatory warning to print once entry points exist**: from this point on, `-t205lib/main_<flavor>.dart` is required on every `flutter run`/`flutter build`. Omitting it defaults to206`lib/main.dart`, which no longer has a `main()` function, and the app hangs on the splash screen207with `Could not prepare isolate` in the console. Print this warning verbatim so the user208recognizes the failure mode if it happens later.209210---211212## Phase 5 — Web213214Only if Phase 1 chose web. Read `references/web-flavors.md`.215216Document, do not silently assume:217- `flutter run --flavor <f>` on web only warns (`--flavor is only supported for Android, macOS,218 and iOS devices`) and still runs.219- `flutter build web --flavor <f>` **fails outright** (`Could not find an option named "flavor"`).220- The workaround is always `--dart-define WEB_FLAVOR=<f>`, which `getFlavor()` (Phase 4) already221 reads via the `kIsWeb` branch.222223Print the full per-platform run matrix so the user has every command in one place:224225```226flutter run --flavor dev -t lib/main_dev.dart # Android/iOS227flutter run -d chrome --dart-define WEB_FLAVOR=dev -t lib/main_dev.dart # Web228flutter run --flavor dev --dart-define WEB_FLAVOR=dev -t lib/main_dev.dart # combined229```230231---232233## Phase 6 — IDE config234235Read `references/ide-config.md`.236237### VSCode2382391. `dart run flutter_flavorizr -f -p ide:config` to regenerate `.vscode/launch.json`.2402. The output is minified JSON — reformat it:241 ```bash242 jq '.' .vscode/launch.json > .vscode/formatted_launch.json && mv .vscode/formatted_launch.json .vscode/launch.json243 ```244 If `jq` is unavailable, reformat by editing the file directly and note the manual step.2453. Patch every configuration to add `--dart-define`, `WEB_FLAVOR=<flavor>` to `args` (only if web246 was chosen) and set `program` to the correct `lib/main_<flavor>.dart`. All `<flavor count> × 3`247 build-mode configurations need this — do not stop after the first one.248249### Android Studio2502511. Set `ide: "idea"` in the `flavorizr:` pubspec block, then `dart run flutter_flavorizr -f -p252 ide:config` — this only produces **Debug** configurations.2532. Profile and Release configurations, and the web-aware `additionalArgs`, must be written by hand254 per `references/ide-config.md` — there is no processor for these. Generate all of them, do not255 leave Profile/Release as a manual TODO for the user.256257---258259## Phase 7 — Firebase (optional)260261Only if Phase 1 chose Firebase. Read `references/firebase-flavors.md` in full before touching262anything — this phase has the most moving parts and the most native-build gotchas.2632641. Generate `flutterfire-config.sh` filled in with the real Firebase project id, `applicationId`,265 and `bundleId` per flavor (from Phase 1). **Do not execute** `flutterfire configure` — it is266 interactive and requires the user's own Firebase login; hand them the ready-to-run script and267 the exact commands instead:268 ```bash269 dart pub global activate flutterfire_cli # ^1.4.1 — verify current via pub.dev270 ./flutterfire-config.sh dev271 ./flutterfire-config.sh stg272 ./flutterfire-config.sh prod273 ```2742. `flutter pub add firebase_core` (+ any other Firebase packages already in use elsewhere in the275 project).2763. Verify (and patch if missing) Android build settings: `com.google.gms.google-services` plugin277 in both `android/settings.gradle.kts` and `android/app/build.gradle.kts`; `ndkVersion` and278 Java/Kotlin target `17`.2794. Verify (and patch if needed) `ios/Podfile`: `platform :ios, '16.0'` or higher; same for280 `macos/Podfile` if the project supports macOS.2815. Patch each `lib/main_<flavor>.dart` to import the matching `firebase_options_<flavor>.dart` and282 pass `DefaultFirebaseOptions.currentPlatform` into `runMainApp`; patch `runMainApp` in283 `lib/main.dart` to accept `{required FirebaseOptions firebaseOptions}` and call284 `Firebase.initializeApp(options: firebaseOptions)` before the rest of existing init logic.285286Note the re-run trigger from the KB: `flutterfire configure` must be re-run whenever a new287platform is added or a new Firebase product (Crashlytics, Performance Monitoring, Google Sign-In,288Realtime Database) is first used — this is not a one-time step.289290---291292## Phase 8 — Verification293294Run, and report pass/fail for each (skip any platform not in scope from Phase 1):295296```bash297flutter analyze298flutter build apk --debug --flavor <first-flavor> -t lib/main_<first-flavor>.dart299flutter build web --dart-define WEB_FLAVOR=<first-flavor> -t lib/main_<first-flavor>.dart # if web300```301302If iOS processors ran (Branch A with prerequisites met), note that a real build check requires303Xcode and is out of scope for a non-Mac session — instruct the user to run304`flutter build ios --flavor <f> -t lib/main_<f>.dart --no-codesign` themselves and report back.305306Print the final per-flavor, per-platform run command table (same shape as Phase 5's matrix, now307covering every configured flavor).308309---310311## Phase 9 — Summary312313Print, grouped:314315- **Files created** — path + one-line purpose.316- **Files modified** — path + one-line description of the change.317- **Packages added** — package + version + command used.318- **Manual checklist** — items that cannot be automated: create the Firebase projects themselves319 (if Firebase in scope), open Xcode to visually confirm schemes and icons, upload store listing320 assets per flavor, verify `.xcscheme` files are marked "Shared" so CI can see them.321322---323324## AUDIT branch (partial / complete projects)325326Entered from Phase 0 when any flavor signal already exists.3273281. Load `rules/CATALOG.md` in full before scanning — it contains every heuristic needed; do not329 open individual reference docs unless a violation needs a deeper fix explanation.3302. Scan the project against every rule in the catalog. For folder-scale scans (native config +331 `lib/`), spawn an Explore subagent to enumerate candidate files first, the same pattern as332 `skills/audit-domain-layer/SKILL.md` Phase 2 folder mode — list `.dart` files under `lib/`,333 `android/app/build.gradle.kts`, `ios/Flutter/*.xcconfig`, `ios/**/xcschemes/*.xcscheme`,334 `.vscode/launch.json`, `.idea/runConfigurations/*.xml`.3353. Emit a violations table exactly like `audit-domain-layer`'s Phase 4 format:336337 ```338 ## Audit Results — Flutter Flavors339340 ### android/app/build.gradle.kts341 | Line | Rule ID | Severity | Message |342 |------|---------|----------|---------|343 | 42 | FLAVOR-AND-04 | error | android/app/src/stg/ source set missing |344345 ### lib/env/flavor.dart346 | Line | Rule ID | Severity | Message |347 |------|---------|----------|---------|348 | 9 | FLAVOR-DART-02 | error | getFlavor() has no kIsWeb/WEB_FLAVOR branch — web always resolves to default |349350 ---351 **Summary**: 2 violations across 2 files (2 errors, 0 warnings, 0 info)352 ```353354 If nothing is found, say so explicitly: `No violations found. Flavor setup matches the catalog.`3553564. Ask which rule IDs to fix (`all`, comma-separated list, or `none`) — same pattern as357 `audit-domain-layer` Phase 5. For each selected violation:358 - `autofix_safe: true` → apply directly, show the diff.359 - `autofix_safe: false` → show the exact change, get explicit confirmation before editing.360 - `FLAVOR-GIT-01` is a hard gate, not a fix target — if it fires, stop and point back to361 Phase 2 instead of offering to "fix" it.362 Route each fix through the matching INIT phase above (e.g. a missing Android source set is a363 Phase 3 fix, a broken `getFlavor()` is a Phase 4 fix) rather than re-deriving the logic here —364 apply only the touched piece, never re-run a full phase against an already-partial project.3655. Re-scan touched files only, confirm which violations were resolved.366367Never rewrite a working, unflagged part of the project just because the AUDIT branch touched a368neighboring file.369370---371372## Notes373374- Paths inside this skill are relative to the **target Flutter project root**, not this toolkit375 repo — consistent with every other skill here.376- Out of scope: Melos/pub-workspace monorepos (detected and blocked in Phase 0, not resolved),377 macOS as a flavored target, `--dart-define-from-file` per-flavor `.env` files, and whitelabel378 app patterns (different codebase-sharing model — a separate skill's concern).379- This skill does not overlap with `sentry-init`, which reads flavor entry points380 (`lib/main_*.dart`) once they already exist but does not create them.