A strict Flutter architecture skill and plugin for teams that want Riverpod, Freezed, typed GoRouter, Hive CE, localization, tests, accessibility, and runtime proof enforced the same way every time.
This is not a generic Flutter tips repo. It is an opinionated policy package for building Flutter apps with one clear architecture and enough local enforcement that an agent cannot quietly drift into weaker patterns.
It is unofficial. It is not affiliated with Google, Flutter, Dart, Riverpod, or their maintainers.
The target app shape is simple:
UI widgets
-> generated Riverpod providers and notifiers
-> repositories
-> local or remote datasources
-> APIs, Hive boxes, platform plugins
Domain entities stay pure Dart.
Navigation goes through typed GoRouter routes.
User-facing copy goes through gen-l10n.
Behavior is proven with tests, lints, hooks, evals, and E2E evidence.
The main rules:
- Providers are generated with
@riverpod/@Riverpod; manual provider constructors are out. - State and domain models use sealed Freezed classes; hand-written immutable patterns are out.
- Widgets render and dispatch only. Business logic, storage, networking, and policy live behind their owning provider, notifier, repository, datasource, or service.
- Async work is guarded with
ref.mountedorcontext.mounted. - Domain primitives with meaning become Value Objects.
- Route strings are owned by typed GoRouter definitions and generated helpers.
- Visible strings, tooltips, and semantic labels come from
AppLocalizations. - Shared/realtime flows need writer plus observer proof, not screenshots.
Flutter lets teams build the same feature many ways. That flexibility is useful for experiments, but it is expensive when agents are writing production code: each extra acceptable pattern becomes another place for drift, hidden state, weak tests, or shallow wrappers.
This architecture chooses one path on purpose. It is better for this workflow because it makes ownership obvious:
| Common drift | This architecture forces |
|---|---|
| Widgets reaching into storage, HTTP, or plugins. | Widgets render localized UI and dispatch user intent only. |
| Notifiers mixing state transitions with SDK details. | Notifiers own state; repositories and datasources own IO boundaries. |
| Domain models shaped by JSON, Hive, or Flutter widgets. | Domain stays pure Dart with explicit Value Objects and invariants. |
| Route strings copied through the app. | Typed GoRouter routes are the navigation source of truth. |
| "Looks fine" UI changes without proof. | Lints, hooks, tests, accessibility checks, evals, and E2E proof all matter. |
The tradeoff is deliberate: less framework freedom, more repeatability. The goal is not to cover every valid Flutter style. The goal is to make one strict style easy to review, easy to test, and hard for an agent to accidentally weaken.
Install it as a plugin when you want enforcement. A raw SKILL.md install is
guidance-only and cannot register runtime hooks.
| Layer | What it does | Source |
|---|---|---|
| Skill | Loads the rules, trigger map, and pre-flight checklist into the agent context. | SKILL.md |
| Hooks | Blocks obvious drift after edits and before the agent stops. | hooks/ |
| Analyzer | Enforces AST-level Flutter/Riverpod rules through dart analyze. |
analysis_options.yaml |
| Dart Decimate | Gates dead code, cycles, duplication, complexity, dependency hygiene, and changed-code risk. | dart-decimate.md |
| Evals | Defines trigger, routing, and answer-quality regression cases. | evals/ |
| References | Holds detailed guidance so SKILL.md stays small and direct. |
references/ |
For Flutter/Riverpod packages, the hard project gates are package-root dart analyze with flutter_skill_lints and riverpod_lint wired under top-level
plugins: in analysis_options.yaml, plus Dart Decimate after every write
batch. Pure-Dart CLI packages use their native Dart analysis profile without
either plugin.
The README is intentionally short, so it does not list every rule. The full contract lives in SKILL.md and the task-specific files under references/. In practice, the enforcement covers more than folder layout:
| Area | What gets enforced |
|---|---|
| Analyzer setup | Flutter/Riverpod packages keep analysis_options.yaml, strict analyzer flags, generated-file exclusions, and both flutter_skill_lints and riverpod_lint; pure-Dart CLI packages use their native Dart analysis profile. |
| Code health | The project owns its full zero-finding Dart Decimate check: Hard Eng uses its installed runner; a standalone project uses its established command or a direct native scan. |
| Git push | Existing project-owned hooks and checks can block pushes. This skill preserves their owner and does not replace them. |
| Riverpod | Generated providers only, no legacy provider constructors, no ref.watch in notifier methods, no provider-derived caches in ConsumerState, and no standalone event/signal providers. |
| Async lifecycle | ref.mounted / context.mounted guards after awaits, safe finally handling, cancelled subscriptions/timers/controllers, and stale async write protection. |
| Widgets | Reusable presentation widgets render immutable inputs and emit typed callbacks; navigation, page stacks, selected records, workflow branching, providers, and infrastructure stay with screens/routes/notifiers. |
| State and domain | Sealed Freezed classes, semantic nullability, no sentinel fallbacks, Value Objects for meaningful primitives, pure domain imports, and no hand-written domain copyWith. |
| Storage and IO | Hive, SharedPreferences, secure storage, file APIs, and path-provider calls stay in local datasources, then flow through repositories. |
| Navigation | Typed GoRouter helpers own page navigation, raw route strings and named navigation are blocked, redirects are pure and matrix-tested, and modal helpers keep local dismissal separate. |
| Localization and accessibility | User-facing copy, tooltips, semantic labels, image labels, and accessibility text come from l10n; app-root text-scale clamps are blocked. |
| Performance and interaction | High-frequency inputs debounce/throttle/coalesce, expensive widgets are gated, repeated lookups use shared indexes/extensions, and broad collection watches are avoided. |
| Platform APIs | Exact-alarm permission uses flutter_local_notifications; platform-specific plugin implementations are resolved and null-checked before use. |
| Error reporting | When accepted or present, one provider-neutral Crash boundary owns Crashlytics/Sentry calls, privacy scrubbing, startup integration, and exact-release symbol proof; otherwise no provider is added. |
| Windows delivery | One secret-free exact-SHA diagnostic precedes one publisher; clean-runner codegen, CRT staging, Inno identity, bounded install phases, bootstrap/upgrade preservation, immutable readback, and pointer-last activation are proven. |
| Previews and E2E | Widget previews use deterministic fakes only; runtime E2E proves behavior with stable selectors, logs, cleanup, and writer-plus-observer proof for shared state. |
| Repo drift | Drift checks keep docs/examples honest, smoke tests exercise hook fixtures, markdown examples are parsed, and eval suites cover trigger, routing, and answer policy. |
lib/
├── core/
│ ├── extensions/
│ ├── navigation/
│ ├── services/
│ ├── theme/
│ └── widgets/
├── features/
│ └── feature_x/
│ ├── data/ # DTOs, models, local/remote datasources
│ ├── domain/ # Pure Dart entities and value objects
│ ├── repositories/ # Orchestration and model/entity mapping
│ └── presentation/ # Notifiers, screens, atoms, widgets
└── main.dart
Ownership rules are the point:
| Owner | Belongs here | Does not belong here |
|---|---|---|
| Widget | Layout, localized rendering, user dispatch. | Storage, HTTP, mutation policy, provider-derived caches. |
| Notifier | State transitions, mutation flow, durable UI status. | Hive calls, plugin calls, raw HTTP, hidden dependency construction. |
| Repository | Domain-facing contract and orchestration. | UI state, BuildContext, widget concerns. |
| Datasource | API/Hive/platform details and wire models. | Domain policy or presentation decisions. |
| Domain | Pure entities, Value Objects, invariants. | Flutter imports, JSON, Hive annotations, UI copy. |
pnpm dlx skills@latest add https://github.com/sgaabdu4/building-flutter-apps \
--skill building-flutter-apps --agent claude-code codexSupported hosts are Claude Code and Codex. Codex can invoke the standalone
skill explicitly with $building-flutter-apps.
/plugin marketplace add sgaabdu4/building-flutter-apps
/plugin install building-flutter-apps@building-flutter-appsClaude reads .claude-plugin/marketplace.json and
.claude-plugin/plugin.json, then loads hooks/hooks.json.
Invoke the skill explicitly with /building-flutter-apps:building-flutter-apps;
Claude may also select it automatically from its description.
codex plugin marketplace add sgaabdu4/building-flutter-apps --ref main
codex plugin add building-flutter-apps@building-flutter-appsCodex reads .codex-plugin/plugin.json, loads the shared skill, and discovers
hooks/hooks.json. Review and trust the hook definition in /hooks, then start
a new task.
cp <plugin-cache>/skills/building-flutter-apps/references/analysis_options.yaml ./analysis_options.yaml
mkdir -p lib/core/extensions
cp <plugin-cache>/skills/building-flutter-apps/templates/flutter/lib/core/extensions/*.dart ./lib/core/extensions/
dart pub get
dart analyzeNotes:
flutter_skill_lints: ^0.13.4andriverpod_lint: ^3.1.9are analyzer plugins. Keep both inanalysis_options.yamlunder top-levelplugins:; do not add either topubspec.yaml.- If
lib/core/extensions/already exists, merge the template files instead of overwriting them. - A healthy setup should prove that at least one
flutter_skill_lintsdiagnostic and oneriverpod_lintdiagnostic can fire. - Pure-Dart CLI packages use their native Dart analysis profile instead of this Flutter/Riverpod template.
- An installed Hard Eng project runs
python3 .hooks/hard-eng.py checkfor its configured native checks. A standalone project without an established Dart Decimate check runspnpm dlx --config.ignore-scripts=false --allow-build=dart-decimate dart-decimate@latest check . --threshold 0 --format jsonfrom its Git root. Do not add a wrapper, dependency, or global coordinator for this skill, or replace an existing hook orcore.hooksPath.
The installed skill owns the exact package constraints in core-stack.md, so the same source of truth is available to every supported agent harness. Constraint changes require a real project package solve and analyzer proof.
| Runtime | Edit hook | Stop hook | Prompt hook |
|---|---|---|---|
| Claude Code | PostToolUse |
Stop |
UserPromptSubmit |
| Codex CLI | PostToolUse |
Stop |
UserPromptSubmit |
The hook scripts no-op outside Flutter projects by walking upward for
pubspec.yaml.
The eval harnesses are deliberately split:
| File | Purpose |
|---|---|
| evals/trigger-eval.json | Checks when the skill should and should not activate. |
| evals/routing-eval.json | Checks direct progressive-disclosure routing. |
| evals/evals.json | Checks whether answers follow the policy. |
Run the local structural checks before publishing changes:
bash tool/check_drift.sh
bash tool/smoke_test.sh
python3 tool/check_skill_routing.py
ruby tool/verify_markdown_examples.rbCI runs these, the Windows installer checks, the
drift fixtures, and the upstream drift check on every pull request and push to main.
Use the canonical commands in core-stack.md.
This repo tracks the upstream flutter/skills Flutter skill set by commit and
per-skill hash in tool/upstream/flutter_skills.lock.json.
ruby tool/check_upstream_flutter_skills.rb
ruby tool/check_upstream_flutter_skills.rb --updateUse --strict-commit when CI should fail on upstream commits even if tracked
Flutter skill files did not change.
Keep changes small and enforceable:
- Put detailed guidance in
skills/building-flutter-apps/references/, not inSKILL.md. - Keep
skills/building-flutter-apps/references/core-stack.mdas the package-version SSOT. - Add or update hook fixtures when changing scanner behavior.
- Add eval cases when changing trigger behavior or answer policy.
- Run drift, smoke, markdown-example, and relevant eval checks before release.
MIT. See LICENSE.
