Idiomatic Flutter and Dart rules: feature-first layout, Riverpod, freezed, go_router, const correctness.
Scanned 8/31/2026
Install to Claude Code
npx -y skills add uwuclxdy/agenticat --skill clean-flutter --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Clean Flutter?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/uwuclxdy-clean-flutter)More formats (shields.io, HTML) on the badges page.
---
name: clean-flutter
description: "Idiomatic Flutter and Dart rules: feature-first layout, Riverpod, freezed, go_router, const correctness."
when_to_use: "Use when writing or reviewing Dart, widgets, `BuildContext`, `build_runner`, or state management."
metadata:
author: uwuclxdy
version: "1.4"
---
# Clean Flutter
Flutter and Dart conventions for writing, reviewing, and refactoring. The core rules below always apply. Load the one reference file matching the task's domain; don't load them all.
If the `clean-code` skill is installed, its language-agnostic principles (function size, naming hygiene, comment discipline) still apply. Where generic advice conflicts with Flutter idiom, this skill wins: "replace switch with polymorphism" maps to exhaustive `switch` over a sealed class, which in Dart is the intended tool, not a smell.
| Task touches | File |
|---|---|
| Project layout, MVVM layering, repository boundaries, `Result` | `references/architecture.md` |
| Providers, `Notifier`/`AsyncNotifier`, `ref.watch`/`read`/`listen`, DI, autoDispose | `references/state-management.md` |
| Routes, deep links, nested or guarded navigation | `references/navigation.md` |
| Data models, freezed, JSON serialization, build_runner workflow | `references/models-serialization.md` |
| Writing tests: unit, widget, golden, integration | `references/testing.md` |
| Rebuild storms, async-gap crashes, layout overflow, `dynamic` leaks | `references/pitfalls.md` |
| Picking a package (storage, network, forms, i18n, background), pubspec hygiene | `references/packages.md` |
## Toolchain & Setup
- Pin the Flutter SDK with fvm (`.fvmrc`, `fvm use`) and commit the pin so every machine and CI build the same stable SDK.
- `very_good_analysis` in `analysis_options.yaml` as the lint floor. It layers `strict-casts`, `strict-raw-types`, and `strict-inference` on top of `flutter_lints`, catching the exact classes of mistake generated code produces (implicit `dynamic`, unchecked casts). Fix warnings; never loosen a rule to silence one. `flutter analyze` stays clean in CI.
- Codegen (`freezed`, `json_serializable`, `riverpod_generator`, `retrofit`) runs through `dart run build_runner build --delete-conflicting-outputs`. Generated `*.g.dart` and `*.freezed.dart` are build output: regenerate after editing the annotated source, never hand-edit them. Detail in `references/models-serialization.md`.
## Widgets & Build
- `build()` is pure: no side effects, no I/O, no allocation you can hoist out. It runs often; assume every rebuild is free to happen at any time.
- `const` wherever the analyzer allows (`prefer_const_constructors`). A `const` widget is skipped on rebuild, the cheapest perf win and the one generated code drops most.
- Extract widget subtrees into their own `StatelessWidget`/`ConsumerWidget` classes, not `Widget _buildFoo()` methods. A helper method rebuilds with its parent and can't be `const`; a widget class gets its own rebuild boundary and const-ness.
- Compose small widgets. A deep `build` method that could be five classes is the Flutter equivalent of a 200-line function.
- `Key`s only where identity matters (reordered or inserted list items, state preserved across a position change). Don't scatter keys nothing reads.
## State
- Riverpod with codegen (`@riverpod`) is the default for both state and DI. `ref.watch` in `build` to react to changes; `ref.read` in callbacks and event handlers for a one-shot read. Watching in a callback re-subscribes on every call; reading in `build` misses updates. Detail in `references/state-management.md`.
- Push state to the smallest widget that needs it. Scope `ref.watch` with `.select(...)` so a one-field change doesn't rebuild the whole screen.
- `setState` is fine for genuinely local ephemeral state (a toggle, focus, an animation flag). Not for app state or anything shared across widgets.
- Package-specific deprecation/trend verdicts (GetX, Isar, old Hive, golden_toolkit) live in `references/pitfalls.md`'s Named traps list, even when the package's own topic has a dedicated reference file (e.g. `state-management.md`).
## Async & Lifecycle
- After any `await` inside a `State` or callback, a `BuildContext` may be dead. Guard with `if (!context.mounted) return;` (or `if (!mounted) return;` in a `State`) before touching `context`, `Navigator`, `setState`, or a controller. `use_build_context_synchronously` flags the common cases, not all of them.
- Dispose what you create. `TextEditingController`, `ScrollController`, `AnimationController`, `FocusNode`, and `StreamSubscription` all leak and can fire after teardown without a matching `dispose()`/`cancel()`.
## Errors
- Repositories catch throwing calls (network, disk, platform channels) internally and return a sealed `Result<T>` (`Ok`/`Failure`); callers `switch` on it exhaustively. The failure path becomes part of the signature instead of an invisible throw. Detail in `references/architecture.md`.
- Let genuine bugs (programmer error, broken preconditions) throw and reach crash reporting. Don't wrap them in a `try/catch` that swallows them into a silent no-op.
## Naming & Style
- `lowerCamelCase` for members and locals, `UpperCamelCase` for types, `lowercase_with_underscores` for files and directories. Match `dart format`; never hand-format around it.
- Private by default (`_name`); widen visibility only when another library needs it.
- `async`/`await` over raw `.then()` chains. Type every public signature and let strict-inference reject accidental `dynamic`.
## Pre-Submit Checklist
- [ ] `flutter analyze` clean under very_good_analysis; no rule loosened to pass
- [ ] codegen regenerated after any model/provider edit; no hand-edited `*.g.dart`/`*.freezed.dart`
- [ ] `const` on every eligible widget; subtrees extracted to classes, not `_build` methods
- [ ] no `BuildContext`/`Navigator`/`setState` after an `await` without a `mounted` guard
- [ ] every controller, subscription, and notifier disposed
- [ ] `ref.watch` in build, `ref.read` in callbacks, `ref.listen` for side effects; watch scoped with `.select`
- [ ] repository calls return `Result`; genuine bugs throw rather than being swallowed
- [ ] no unmaintained package added (pub.dev signals checked, see `references/packages.md`)
- [ ] tests fail if the logic breaks; layer chosen per the pyramid (`references/testing.md`)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!