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.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.
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_lintsis an analyzer plugin. Keep it only inanalysis_options.yamlunderplugins:; do not add it 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. - Invoke the global canonical deterministic gate; it owns bounded
npx --yes dart-decimate@latestexecution. 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 overridecore.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
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 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.
License
MIT. See LICENSE.
No comments yet
Be the first to share your take.