mcpField
F
flutter-conventions-index

flutter-conventions-index

github.com/zakariaf开发63149 次安装5 周前更新
代码仓库

关于 flutter-conventions-index

flutter-conventions-index 是 Flutter/Dart 应用开发的入口技能,汇总通用架构与编码规则,并将实现、测试和发布等任务指向专门技能。

主要功能

  1. 01按功能划分的分层架构规则
  2. 02Riverpod 状态与依赖注入约定
  3. 03UI、异步与无障碍指南
  4. 04按任务索引专门技能
  5. 05功能实现步骤

适用场景

  1. 01查找任务适用的技能
  2. 02开始实现新功能
  3. 03审查全应用 Flutter 约定
  4. 04规划测试与发布工作

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zakariaf/flutter-skills --skill flutter-conventions-index

安装后在 Claude Code 中输入/flutter-conventions-index确认已加载。

详细介绍

Flutter 约定 — 索引

这是 Flutter/Dart 应用的入口。本文说明每项任务都必须遵守的跨领域基本规则,并将每个关注点引导至相应的专项技能。对于任何非简单任务:先遵循本文规则,再查阅相关专项技能以了解详细内容。这是本技能库中唯一列出所有其他技能的技能。

默认假设这是一个单包 Flutter 应用。Monorepo / pub-workspace 指南仅在负责相关内容的技能中说明(project-structure-and-packages、codegen-and-toolchain),小型应用无需遵循。

不容妥协的规则

  1. 以功能为先、分层、仅向下依赖。 按功能(文件夹)分组,再按层分组:视图 → ViewModel → Repository → Service/数据。下层永不向上导入;依赖图必须是严格的 DAG。原因:无环且向下的依赖图是防止代码变成一团乱麻的唯一结构性保障。(flutter-architecture、project-structure-and-packages)
  2. Widget 保持简单。 Widget 中不包含业务逻辑、数据访问、数学运算或格式化逻辑——它读取状态并进行渲染。原因:build() 中的逻辑无法测试,且重建行为不可预测。(widget-composition)
  3. 每个屏幕对应一个 ViewModel,管理不可变状态。 单个 Notifier/AsyncNotifier 持有私有可变状态,并将其作为具有值相等性的不可变值公开;状态转换时赋予新状态,绝不原地修改。原因:不可变值加单一所有者,让每次变更都可比较、可测试。(state-management-riverpod)
  4. Riverpod 3.x 同时负责状态管理和 DI。 仅使用现代 Notifier/AsyncNotifier/Future/Stream provider;provider 即 DI 容器。不使用 get_it、package:provider,也不使用旧版 StateProvider/StateNotifierProvider/ChangeNotifierProvider。原因:只采用一种组合模型,无需协调第二套 DI 框架。(state-management-riverpod、app-startup-and-bootstrap)
  5. 单一写入路径。 Widget 或 ViewModel 绝不直接修改持久化状态;每次修改都通过 Repository 方法完成,先持久化,再通过 stream 重新发布。原因:通过单一、持久且可观测的路径,确保状态不会只写入一半。(state-management-riverpod、persistence-drift)
  6. 派生而不存储;依赖抽象。 读取时计算派生值,而不是缓存第二份副本;面向注入的接口编程,而不是面向具体实现。原因:重复的数据源会逐渐失去同步;注入的接缝能让代码可测试。(flutter-architecture、service-boundary-and-native)
  7. 不可变模型,类型化错误。 Domain 和 UI 状态使用 freezed/sealed 值类型,提供 copyWith 和值相等性。纯函数必须是全函数——返回不确定性,绝不抛出异常。可恢复的 I/O 返回 sealed 类型 Result<T, F extends Failure>,并通过穷尽式分支处理;绝不使用会吞掉错误的 catch (_) {}。原因:编译器会强制处理所有情况;失败携带稳定的代码,而非本地化字符串。(error-handling-typed-results、dart3-idioms-and-coding-standards)
  8. 通过注入的接口隔离副作用。 所有平台/原生副作用(时钟、通知、分享、分析、存储)都通过 Dart 接口实现,由 provider 提供,在组合根处统一覆盖,并在测试中使用伪实现。通过注入的 Clock 读取当前时间,绝不调用 DateTime.now()。原因:全局副作用是不可确定、无法测试的依赖。(service-boundary-and-native、value-objects-money-and-units)
  9. 异步操作绝不悄无声息。 对所有操作使用 await,或显式处理 Future;不使用即发即弃的箭头回调。每次 await 后检查 BuildContext/mounted;释放控制器、订阅、计时器和 sink。原因:被丢弃的 Future 会吞掉任何 lint 都检测不到的错误。(async-safety)
  10. 保持单元精简,提取 Widget 类。 遵守 dart3-idioms-and-coding-standards 规定的复杂度上限表(方法 ≤30、build() ≤80、文件 ≤300、位置参数 ≤3、逻辑嵌套 ≤3——Widget 构建树可按规定的例外嵌套至 ≤5)。提取 const StatelessWidget 类,绝不使用 _buildX() 方法;凡允许之处都使用 const。原因:小型的 const 子树重建更少,阅读也更快。(dart3-idioms-and-coding-standards、widget-composition、flutter-performance)
  11. 名称体现职责。 使用 …Screen/…Notifier/…Repository/…Service/…Failure;严格遵循 Effective Dart 的大小写规范;使用完整单词,并在名称中注明单位;文件名应与其主要声明一致。原因:通过 grep 搜索或查看文件名,就应能看出所属层级。(naming-conventions)
  12. 从设计上支持 RTL 和无障碍访问。 仅使用方向性(start/end)几何属性,绝不硬编码左/右;所有用户可见字符串都通过 gen_l10n 从 ARB 文件获取;为每个语义节点标注标签和角色;绝不限制文本缩放,也不单靠颜色传达信息。原因:正确性属性很容易从一开始就融入设计,却很难事后补上。(i18n-rtl-l10n、accessibility-as-code)
  13. 测试代码结构,而非固定比例。 纯核心逻辑:使用注入时钟的快速 package:test 测试和属性不变量。其他所有内容:使用 flutter_test 和 mocktail、ProviderContainer.test() 和 overrideWith——伪造 Repository/Service,而不是 Notifier。通过一个验收测试覆盖整个应用。原因:测试应针对风险;不变量由纯核心逻辑承担。(testing-strategy、widget-golden-and-a11y-testing)
  14. 严格 lint 是底线;格式不可妥协。 每次 PR 前,确保 dart format 无问题且 dart analyze --fatal-infos 通过,并使用版本固定且包含 strict-casts/strict-raw-types 的 very_good_analysis 配置。原因:分析器通过是你能获得的成本最低的正确性信号。(lint-and-style-config、dependency-hygiene)

前往正确的技能

当你需要……打开
了解概况,不确定哪个技能适用于当前任务flutter-conventions-index(本技能)
决定分层、功能与包的划分、依赖 DAGflutter-architecture
搭建包、barrel、lib/ 与 lib/src/、工作区布局project-structure-and-packages
编写 main()、错误处理器、DI 覆盖项、预热,以及应用生命周期中的后台/恢复时刷新app-startup-and-bootstrap
编写 Notifier/AsyncNotifier、family/autoDispose、单一写入路径state-management-riverpod
构建或重构 UI、编写 build()、布局/内边距widget-composition
配置应用路由器、重定向/身份验证守卫、深层链接、导航 Shell、过渡效果、PopScope、404navigation-and-routing
构建 Form、同步/异步字段验证、焦点遍历、键盘操作、输入格式化器forms-and-input
呈现加载/空/错误状态、Snackbar、横幅、对话框、撤销、重试ui-states-and-feedback
响应式断点、大屏幕/平板/折叠屏主从布局、按屏幕宽度选择 NavigationRail 或 BottomNavigationBaradaptive-layout
选择 Dart 3 语言结构(sealed、records、类修饰符)dart3-idioms-and-coding-standards
为类/文件/变量/布尔值命名naming-conventions
配置 analysis_options.yaml、Lint 严重级别lint-and-style-config
修复卡顿、缩小重建范围、使用惰性列表、在 Isolate 外执行工作flutter-performance
为公共 API 编写 /// 文档注释dartdoc-conventions
编写异步代码、保护 context/mounted、处理释放async-safety
对错误建模、使用 Result/Failure、确保数据不会丢失的流程error-handling-typed-results
编写单元/Widget 测试、设置 Faketesting-strategy
编写 Golden、布局、RTL 或无障碍 Widget 测试widget-golden-and-a11y-testing
添加 Drift 表、DAO 或 .watch 流persistence-drift
编写只向前的架构迁移run-migration
导出、备份、分享、导入或恢复用户数据data-export-and-restore
设置 build_runner、build.yaml、生成代码策略codegen-and-toolchain
在 analyze 前运行代码生成run-codegen
编写 GitHub Actions CI 流水线和门禁ci-pipeline-and-gates
添加 ARB 字符串、ICU 复数形式、RTL 几何布局i18n-rtl-l10n
编写 Semantics、目标区域、遍历顺序、文本缩放accessibility-as-code
在纯核心中对金额、单位、日期建模value-objects-money-and-units
构建设备端提醒/通知local-notifications-scheduler
编写 CustomPainter + 手势命中测试custom-canvas-and-gestures
添加动画、触觉反馈、庆祝效果或成功/错误时刻motion-and-haptics
根据种子或日期生成内容,确保所有设备上的结果完全一致seeded-determinism-and-golden-vectors
添加广告、激励获取循环、付费墙或权益门禁ads-and-iap-monetization
接入原生通道或平台副作用service-boundary-and-native
编辑 pubspec/lockfile、审查新依赖dependency-hygiene
从头到尾搭建完整的功能模块scaffold-feature-module
规划令牌 → 主题 → 组件(不含美学数值)design-system-structure
重新基准化已提交的 Golden 图像run-goldens-rebaseline
发布前进行每个应用仅一次的设计/QA 审查design-review-workflow
准备发布:版本控制、签名、符号文件、商店声明、分阶段推出release-and-store-shipping

构建功能时的推荐顺序

  1. 模型 + 纯函数 — 不可变值类型;全定义、由时钟注入的核心 → dart3-idioms-and-coding-standards、value-objects-money-and-units
  2. 数据层 — 仓库 = 唯一事实来源 / 唯一写入路径;需要持久化时使用 Drift 表 + DAO → persistence-drift、error-handling-typed-results
  3. 服务边界 — 每项副作用都通过注入的接口实现 → service-boundary-and-native
  4. ViewModel — Notifier/AsyncNotifier、family/autoDispose、意图方法 → state-management-riverpod
  5. 视图 + 小部件 — 简单的 ConsumerWidget、小型 const 小部件类、表单、响应式布局、适配 RTL 的几何布局;在认为主流程已完成之前,先处理加载 / 空 / 错误状态 → widget-composition、ui-states-and-feedback、forms-and-input、adaptive-layout、i18n-rtl-l10n、accessibility-as-code
  6. 接入 DI + 路由 — 在组合根中覆盖 providers;功能路由注册到唯一的 go_router → app-startup-and-bootstrap、navigation-and-routing、scaffold-feature-module
  7. 文档 + 测试 — 为公共接口编写 ///;使用伪对象测试核心不变量 + 容器 / 小部件 → dartdoc-conventions、testing-strategy、widget-golden-and-a11y-testing
  8. 性能分析,然后通过 CI 门禁 — 在 profile 模式下测量;格式化 / 分析 / 代码生成 / 测试全部通过 → flutter-performance、codegen-and-toolchain、ci-pipeline-and-gates
  9. 评审,然后发布 — 在构建结束时进行一次设计 / QA 检查,然后准备发布产物:版本管理、签名、归档符号、商店声明、分阶段发布 → design-review-workflow、release-and-store-shipping

全程遵循:naming-conventions 和 lint-and-style-config,确保每一行代码都规范。

基线项目设置

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

每个功能都遵循的结构

// 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>> {
  @override
  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});
  @override
  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),
    );
  }
}

反模式

  • 在小部件中进行数据访问或业务逻辑处理,编写臃肿的 build(),或使用 _buildXxx() 辅助方法 — 难以测试,也不利于重建。
  • 使用 setState 管理应用 / 共享状态;由 UI 编辑可变模型 — 违反单一所有者 + 不可变状态规则。
  • 使用 get_it/package:provider/旧版 StateProvider/StateNotifierProvider/ChangeNotifierProvider — 引入第二套 DI / 状态框架,增加协调成本。
  • 由小部件或 ViewModel 直接写入 DAO,或采用“重新发布后再持久化” — 绕过唯一写入路径,可能暴露只写入一半的状态。
  • 在领域代码中使用 DateTime.now() — 这是一项非确定性依赖;应注入 Clock。
  • 吞掉错误(catch (_) {})、让无类型异常泄漏到 UI,或启动一个不等待结果的 Future — 这类静默故障没有 lint 能捕获。
  • 硬编码左右方向,使用原始数字字符串,或在小部件中写入面向用户的字面文本 — 从设计上就无法适配 RTL 和本地化。
  • 使用 !/late/dynamic 来规避严格的类型约束;一概使用 // ignore;对抗 dart format — 这些做法会掩盖真正的问题。
  • 未经性能分析就优化,或在 debug 模式下进行性能分析 — 测量的对象不对。

完成定义

  • 代码位于正确的功能/层中;依赖图只指向下层(规则 1、6)。
  • View 保持轻量;由一个 Notifier/AsyncNotifier 管理不可变状态;不使用旧版 Provider 或额外的 DI 容器(规则 2–4)。
  • 所有变更都通过仓库的唯一写入路径进行——先持久化,再重新发布(规则 5)。
  • 模型不可变且支持值相等;纯函数完备;可恢复的 I/O 使用 Result + 密封的 Failure,并通过穷尽式 switch 处理;不吞掉错误(规则 7)。
  • 副作用通过注入的接口实现;“当前时间”来自 Clock(规则 8)。
  • 异步操作均已等待/处理;await 后检查 context/mounted;控制器和订阅均已释放(规则 9)。
  • 单元符合限制;提取 Widget 类;应用 const;名称体现角色和单位(规则 10–11)。
  • 几何布局支持 RTL;字符串放在 ARB 中;语义标签齐全;不限制文本缩放(规则 12)。
  • 公共接口有 /// 文档;核心不变量以及容器/Widget 测试均使用 fake 并通过(规则 13)。
  • 在固定的 lint 配置文件中,dart format + dart analyze --fatal-infos 均无问题(规则 14)。
  • 已根据任务深度查阅合适的专项技能。

相关技能

此表中的每项技能都是同级技能;请打开路由表指向的技能。从 flutter-architecture 开始,了解代码应放在哪里;再看 state-management-riverpod,了解状态如何流转;最后看 error-handling-typed-results,了解 Result/Failure 主干。

参考资料

评论

支持 Markdown