flutter-conventions-index
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter Conventions — Index
Flutter 约定 — 索引
The front door for this Flutter/Dart app. It states the cross-cutting house rules every task obeys, then routes each concern to a focused skill. For any non-trivial task: apply the rules here, then open the specialized skill for depth. This is the only skill that names every other skill in the library.
Assumes a single-package Flutter app by default. Monorepo / pub-workspace guidance is fenced inside the skills that own it (, ) — never required for a small app.
project-structure-and-packagescodegen-and-toolchain这是 Flutter/Dart 应用的总入口。它首先规定每项任务都必须遵守的跨领域团队规则,然后将每个关注点分流到专门的 skill。对于任何非简单任务:先应用这里的规则,再打开专业 skill 获取深入指导。这是整个 skill 库中唯一列出所有其他 skill 的 skill。
默认假设为单 package Flutter 应用。Monorepo / pub-workspace 指南限定在负责相关主题的 skill 中(、),小型应用无需使用。
project-structure-and-packagescodegen-and-toolchainNon-negotiable rules
不可妥协的规则
- Feature-first, layered, downward-only. Group by feature (a folder), then by layer: View → ViewModel → Repository → Service/data. Lower layers never import upward; the dependency graph is a strict DAG. WHY: an acyclic downward graph is the only structural guard against a big ball of mud. (,
flutter-architecture)project-structure-and-packages - Widgets are dumb. No business logic, data access, math, or formatting in a widget — it reads state and renders. WHY: logic in is untestable and rebuilds unpredictably. (
build())widget-composition - One ViewModel per screen, over immutable state. A single /
Notifierowns private mutable state and exposes it as an immutable value with value equality; a transition assigns a new state, never mutates in place. WHY: immutable value + single owner makes every change diffable and testable. (AsyncNotifier)state-management-riverpod - Riverpod 3.x is state + DI. Modern /
Notifier/AsyncNotifier/Futureproviders only; providers are the DI container. NoStream, noget_it, no legacypackage:provider/StateProvider/StateNotifierProvider. WHY: one composition model, no second DI framework to reconcile. (ChangeNotifierProvider,state-management-riverpod)app-startup-and-bootstrap - Single write path. A widget or ViewModel never mutates persisted state directly; every mutation is a repository method that persists first, then republishes via a stream. WHY: one durable, observable route means state is never half-written. (,
state-management-riverpod)persistence-drift - Derive, don't store; depend on abstractions. Compute derived values on read instead of caching a second copy; program against interfaces you inject, not concretes. WHY: a duplicated source of truth drifts out of sync; injected seams keep code testable. (,
flutter-architecture)service-boundary-and-native - Immutable models, typed errors. Domain and UI state are /sealed value types with
freezedand value equality. Pure functions are total — they return uncertainty, never throw. Recoverable I/O returns a sealedcopyWith, switched exhaustively; never a swallowedResult<T, F extends Failure>. WHY: the compiler enforces every case; failures carry a stable code, not a localized string. (catch (_) {},error-handling-typed-results)dart3-idioms-and-coding-standards - Side effects behind injected interfaces. Every platform/native effect (clock, notifications, share, analytics, storage) is a Dart interface behind a provider, overridden once at the composition root, faked in tests. Read "now" from an injected , never
Clock. WHY: a global side effect is a non-deterministic, untestable dependency. (DateTime.now(),service-boundary-and-native)value-objects-money-and-units - Async is never silent. everything or handle the
awaitexplicitly; no fire-and-forget arrow callbacks. GuardFuture/BuildContextafter everymounted; dispose controllers, subscriptions, timers, and sinks. WHY: a droppedawaitswallows errors no lint catches. (Future)async-safety - Small units, extracted widget classes. Keep to the complexity-limit table owned by (method ≤30,
dart3-idioms-and-coding-standards≤80, file ≤300, positional params ≤3, logic nesting ≤3 — widget build trees may nest to ≤5 as the stated exception). Extractbuild()constclasses, neverStatelessWidgetmethods;_buildX()everywhere legal. WHY: small const subtrees rebuild less and read faster. (const,dart3-idioms-and-coding-standards,widget-composition)flutter-performance - Names carry roles. /
…Screen/…Notifier/…Repository/…Service; Effective-Dart casing verbatim; full words with units in the name; file named after its primary declaration. WHY: a grep or a filename should reveal the layer. (…Failure)naming-conventions - RTL and a11y by construction. Use directional (/
start) geometry only, never hardcoded left/right; every user string comes from an ARB viaend; label and role every semantic node; never clamp the text scaler or rely on color alone. WHY: correctness properties are cheap to build in and expensive to retrofit. (gen_l10n,i18n-rtl-l10n)accessibility-as-code - Test the shape of the code, not a fixed ratio. Pure core: fast, clock-injected + property invariants. Everything else:
package:test+flutter_test,mocktail+ProviderContainer.test()— fake the repository/services, not theoverrideWith. One acceptance test anchors the whole app. WHY: tests follow risk; the pure core carries the invariants. (Notifier,testing-strategy)widget-golden-and-a11y-testing - Strict lint is the floor; format is not negotiable. clean and
dart formatgreen before every PR, on a version-pinneddart analyze --fatal-infosinclude withvery_good_analysis/strict-casts. WHY: a green analyzer is the cheapest correctness signal you have. (strict-raw-types,lint-and-style-config)dependency-hygiene
- 以 feature 为先,分层且只能向下依赖。 先按 feature(文件夹)分组,再按层划分:View → ViewModel → Repository → Service/data。下层绝不向上导入;依赖图必须是严格的 DAG。原因是:无环的向下依赖图是防止代码变成一团乱麻的唯一结构性保障。(、
flutter-architecture)project-structure-and-packages - Widget 应保持无业务逻辑。 Widget 中不得包含业务逻辑、数据访问、计算或格式化逻辑——它只负责读取状态并进行渲染。原因是:写在 中的逻辑难以测试,而且重建行为不可预测。(
build())widget-composition - 每个屏幕使用一个 ViewModel,并采用不可变状态。 单个 /
Notifier持有私有可变状态,并以具有值相等性的不可变值对外暴露;每次状态转换都赋值一个新状态,绝不原地修改。原因是:不可变值与单一所有者使每次变更都可比较、可测试。(AsyncNotifier)state-management-riverpod - Riverpod 3.x 同时负责状态管理与 DI。 仅使用现代的 /
Notifier/AsyncNotifier/Futureproviders;providers 就是 DI 容器。不得使用Stream、get_it,也不得使用旧版package:provider/StateProvider/StateNotifierProvider。原因是:只保留一种组合模型,无需再协调第二套 DI 框架。(ChangeNotifierProvider、state-management-riverpod)app-startup-and-bootstrap - 单一写入路径。 Widget 或 ViewModel 绝不直接修改持久化状态;每次变更都通过 repository 方法完成,先持久化,再通过 stream 重新发布。原因是:一条持久且可观察的路径可确保状态永远不会被写入一半。(、
state-management-riverpod)persistence-drift - 派生而非存储;依赖抽象而非实现。 在读取时计算派生值,而不是缓存第二份副本;面向注入的接口编程,而不是具体实现。原因是:重复的数据源会逐渐失去同步;注入的接缝可保持代码可测试。(、
flutter-architecture)service-boundary-and-native - 不可变模型与类型化错误。 领域状态和 UI 状态均使用带有 和值相等性的
copyWith/sealed 值类型。纯函数是全函数(total)——返回表示不确定性的结果,绝不抛出异常。可恢复的 I/O 返回 sealed 的freezed,并以穷尽方式处理;绝不能吞掉Result<T, F extends Failure>中的错误。原因是:编译器可以强制处理每一种情况;Failure 携带稳定的错误代码,而不是本地化字符串。(catch (_) {}、error-handling-typed-results)dart3-idioms-and-coding-standards - 副作用置于注入式接口之后。 每项平台/native 副作用(时钟、通知、分享、分析、存储)都应实现为 provider 背后的 Dart 接口,在组合根统一覆盖一次,并在测试中替换为 fake。读取“当前时间”时应使用注入的 ,绝不能调用
Clock。原因是:全局副作用是一种不可预测、不可测试的依赖。(DateTime.now()、service-boundary-and-native)value-objects-money-and-units - 异步处理绝不能悄无声息。 所有异步操作都要 ,或显式处理
await;不得使用放任执行的箭头回调。每次Future之后都要检查await/BuildContext;并释放 controllers、subscriptions、timers 和 sinks。原因是:被丢弃的mounted会吞掉 lint 无法发现的错误。(Future)async-safety - 保持小单元,并提取独立的 widget 类。 遵守 所维护的复杂度限制表(方法 ≤30、
dart3-idioms-and-coding-standards≤80、文件 ≤300、位置参数 ≤3、逻辑嵌套 ≤3——widget 构建树可嵌套至 ≤5 层,作为明确例外)。提取独立的build()const类,不要使用StatelessWidget方法;所有合法位置都使用_buildX()。原因是:小型 const 子树重建更少,也更易阅读。(const、dart3-idioms-and-coding-standards、widget-composition)flutter-performance - 名称应体现角色。 使用 /
…Screen/…Notifier/…Repository/…Service;严格遵循 Effective Dart 的大小写规范;名称中应包含完整单词和单位;文件应以其中的主要声明命名。原因是:通过 grep 或文件名即可识别所属层级。(…Failure)naming-conventions - 从设计 outset 保障 RTL 与 a11y。 仅使用方向性几何属性(/
start),绝不硬编码 left/right;所有用户字符串均通过end从 ARB 获取;每个语义节点都要设置标签和角色;绝不限制 text scaler,也不能只依赖颜色传达信息。原因是:正确性属性在构建时引入成本很低,事后补救成本却很高。(gen_l10n、i18n-rtl-l10n)accessibility-as-code - 测试代码的结构,而不是固定比例。 纯核心使用快速、注入 Clock 的 和属性不变量测试。其余部分使用
package:test+flutter_test、mocktail+ProviderContainer.test()——应 fake repository/services,而不是 fakeoverrideWith。使用一个 acceptance test 锚定整个应用。原因是:测试应跟随风险分布;纯核心承载关键不变量。(Notifier、testing-strategy)widget-golden-and-a11y-testing - 严格 lint 是最低标准,格式化不容妥协。 每次 PR 前都必须确保 无差异且
dart format通过;使用在dart analyze --fatal-infos中固定版本的pubspecinclude,并启用very_good_analysis/strict-casts。原因是:分析器通过是最廉价的正确性信号。(strict-raw-types、lint-and-style-config)dependency-hygiene
Route to the right skill
路由到正确的 skill
| When you are… | Open |
|---|---|
| Orienting, unsure which skill governs the task | |
| Deciding layers, features-vs-packages, the dependency DAG | |
Scaffolding packages, barrels, | |
Writing | |
Writing a | |
Building or refactoring UI, writing | |
| Configuring the app router, redirects/auth guards, deep links, nav shells, transitions, PopScope, 404 | |
Building a | |
| Rendering loading/empty/error states, snackbars, banners, dialogs, Undo, retry | |
Responsive breakpoints, large-screen/tablet/foldable master-detail, | |
| Choosing a Dart 3 construct (sealed, records, class modifiers) | |
| Naming a class/file/variable/boolean | |
Configuring | |
| Fixing jank, narrowing rebuilds, lazy lists, off-isolate work | |
Writing | |
Writing async code, guarding | |
Modeling errors, | |
| Writing unit/widget tests, setting up fakes | |
| Writing golden, layout, RTL, or accessibility widget tests | |
Adding a Drift table, DAO, or | |
| Writing a forward-only schema migration | |
| Exporting, backing up, sharing, importing, or restoring user data | |
Setting up | |
| Running codegen before analyze | |
| Writing the GitHub Actions CI pipeline and gates | |
| Adding ARB strings, ICU plurals, RTL geometry | |
Authoring | |
| Modeling money, units, dates in a pure core | |
| Building on-device reminders/notifications | |
Writing a | |
| Adding an animation, a haptic, a celebration, or a success/error moment | |
| Generating content from a seed or date every device must reproduce identically | |
| Adding ads, a rewarded earn loop, a paywall, or an entitlement gate | |
| Wiring a native channel or platform side effect | |
Editing | |
| Scaffolding a whole feature module end to end | |
| Structuring tokens → theme → components (no aesthetic values) | |
| Re-baselining committed golden images | |
| Running the once-per-app design/QA review before release | |
| Cutting a release: versioning, signing, symbols, store declarations, rollout | |
| 当你正在…… | 打开 |
|---|---|
| 熟悉项目,或不确定哪个 skill 负责当前任务 | |
| 决定分层、feature 与 package 的边界、依赖 DAG | |
搭建 package、barrels、 | |
编写 | |
编写 | |
构建或重构 UI、编写 | |
| 配置应用 router、重定向/auth guards、deep links、导航壳、transitions、PopScope、404 | |
构建 | |
| 渲染加载/空/错误状态、snackbars、banners、dialogs、Undo、retry | |
响应式 breakpoints、大屏/平板/折叠屏 master-detail、根据宽度选择 | |
| 选择 Dart 3 语法结构(sealed、records、class modifiers) | |
| 为类/文件/变量/布尔值命名 | |
配置 | |
| 解决卡顿、缩小重建范围、延迟加载列表、isolate 外工作 | |
为公共 API 编写 | |
编写异步代码、保护 | |
建模错误、 | |
| 编写单元/widget 测试、配置 fakes | |
| 编写 golden、布局、RTL 或无障碍 widget 测试 | |
添加 Drift table、DAO 或 | |
| 编写仅向前执行的 schema migration | |
| 导出、备份、分享、导入或恢复用户数据 | |
配置 | |
| 在 analyze 之前运行 codegen | |
| 编写 GitHub Actions CI pipeline 和质量门禁 | |
| 添加 ARB 字符串、ICU plurals、RTL 几何布局 | |
编写 | |
| 在纯核心中建模金额、单位、日期 | |
| 构建设备端提醒/通知 | |
编写 | |
| 添加动画、触觉反馈、庆祝效果或成功/错误时刻 | |
| 根据 seed 或日期生成内容,并确保每台设备都生成完全一致的结果 | |
| 添加广告、rewarded earn loop、paywall 或 entitlement gate | |
| 接入 native channel 或平台副作用 | |
编辑 | |
| 端到端搭建完整 feature module | |
| 组织 tokens → theme → components(不包含审美类数值) | |
| 为已提交的 golden images 重新建立基线 | |
| 发布前执行整个应用一次的设计/QA 审查 | |
| 准备发布:版本控制、签名、symbols、商店声明、rollout | |
Recommended order when building a feature
构建 feature 时推荐的顺序
- Model + pure functions — immutable value types; total, clock-injected core → ,
dart3-idioms-and-coding-standardsvalue-objects-money-and-units - Data layer — repository = single source of truth / single write path; Drift table + DAO if persisted → ,
persistence-drifterror-handling-typed-results - Service seams — every side effect behind an injected interface →
service-boundary-and-native - ViewModel — /
Notifier,AsyncNotifier/family, intent methods →autoDisposestate-management-riverpod - View + widgets — dumb , small
ConsumerWidgetwidget classes, forms, responsive layout, RTL-safe geometry, and the loading/empty/error states before the happy path is called done →const,widget-composition,ui-states-and-feedback,forms-and-input,adaptive-layout,i18n-rtl-l10naccessibility-as-code - Wire DI + routing — providers overridden at the composition root; the feature route registers into the single →
go_router,app-startup-and-bootstrap,navigation-and-routingscaffold-feature-module - Docs + tests — on the public surface; core invariants + container/widget tests with fakes →
///,dartdoc-conventions,testing-strategywidget-golden-and-a11y-testing - Profile, then the CI gate — measure in profile mode; format/analyze/codegen/test green → ,
flutter-performance,codegen-and-toolchainci-pipeline-and-gates - Review, then ship — one end-of-build design/QA pass, then the release artifact: versioning, signing, archived symbols, store declarations, staged rollout → ,
design-review-workflowrelease-and-store-shipping
Throughout: and keep every line honest.
naming-conventionslint-and-style-config- 模型 + 纯函数——不可变值类型;全函数、注入 Clock 的核心 → 、
dart3-idioms-and-coding-standardsvalue-objects-money-and-units - 数据层——repository = 单一事实来源/单一写入路径;需要持久化时使用 Drift table + DAO → 、
persistence-drifterror-handling-typed-results - Service 接缝——每项副作用都置于注入式接口之后 →
service-boundary-and-native - ViewModel——/
Notifier、AsyncNotifier/family、intent methods →autoDisposestate-management-riverpod - View + widgets——无逻辑的 、小型
ConsumerWidgetwidget 类、表单、响应式布局、RTL-safe 几何布局;在完成 happy path 之前先实现加载/空/错误状态 →const、widget-composition、ui-states-and-feedback、forms-and-input、adaptive-layout、i18n-rtl-l10naccessibility-as-code - 连接 DI + 路由——在组合根覆盖 providers;将 feature route 注册到唯一的 中 →
go_router、app-startup-and-bootstrap、navigation-and-routingscaffold-feature-module - 文档 + 测试——为公共 API 编写 ;为核心不变量、container 和 widget 测试配置 fakes →
///、dartdoc-conventions、testing-strategywidget-golden-and-a11y-testing - 先分析性能,再设置 CI 门禁——在 profile mode 中测量;确保 format/analyze/codegen/test 全部通过 → 、
flutter-performance、codegen-and-toolchainci-pipeline-and-gates - 先审查,再发布——执行一次构建结束时的设计/QA 审查,然后生成发布产物:版本控制、签名、归档 symbols、商店声明、分阶段 rollout → 、
design-review-workflowrelease-and-store-shipping
整个过程中: 和 会确保每一行代码都经得起检查。
naming-conventionslint-and-style-configBaseline project setup
基础项目配置
yaml
undefinedyaml
undefinedanalysis_options.yaml
analysis_options.yaml
include: package:very_good_analysis/analysis_options.yaml # pin the version in pubspec
analyzer:
language:
strict-casts: true
strict-raw-types: true
```bashinclude: package:very_good_analysis/analysis_options.yaml # pin the version in pubspec
analyzer:
language:
strict-casts: true
strict-raw-types: true
```bashThe PR gate (mirror in CI — see ci-pipeline-and-gates)
The PR gate (mirror in CI — see ci-pipeline-and-gates)
dart run build_runner build --delete-conflicting-outputs # if the app uses codegen
dart format --set-exit-if-changed .
dart analyze --fatal-infos --fatal-warnings
flutter test
undefineddart run build_runner build --delete-conflicting-outputs # if the app uses codegen
dart format --set-exit-if-changed .
dart analyze --fatal-infos --fatal-warnings
flutter test
undefinedThe shape every feature repeats
每个 feature 都应复用的结构
dart
// Repository = the SINGLE WRITE PATH: persist first, then observers re-emit.
class OrderRepository {
OrderRepository(this._db, this._clock);
final AppDatabase _db;
final Clock _clock;
/// The only route an order reaches storage. Returns a typed Result, never throws.
Future<Result<void, PersistFailure>> place(OrderDraft draft) async {
try {
await _db.transaction(() async {
await _db.orderDao.insert(draft.toRow(_clock.now())); // durable FIRST
});
return const Ok(null); // watchers over the DAO re-emit on commit
} on DriftException catch (e, s) {
return Err(PersistFailure.write(cause: e, stack: s));
}
}
}
// ViewModel = one StreamNotifier per screen; state tracks EVERY DAO emission.
// Riverpod 3.x: extend the unified base class, opt into autoDispose on the provider.
class OrderListNotifier extends StreamNotifier<List<Order>> {
Stream<List<Order>> build() =>
ref.watch(orderRepositoryProvider).recent(); // re-emits on every commit
Future<void> place(OrderDraft draft) async {
final result = await ref.read(orderRepositoryProvider).place(draft);
switch (result) {
case Ok(): break; // the watched stream re-emits the new state
case Err(:final failure): state = AsyncError(failure, StackTrace.current);
}
}
}
final orderListProvider =
StreamNotifierProvider.autoDispose<OrderListNotifier, List<Order>>(
OrderListNotifier.new,
);
// View = dumb ConsumerWidget: reads one notifier, renders, no logic.
class OrderListScreen extends ConsumerWidget {
const OrderListScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(orderListProvider).when(
loading: () => const _OrdersSkeleton(),
error: (e, _) => _RetryView(onRetry: () => ref.invalidate(orderListProvider)),
data: (orders) => _OrderListView(orders: orders),
);
}
}dart
// Repository = the SINGLE WRITE PATH: persist first, then observers re-emit.
class OrderRepository {
OrderRepository(this._db, this._clock);
final AppDatabase _db;
final Clock _clock;
/// The only route an order reaches storage. Returns a typed Result, never throws.
Future<Result<void, PersistFailure>> place(OrderDraft draft) async {
try {
await _db.transaction(() async {
await _db.orderDao.insert(draft.toRow(_clock.now())); // durable FIRST
});
return const Ok(null); // watchers over the DAO re-emit on commit
} on DriftException catch (e, s) {
return Err(PersistFailure.write(cause: e, stack: s));
}
}
}
// ViewModel = one StreamNotifier per screen; state tracks EVERY DAO emission.
// Riverpod 3.x: extend the unified base class, opt into autoDispose on the provider.
class OrderListNotifier extends StreamNotifier<List<Order>> {
Stream<List<Order>> build() =>
ref.watch(orderRepositoryProvider).recent(); // re-emits on every commit
Future<void> place(OrderDraft draft) async {
final result = await ref.read(orderRepositoryProvider).place(draft);
switch (result) {
case Ok(): break; // the watched stream re-emits the new state
case Err(:final failure): state = AsyncError(failure, StackTrace.current);
}
}
}
final orderListProvider =
StreamNotifierProvider.autoDispose<OrderListNotifier, List<Order>>(
OrderListNotifier.new,
);
// View = dumb ConsumerWidget: reads one notifier, renders, no logic.
class OrderListScreen extends ConsumerWidget {
const OrderListScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(orderListProvider).when(
loading: () => const _OrdersSkeleton(),
error: (e, _) => _RetryView(onRetry: () => ref.invalidate(orderListProvider)),
data: (orders) => _OrderListView(orders: orders),
);
}
}Anti-patterns
反模式
- Data access or business logic inside a widget, a god-, or
build()helper methods — untestable and rebuild-unfriendly._buildXxx() - for app/shared state; a mutable model edited by the UI — breaks the single owner + immutable-state rule.
setState - /
get_it/legacypackage:provider/StateProvider/StateNotifierProvider— a second DI/state framework to reconcile.ChangeNotifierProvider - A widget or ViewModel writing a DAO directly, or "republish then persist" — bypasses the single write path and can expose half-written state.
- in domain code — a non-deterministic dependency; inject a
DateTime.now().Clock - A swallowed error (), an untyped exception leaking to the UI, or a fire-and-forget
catch (_) {}— silent failure no lint catches.Future - Hardcoded left/right, a raw digit string, or a user-facing literal in a widget — breaks RTL and localization by construction.
- /
!/lateto dodge honest types; blanketdynamic; fighting// ignore— hides the real defect.dart format - Optimizing without profiling, or profiling in debug mode — measures the wrong thing.
- 在 widget、臃肿的 或
build()辅助方法中访问数据或编写业务逻辑——难以测试,也不利于重建。_buildXxx() - 对应用级/共享状态使用 ;由 UI 修改可变模型——破坏单一所有者与不可变状态规则。
setState - 使用 /
get_it/旧版package:provider/StateProvider/StateNotifierProvider——引入第二套需要协调的 DI/状态框架。ChangeNotifierProvider - Widget 或 ViewModel 直接写入 DAO,或者采用“先重新发布、后持久化”——绕过单一写入路径,并可能暴露只写入一半的状态。
- 在领域代码中调用 ——形成不可预测的依赖;应注入
DateTime.now()。Clock - 吞掉错误()、让未类型化异常泄漏到 UI,或放任
catch (_) {}自行执行——这些静默失败无法被 lint 发现。Future - 硬编码 left/right、使用原始数字字符串,或在 widget 中使用面向用户的字面量——破坏 RTL 与本地化保障。
- 使用 /
!/late逃避真实类型检查;使用全局性的dynamic;与// ignore对抗——这些做法会掩盖真实缺陷。dart format - 未进行 profiling 就优化,或在 debug mode 中 profiling——得到的测量结果不具代表性。
Definition of done
完成定义
- Code sits in the correct feature/layer; the dependency graph only points downward (rules 1, 6).
- The View is dumb; one /
Notifierowns immutable state; no legacy provider or extra DI container (rules 2–4).AsyncNotifier - Every mutation goes through a repository's single write path — persist, then republish (rule 5).
- Models immutable with value equality; pure functions total; recoverable I/O is + sealed
Result, exhaustively switched; no swallowed errors (rule 7).Failure - Side effects are behind injected interfaces; "now" comes from a (rule 8).
Clock - Async is awaited/handled; /
contextguarded after awaits; controllers and subscriptions disposed (rule 9).mounted - Units within limits; widget classes extracted; applied; names carry role + units (rules 10–11).
const - RTL-safe geometry; strings in ARB; semantics labeled; text scaler not clamped (rule 12).
- Public surface has docs; core invariants + container/widget tests with fakes pass (rule 13).
/// - +
dart formatclean on a pinned lint include (rule 14).dart analyze --fatal-infos - The right specialized skill was consulted for the depth of the task.
- 代码位于正确的 feature/层级;依赖图只向下指向(规则 1、6)。
- View 不包含业务逻辑;一个 /
Notifier持有不可变状态;不使用旧版 provider 或额外 DI 容器(规则 2–4)。AsyncNotifier - 每次变更都经过 repository 的单一写入路径——先持久化,再重新发布(规则 5)。
- 模型不可变并具有值相等性;纯函数为全函数;可恢复 I/O 使用 + sealed
Result并进行穷尽处理;不吞掉错误(规则 7)。Failure - 副作用位于注入式接口之后;“当前时间”来自 (规则 8)。
Clock - 异步操作均被 await 或显式处理;await 之后保护 /
context;释放 controllers 和 subscriptions(规则 9)。mounted - 单元大小符合限制;提取独立的 widget 类;应用 ;名称体现角色和单位(规则 10–11)。
const - 几何布局支持 RTL;字符串来自 ARB;语义节点具有标签;不限制 text scaler(规则 12)。
- 公共 API 包含 文档;使用 fakes 的核心不变量、container 和 widget 测试全部通过(规则 13)。
/// - 在固定版本的 lint include 下,+
dart format无问题(规则 14)。dart analyze --fatal-infos - 已针对任务深度查阅正确的专业 skill。
Related skills
相关 skills
Every skill in this table is a sibling; open the one the routing table points to. Start with for where code goes, for how state flows, and for the / spine.
flutter-architecturestate-management-riverpoderror-handling-typed-resultsResultFailure表中的每个 skill 都与当前 skill 同级;请打开路由表所指向的 skill。先通过 了解代码应放在哪里,通过 了解状态如何流动,再通过 了解 / 主干。
flutter-architecturestate-management-riverpoderror-handling-typed-resultsResultFailureReferences
参考资料
- Effective Dart — https://dart.dev/effective-dart
- Flutter architecture recommendations — https://docs.flutter.dev/app-architecture
- Riverpod — https://riverpod.dev
- very_good_analysis — https://pub.dev/packages/very_good_analysis
- Flutter internationalization — https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization
- Effective Dart — https://dart.dev/effective-dart
- Flutter 架构建议 — https://docs.flutter.dev/app-architecture
- Riverpod — https://riverpod.dev
- very_good_analysis — https://pub.dev/packages/very_good_analysis
- Flutter 国际化 — https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization