Gate
On skill activation, emit verbatim once:
building-flutter-apps active. Pre-flight required.
Before writing any .dart code, emit verbatim:
Reading building-flutter-apps gate.
After every code change to a .dart file (or to pubspec.yaml / build.yaml / analysis_options.yaml):
- Run
dart analyzefrom the package root. Block on any ERROR or WARNING. - Emit the filled-in Pre-Flight checklist. T0 always. T1 / T2 only if their domain was touched.
- If
dart analyzeis not wired withflutter_skill_lints, run Setup before continuing.
Critical Rules
-
Use
dart analyzefrom package root, neverflutter analyzeand never path-scoped. Copy references/analysis_options.yaml to project root and wireflutter_skill_lints+riverpod_lintunderplugins:.flutter analyze libsilently drops plugin diagnostics (flutter#184190). -
Use
@riverpod/@Riverpodcodegen for every provider — state, computed, repository, datasource, service, family, stream. Never manualProvider,FutureProvider,StreamProvider,StateProvider,StateNotifierProvider,NotifierProvider,AsyncNotifierProvider,ChangeNotifierProvider. Rundart run build_runner watch --delete-conflicting-outputs. -
Guard every
awaitin notifiers and repositories withif (!ref.mounted) return;. Guard everyawaitin widgets andStatewithif (!context.mounted) return;. InsideState, neverif (mounted)— alwaysif (!context.mounted) return;. Insidefinally, use the guard formif (ref.mounted) { ... }— neverif (!ref.mounted) return;. -
Extract widgets to public classes. No
_buildXxx()helpers. Noclass _Foo extends StatelessWidget | StatefulWidget | ConsumerWidget | ConsumerStatefulWidget | HookWidget | HookConsumerWidget. Mark file-internal widgets@visibleForTesting._FooState extends State<Foo>stays private (Flutter convention — exempt). -
Use
Object?or a specific type for unknown values.dynamiconly forMap<String, dynamic>JSON. Nevervalue!— useif (value case final v?). -
Use
AppLocalizations(gen-l10n) for every user-facing string. Never hardcode UI copy in widgets, notifiers, repositories, or datasources. In widgets, bindfinal l10n = context.l10n;at the top ofbuildand usel10n.someKey; never chaincontext.l10n.someKey.*Stringsconstants only for non-user-facing IDs. For l10n config, put ARB files inarb-dir(lib/l10nby default). Generated Dart is written to${arb-dir}/${output-localization-file}unlessoutput-diris set; importapp_localizations.dartfrom that directory. -
Use
sealed classfor Freezed unions and states. Neverabstract classwith@freezed. Match with Dart nativeswitch— never Freezed.when()/.map(). For VOs in/domain/values/, annotate@Freezed(map: FreezedMapOptions.none, when: FreezedWhenOptions.none)to disable codegen of those methods entirely. Lint:freezed_disable_map_when_required. -
Never prop-drill state. Child widgets read providers directly with
ref.watch/ref.read/ref.listen. Do not pass entity / state / notifier instances through constructors. Constructor params allowed: immutable IDs (for routing/lookup), callbacks,Key, and primitive props on leaf atoms.ConsumerStatemay own lifecycle handles, not provider-derived*Cache/*Source/*DayStartfields; use computed providers or localbuildvalues. Lint:riverpod_consumer_state_derived_cache. -
Use a mixin when the same behavior appears in 2+ classes. Extract to a
mixinwith anonclause (e.g.mixin RetryMixin on AsyncNotifier<X>). Suffix the name withMixin. Copy-paste sharing across notifiers, widgets, or services is forbidden — replace with a mixin. -
Storage SDK calls live in Local Datasource, never in Notifier. Hive (
Hive.openBox,box.get/put/delete,Hive.box),SharedPreferences,flutter_secure_storage,dart:iofile ops,path_providerdirectory access — all live behind aLocal<X>Datasourceinterface, called by<X>Repository. Notifiers and widgets never importhive_ce/shared_preferences/flutter_secure_storage/dart:io/path_provider. -
Primitives →
core/extensions/. Never inline.DateTime/String/int/double/num/Duration/Iterable/BuildContextops (capitalize, timeAgo, currency, percent, clamp, format) incore/extensions/{type}_extensions.dart, barrelextensions.dart. Call.timeAgo/.capitalized/.asCurrency/.clamped(...)/.pluralized(...). Forbidden:'${s[0].toUpperCase()}${s.substring(1)}',DateTime.now().difference(...),DateTime.now().toUtc(),DateTime.timestamp(),DateTimeX.nowLocal().startOfDay,DateTimeX.nowLocal().calendarDaysBefore(...),NumberFormat.currency(...).format(...), inline.clamp(...). Raw executable strings and numbers are magic literals; move them to named constants, Value Objects, or semantic helpers. Current time helpers live inDateTimeX(nowUtc,nowLocal); current-day boundaries such asnowLocalStartOfDayand repeated local calendar windows such as "60 days ago" get semanticDateTimeXhelpers. Calendar-day helpers reconstruct date components; they do not subtractDuration(days: ...)across DST-sensitive local dates. Persisted/server timestamps MUST use UTC helpers, and local calendar bucketing of stored timestamps MUST convert to local first. Lints:datetime_now_requires_timezone_intent,avoid_magic_literals(suppress with a comment only when local raw time or literal ownership is intentional and app-specific). Why: SSOT — one fix updates every call site. Apply: 2+ uses → extension. Domain entities NEVER importcore/extensions/(outer dep,arch_domain_importERROR). Domain derive via entity getter (one-off) OR Value Object (cross-entity — Rule 12). See extensions-utilities.md. -
Wrap domain primitives in Value Objects. Domain-meaning
double/int/String(unit, currency, measure, identity, format) → sealed Freezed VO in/domain/values/. Raw redirects PRIVATE (._meters/._raw); public factories MUST contain explicit guards in body (if (v.isNaN) throw ArgumentError.value(...),if (!v.isFinite) throw ..., domain assertions), NEVER passthrough (factory X.unit(T v) => X._unit(v);is rejected — same risk as a public raw redirect). No named primitive factories on domain entities — convert at data/notifier/import boundaries. No hand-writtencopyWithin/domain/. Hive collision: Hive Models in/data/models/hold primitives only; VOs live on domain Entity, mapper bridges. Never change ctor param types or order on a@GenerateAdapters-registered class with shipped user data — silent box corruption,dart analyzeblind to disk. Apply: primitive in 2+ entities → VO. Baredouble distanceMetersat entity boundary = smell. See value-objects.md, hive-persistence.md. Lints:vo_public_raw_constructor(catches public redirect AND passthrough),domain_entity_primitive_factory,domain_custom_copy_with,hive_field_no_vo_type. -
Keep typed GoRouter routes as the navigation SSOT. Define routes once with
go_router_builder, then navigate with generated route helpers such asSomeRoute(...).go(context)andSomeRoute(...).push<T>(context). Keep route paths inside route definitions and generated helpers. Local sheets/dialogs use semantic helpers andNavigator.popfor dismissal. Navigation lints enforce typed routes, local modal helpers, and typed fallback behavior.
Trigger Map
Before writing code in any row below, output Reading: <ref-name> and read the listed reference(s).
| Touching | Read |
|---|---|
Notifier, AsyncNotifier, mutation method, ref.read / ref.watch / ref.listen, `_ensur |