Checklist: Flutter Review
Review gate for Flutter/Dart screens and features. Tag findings Critical/High/Medium/Low/Nit; block on Critical/High. Complements ../standards/flutter_standards.md and ../standards/dart_coding_standards.md.
Layering
- [ ] Nothing under
domain/importspackage:flutter,dio,drift, orflutter/services.dart. - [ ] Repository interfaces live in Domain; implementations and DTOs live in Data.
- [ ] DTO ⇄ entity mapping happens in Data only; no DTO reaches Presentation.
- [ ] Widgets and notifiers call use cases or repositories, never a data source or HTTP client.
- [ ] Dependencies are injected via providers; no singletons or inline construction in business logic.
State (Critical/High)
- [ ] Screen state is an
AsyncValueor a sealed union — nobool isLoading+String? errorpair. - [ ] State objects are immutable; every update emits a new value (no in-place list/map mutation).
- [ ]
ref.watchappears only inbuild/provider bodies;ref.readonly in callbacks. - [ ] The notifier holds no
BuildContext, noNavigator, and no direct HTTP/database call. - [ ] Mutations use
AsyncValue.guardor set an explicit failure state — no error escapes unhandled. - [ ] One state library in use (Riverpod or BLoC), not both.
- [ ]
keepAlive(where used) carries a comment justifying the lifetime.
Widgets
- [ ]
build()contains no networking, persistence, or business rules and is side-effect free. - [ ]
constconstructors used wherever applicable; analyzer clean withprefer_const_constructors. - [ ]
StatefulWidgetused only for ephemeral widget-local state, not screen state. - [ ] Every controller, listener, and subscription created is disposed.
- [ ] No widget file exceeds a readable size; subwidgets extracted past ~40 lines of
build.
Navigation
- [ ] All navigation goes through the
go_routerroute table; no ad-hocNavigator.push. - [ ] Routes carry ids/primitives; a cold-start deep link resolves without a passed object.
- [ ] Auth gating is a single
redirect, not per-screen checks. - [ ] No
BuildContextused after anawaitwithout acontext.mountedguard.
Async & Errors
- [ ] No bare
catch (_) {}; every catch handles, translates, or rethrows. - [ ] Failures crossing the Data boundary are typed (sealed
Failure), not rawDioException. - [ ] No
!(bang) on data that can legitimately be absent. - [ ] No unawaited futures except an explicit
unawaited(...). - [ ] CPU-bound work runs in an isolate, not on the UI isolate.
Security
- [ ] Tokens, refresh tokens, and PII are in
flutter_secure_storage— nevershared_preferences. - [ ] No secrets, API keys, or client secrets compiled into Dart source or
--dart-define. - [ ] No token, password, auth header, or PII in any log or crash report.
- [ ] TLS validation is never disabled (
badCertificateCallbacknever returnstrueblanket).
Performance
- [ ] Long lists use
ListView.builder/slivers with stable keys. - [ ] Provider watches narrowed with
selectwhere the state object is large. - [ ] Images decoded at display size (
cacheWidth/cacheHeight). - [ ] No
Opacity/ClipRRect/ShaderMaskwrapping a large animated subtree without cause. - [ ] Any performance claim is backed by a
--profilemeasurement, not a guess.
Accessibility
- [ ] Every interactive control has a visible label or a
Semanticslabel. - [ ] Tap targets are at least 48×48dp.
- [ ] No hardcoded colors or font sizes; theme and
textThemeused so text scaling works. - [ ] Layout verified at the largest system text size — no clipping or overflow.
- [ ]
ValueKeypresent on anything a test or automation drives.
Tests
- [ ] Notifier tests use
ProviderContainerwith overrides andaddTearDown(container.dispose). - [ ] Widget tests cover loading, loaded, empty, and error for each screen.
- [ ] Finders use keys or semantics, not widget types.
- [ ] No real network, database, clock, or randomness in unit or widget tests.
- [ ] No
pumpAndSettlewhere an indeterminate progress indicator is on screen.
Verdict
- [ ] Verdict recorded: Approve / Approve-with-nits / Request-changes.
- [ ] Every Critical/High finding has a file:line and a concrete fix.