Building Flutter Apps

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.

What It Enforces

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.mounted or context.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.

Why This Is Opinionated

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.

How Enforcement Works

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/

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 Flutter/Dart write batch.

Enforcement Coverage

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 analysis_options.yaml exists, strict analyzer flags stay on, generated files are excluded, and both flutter_skill_lints and riverpod_lint are proven active.
Code health Dart Decimate runs one full zero-finding JSON scan per affected Git root; changed/base/baseline/audit modes cannot hide inherited findings.
Git push The canonical deterministic gate blocks pushes when Dart Decimate reports findings or a tool/config failure.
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.

Architecture

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.

Install

Standalone agent skill

npx skills add https://github.com/sgaabdu4/building-flutter-apps --skill building-flutter-apps

Codex can invoke it explicitly with $building-flutter-apps; other harnesses use their own skill-selection syntax.

Claude Code

/plugin marketplace add sgaabdu4/building-flutter-apps
/plugin install building-flutter-apps@building-flutter-apps

Claude 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 CLI

codex plugin marketplace add sgaabdu4/building-flutter-apps --ref main
codex plugin add building-flutter-apps@building-flutter-apps

Codex 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.

Copilot CLI

copilot plugin marketplace add sgaabdu4/building-flutter-apps
copilot plugin install building-flutter-apps@building-flutter-apps

Copilot reads .github/plugin/marketplace.json and root plugin.json, then loads hooks/hooks.copilot.json.

Bootstrap A Flutter Project

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 analyze
python3 "$HOME/.agents/skills/deterministic-checks/scripts/dart_decimate_gate.py" --package . --timeout 600

Notes:

  • flutter_skill_lints is an analyzer plugin. Keep it only in analysis_options.yaml under plugins:; do not add it to pubspec.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_lints diagnostic and one riverpod_lint diagnostic can fire.
  • Invoke the global canonical deterministic gate; it owns bounded npx --yes dart-decimate@latest execution. Do not run the scanner raw, copy a runtime or adapter into the project, add a project dependency or tool bundle, replace other checks, or override core.hooksPath.

What's Included

Core Stack

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.

Hook Events

Runtime Edit hook Stop hook Prompt hook
Claude Code PostToolUse Stop UserPromptSubmit
Codex CLI PostToolUse Stop UserPromptSubmit
Copilot CLI postToolUse agentStop userPromptSubmitted

The hook scripts no-op outside Flutter projects by walking upward for pubspec.yaml.

Reference Guide

Topic File
Architecture and layers references/architecture.md
Analyzer setup references/analysis-options.md
Dart Decimate references/dart-decimate.md
Atomic UI and accessibility references/atomic-design.md
Riverpod codegen references/riverpod-codegen.md
Freezed and sealed state references/freezed-sealed.md
State lifecycle references/state-management-lifecycle.md
Testing references/testing.md
Networking boundaries references/networking.md
Error reporting references/error-reporting.md
l10n and ARB files references/localization.md
Typed routing and deep links references/deep-linking.md
Common patterns references/common-patterns.md
Hive CE persistence references/hive-persistence.md
Widget previews references/widget-previews.md
Runtime E2E proof references/dart-mcp-e2e-testing.md
Clean-checkout build proof references/build-reproducibility.md
Windows installer and updater delivery references/windows-installer-pipeline.md
Copyable Windows workflow topology windows-installer-workflow.yml
Copyable inno_bundle pubspec settings inno-bundle-pubspec.yaml

Evals And Proof

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/dart_decimate_gate_test.py
python3 tool/check_skill_routing.py
ruby tool/verify_markdown_examples.rb

Code Generation

Use the canonical commands in core-stack.md.

Upstream Drift

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 --update

Use --strict-commit when CI should fail on upstream commits even if tracked Flutter skill files did not change.

Contributing

Keep changes small and enforceable:

  • Put detailed guidance in skills/building-flutter-apps/references/, not in SKILL.md.
  • Keep skills/building-flutter-apps/references/core-stack.md as 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.

License

MIT. See LICENSE.