关于 building-flutter-apps
面向 Flutter 和 Dart 开发的 skill,按场景引导编码 Agent 查阅适用规范,并规定实现与检查流程。内容涵盖 Riverpod、架构、测试、无障碍和本地化。
主要功能
- 01按场景选择参考资料
- 02Flutter 与 Riverpod 编码规范
- 03架构与状态管理指导
- 04测试、无障碍和本地化规则
- 05文件修改后的预检流程
适用场景
- 01启动或修改 Flutter 应用
- 02实现 Riverpod 状态与异步操作
- 03检查界面、无障碍或导航改动
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sgaabdu4/ building-flutter-apps --skill building-flutter-apps安装后在 Claude Code 中输入/building-flutter-apps确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/defender-installer-scan.ps1inno-bundle-pubspec.yamlinno-uninstall-settlement-sentinel.ps1windows-installer-workflow.ymlreferences/analysis_options.yamlanalysis-options.mdarchitecture.mdatomic-design.mdbuild-reproducibility.mdcommon-patterns.mdcore-stack.mddart-decimate.mddart-mcp-e2e-testing.mddart-patterns-records.mddeep-linking.mderror-reporting.mdflutter-optimizations.mdfreezed-sealed.mdhive-persistence.mdreferences/atomic-design/accessibility.mdreferences/common-patterns/debounce-gate-batch.mddelta-sync.mdlists-forms-workflows.mdmodals-navigation.mdnavigation-flow.mdrouting-app-shell.mdreferences/extensions/collections-helpers.mdcontext-ui.mdprimitive-formatting.md详细介绍
先读
- 本技能会覆盖通用的 Flutter/Dart 建议;关键规则优先于示例、公开文档和旧项目代码。
- 每个参考的
先读部分均为权威依据。 - 每批写入
.dart/pubspec.yaml/build.yaml/analysis_options.yaml后,请运行 Pre-Flight;其引用的规则/参考资料负责适用的检查。
渐进式披露门控
在编写代码前,只阅读匹配范围最窄的 Trigger Map 行;场景/子系统行负责涵盖偶然出现的技术栈/文件词汇。仅当没有匹配的场景行时,才阅读诸如 common-patterns.md 的父级参考;切勿批量读取 references/。在 Pre-Flight 中引用确切的参考。
关键规则
| ID | 规则 | 详情引用 |
|---|---|---|
| R1 | Flutter/Riverpod 包 = 首先接入 flutter_skill_lints + riverpod_lint,然后在包根目录运行 dart analyze 及其项目自有的 Dart Decimate 检查;纯 Dart 命令行 = 使用原生 Dart 分析配置,两者插件都不引入。 | analysis-options.md, dart-decimate.md, setup.md |
| R2 | 每个 provider 都使用 @riverpod / @Riverpod 代码生成;不允许手写 provider 类或旧式 provider 族。 | riverpod-codegen.md |
| R3 | 用 ref.mounted / context.mounted 防护异步间隙;finally 使用 if (ref.mounted) { ... }。 | async-mutations.md |
| R4 | Widget 必须是公开类;除 State 外,不允许 _buildXxx()、widget 顶层辅助函数或私有 widget 类。 | atomic-design.md, performance.md |
| R5 | 可空性是语义性的;不允许空值/null/bool 哨兵回退、value!、可空集合或裸的必填领域字符串。 | value-objects.md, freezed-sealed.md; Lints: domain_raw_required_string |
| R6 | 所有面向用户的字符串、工具提示、语义和可见的无障碍文案都使用 AppLocalizations。 | localization.md, accessibility.md |
| R7 | 不可变状态/实体使用 sealed Freezed,每个文件一个声明,使用原生 switch,并禁用 VO 的 map/when。 | freezed-sealed.md, value-objects.md |
| R8 | presentation/widgets/ 渲染不可变输入并发出类型化回调;屏幕/路由/notifier 负责导航、工作流、领域状态和基础设施。 | presentation-widgets.md |
| R9 | 2 个及以上类中的重复行为提取为带 on 子句的小型无状态 *Mixin。 | mixins.md |
| R10 | 存储 SDK 调用位于仓储之后的本地数据源中;生产环境的 Hive 导入使用 hive_ce_flutter。 | hive-persistence.md, architecture.md |
| R11 | 基础类型/context/集合操作位于 core/extensions/;领域层绝不导入这些扩展。 | context-ui.md, primitive-formatting.md, collections-helpers.md |
| R12 | 具有含义的领域基础类型成为经过校验的 Freezed Value Object;Hive 模型保留基础类型,由映射器桥接。 | value-objects.md, hive-persistence.md; Lints: domain_unit_primitive |
| R13 | 类型化的 GoRouter 路由是导航的 SSOT;重定向是纯解析器逻辑,并带可按 id 回退的可空 UI。 | deep-linking.md, routing-app-shell.md |
| R14 | 对话框/底部面板渲染不可变快照、弹出结果,并将变更/清理交给 notifier。 | modals-navigation.md, state-management-lifecycle.md |
| R15 | 对高频 UI、同步、持久化、远程函数、重置和查找边界进行防抖、门控和批处理。 | debounce-gate-batch.md |
| R16 | 应用外壳保持声明式;引导监听器位于同级的根 ConsumerWidget 中。 | routing-app-shell.md |
| R17 | 在退出之后保持控制流扁平;移除 return / throw / break / continue 之后不必要的 else。 | Lint: avoid_unnecessary_else_after_control_flow |
| R18 | 直接使用 onReorderItem 移除后的索引;绝不添加旧式 onReorder 适配计算。 | Lint: use_on_reorder_item_index_semantics |
| R19 | Android 精确闹钟使用 flutter_local_notifications 权限 API,而不是手动设置 intent。 | Lint: use_local_notifications_exact_alarm_permission_api |
| R20 | 在调用平台成员之前,先解析可空的平台特定插件实现。 | Lint: resolve_platform_specific_implementation_before_use |
| R21 | Widget 预览仅用于预览,使用确定性的假实现;不允许真实 HTTP/Firebase/Hive/原生插件。 | widget-previews.md |
| R22 | 运行时 E2E 通过稳定的选择器、对失败敏感的场景/日志、与主体匹配的证据、事实来源验证、清理以及必要时多角色证明来验证行为。 | dart-mcp-e2e-testing.md |
| R23 | 无障碍即 UI 正确性:本地化的工具提示/语义标签、48x48 目标、对比度、文本缩放、Text.rich。 | accessibility.md, flutter-optimizations.md |
| R24 | 如果接受或已存在远程错误报告,使用一个应用自有的 Crash 边界,且每个操作只有一个报告负责人;否则不添加任何 provider/facade。在遥测之前清理敏感数据并协调有歧义的远程结果。 | error-reporting.md, networking.md |
| R25 | Windows 安装程序交付 = 一个语义引擎 + 类型化应用配置/能力 + 最小步骤的精确 SHA 诊断 → 一个发布者;在一次构建之前保留低成本的守卫,隔离合成的保留证明,并仅激活已验证的不可变字节。 | windows-installer-pipeline.md, workflow scaffold, inno_bundle pubspec scaffold, Inno settlement sentinel, Defender scanner |
| R26 | 对暂停敏感的 Riverpod 状态仅在其持久 owner/listener 存在之后启动;投影直接监听基础状态;切换认证/表单模式会清除瞬态错误。 | notifier-structure.md, state-management-lifecycle.md, testing.md |
| R27 | 原生/自定义链接在生产者、平台注册、Flutter 交付和类型化路由之间使用统一的 URI 契约;在目标设备上证明冷/热启动 + 带签名状态的交付。 | deep-linking.md, dart-mcp-e2e-testing.md |
Trigger Map
| 涉及 | 阅读 |
|---|---|
新应用/项目脚手架,附带技术栈/包提及、main.dart、ProviderScope、MaterialApp.router、应用启动壳 | setup.md + architecture.md + routing-app-shell.md |
Notifier/AsyncNotifier 形态、同步 Notifier.build() 初始化、暂停路由/监听器启动、provider 投影、认证/表单模式错误重置、加载/进度、AsyncValue、清理 | notifier-structure.md + state-management-lifecycle.md + testing.md |
变更方法、ref.read / ref.watch / ref.listen、_ensureRepository、异步取消、ref.mounted、乐观更新、重复获取 | async-mutations.md + state-management-lifecycle.md |
Freezed 实体、密封联合、fromJson / toJson、copyWith、模型 vs 实体、用于 explicit_to_json 的 build.yaml | freezed-sealed.md |
Provider 声明、@riverpod、family、keepAlive、代码生成、Mutation<T>(实验性) | riverpod-codegen.md |
仓储、数据源、领域实体、分层架构、IHttpService、模型到实体的映射 | architecture.md |
值对象、基本类型偏执、Distance/Money/Email/Slug、领域内单位转换、跨实体基本类型、double distanceMeters/int amountCents/String email 坏味道、arch_domain_import 错误 | value-objects.md |
GoRouter、类型化路由、重定向、受认证保护的路由、router provider、context.go、深度链接、自定义 URI scheme、原生扩展/activity 链接、冷启动、导航门控 | routing-app-shell.md + deep-linking.md |
| HTTP、网络、REST、变更后从事实来源获取、长时间运行的远程函数、异步启动 + 对账、传输 id vs 领域 id | networking.md + debounce-gate-batch.md |
原子、分子、有机体、设计令牌、原子 widget、core/widgets/ 提升 | atomic-design.md |
可复用的 presentation/widgets/、widget 自有的导航/页面栈/选中实体/工作流状态、直接访问仓储/服务/provider | presentation-widgets.md |
| 无障碍、语义、tooltip、semanticLabel、图片替代文本、点击目标、对比度、文字缩放 | accessibility.md + flutter-optimizations.md |
Widget 测试、ProviderContainer.test()、UncontrolledProviderScope、fake、mock、AppWidgetKeys、事件契约测试 | testing.md |
flutter_driver、Dart MCP、Marionette MCP、E2E、integration_test、语义选择器、场景验证、截图/媒体证据、日志捕获、原生集成构建成功但设备上失败、运行时权限、插件挂起、仅 release 运行时失败 | dart-mcp-e2e-testing.md |
Hive、TypeAdapter、TypeId、box、持久化迁移、已退役字段核算 | hive-persistence.md |
Crashlytics、FirebaseCrashlytics、Sentry、sentry_flutter、DSN、错误上报、Crash.init、Crash.error、Crash.log、符号上传 | error-reporting.md |
| Mixin、能力 vs 接口、重试辅助、RNG、批量操作 | mixins.md |
服务、单例、发后即忘、abstract final class、unawaited()、Future<void> 签名 | services-and-singletons.md |
@Preview、widget_previews.dart、预览 fake、确定性预览数据 | widget-previews.md |
AppLocalizations、ARB 文件、gen-l10n、区域回退、占位符、复数 / 选择 | localization.md |
性能、构建成本、.select()、const 构造函数、ListView.builder、大列表计算 | performance.md + flutter-optimizations.md |
LayoutBuilder、RenderFlex 溢出、Row / Column 外的 Expanded / Flexible、Stack 外的 Positioned、文字缩放钳制 | layout-diagnostics.md |
| 分页、无限滚动、游标加载、搜索防抖、注册/表单验证与提交、批处理、下拉刷新 | lists-forms-workflows.md + async-mutations.md |
BuildContext 辅助、ModalRoute 当前路由检查、对话框、SnackBarUtils、从 notifier 派发 snackbar | context-ui.md |
DateTime format/diff/timeAgo/startOfDay、String capitalize/truncate/titleCase/initials/format、int / double / num clamp/pluralized/asCurrency/percent/toFixed、Duration format、NumberFormat、DateFormat、intl | primitive-formatting.md |
Iterable 查找/索引、widget 列表辅助、Debouncer、验证器、Result、扩展类型、core/extensions/ 桶导出 | collections-helpers.md |
Record (x, y)、扩展类型 ID、模式匹配、主/简洁构造函数、new()、factory()、点简写如 .center、@RecordUse | dart-patterns-records.md |
Flutter/Riverpod analysis_options.yaml、dart analyze、插件接线、riverpod_lint 版本锁定、分析器崩溃 | analysis-options.md + analysis_options.yaml |
| Skill 设置、Git pre-push、钩子/扫描器注册 | setup.md |
build_runner、缺失生成部分、干净检出、Xcode 选择、Flutter SwiftPM 生成包、Apple 设备构建、本地 vs CI 不一致 | build-reproducibility.md + core-stack.md |
| 包约束、依赖升级、生成器/分析器兼容性 | core-stack.md |
Flutter Windows 桌面打包、GitHub Actions Windows 安装程序、Inno Setup、inno_bundle、更新器/自动更新、CRT DLL、PowerShell/原生安装程序进程、安装程序/版本/AppId 失败 | windows-installer-pipeline.md + build-reproducibility.md + core-stack.md |
| Dart Decimate、死代码、循环依赖、重复代码、复杂度、依赖卫生、全量零发现扫描 | dart-decimate.md |
| 无更窄行覆盖的导航 / 表单 / 列表 / 防抖 / 路由参数回退模式 | common-patterns.md |
| 增量远程拉取、增量令牌、按表同步日期、合并/删除对账 | delta-sync.md |
| 路由参数安全、向导排序、受保护的下一步导航 | navigation-flow.md |
| 对话框 / 工作表 / 模态、快照值对象、await 后拆除、先关闭再导航、pop 回退、嵌套导航器关闭 | modals-navigation.md + state-management-lifecycle.md |
防抖 / 节流 / 合并 — TextField.onChanged、Slider.onChanged、滚动监听器、同步 saveAll、子集变更后全集合重写、持久化辅助、重置/清除哨兵保留、_userTapped 门控、build 中的 WebView / VideoPlayer、服务中的 _storage.read、ref.listenManual 禁用、keepAlive 集合监听、数据源批量加载器、零值保存保护、notifier 边界的基本类型→VO、模态辅助上的 routeSettings | debounce-gate-batch.md |
交付前检查
每次写入 .dart / pubspec.yaml / build.yaml / analysis_options.yaml 后进行内部检查:始终执行 T0;涉及状态/通知器/变更时,另执行 T1;涉及网络/E2E/流/路由时,另执行 T2。编辑循环仅分析已编辑的文件,详见 analysis-options.md;交接前仅运行一次完整的包根目录检查。
- 检查项失败 → 立即报告,并注明规则 ID 或参考文档。
- 交接 → 报告已完成的验证:阅读过的参考文档 + 运行过的检查 + 检查结果。
T0 — 核心
- Flutter/Riverpod 包:编辑循环中已编辑文件的分析无问题;交接前运行一次包根目录
dart analyze --fatal-infos,退出码为 0,并启用flutter_skill_lints+riverpod_lint;设置变更须分别证明两个插件都能产生一条诊断。纯 Dart CLI:适用原生 Dart 分析配置;两个插件均不适用。 - 当前同范围、项目自有的 Dart Decimate 结果符合 dart-decimate.md 中的要求;注明扫描范围。
- 已防护异步间隙:使用
ref.mounted/context.mounted,不使用裸mounted,且finally使用if (ref.mounted) { ... }。 - Provider、状态和 Widget 遵循规则 2-8 和 14:可复用 Widget 仅管理 UI 生命周期;屏幕/路由/通知器负责导航、工作流分支、选定的领域记录、Provider 状态和基础设施。
- 领域/数据/平台遵循规则 7、10-13、17-24、26-27:密封的 Freezed 类型、VO、数据源/仓储存储、核心扩展、类型化路由、防抖/批处理、平台 API、预览、E2E、暂停安全状态、原生链接和无障碍;如果已接受/存在错误报告,则通过单一的脱敏且仅执行一次的边界处理,否则标记为 N/A。
- 除非涉及 Windows 打包/更新程序交付,否则规则 25 = N/A;适用时,其诊断/发布者证明必须通过。
- Trigger Map 中涉及的任何行均已阅读;应由扫描器强制执行的规则须注明确切的 lint 名称。
T1 — 状态 / 通知器 / 变更
- 变更依赖通过无状态 helper/mixin 延迟解析;除可释放的生命周期所有者外,通知器本地不缓存 repo/service。
- 同步
Notifier.build()在返回前避免读取state;异步主状态使用AsyncNotifier.build;持久的同步启动不依赖其所有者/监听器存在前的微任务时序。 -
ref.onDispose()取消订阅/控制器/计时器;持久的状态、Snackbar 和 teardown 由通知器状态负责。 - 长时间运行的同步/身份验证/导入操作会防止过期写入;通知器方法内不调用
ref.watch。 - 对暂停敏感的投影直接监听基础状态;路由暂停/恢复时保留首次更新;身份验证/表单模式变更时清除临时错误。
T2 — 网络 / E2E / 流 / 路由
- 对生成、规范化、重新排序、破坏性或远程函数变更,执行真实数据源获取/协调。
- 共享/实时状态具有写入者 + 观察者的 E2E 证明,且无需手动刷新。
- 选择器使用稳定的文本/语义/工具提示或中央
AppWidgetKeys;不使用内联字符串键,也不通过坐标进行主要点击操作。 - E2E 场景状态确定且与生产行为隔离;Marionette 运行使用
lib/main.dart中由调试模式控制的绑定,Flutter Driver 运行独立的 driver-extension 入口;未知场景会失败;关键日志会导致运行失败;证据须显示断言所针对的屏幕,然后才退出应用;已验证清理操作。 - GoRouter 重定向使用通过矩阵测试的纯解析器、可空的按 ID 查询 Provider/回退 UI,以及生成的类型化路由 helper。
- 原生/自定义 URI 的生成端、Android/iOS 注册、Flutter 传递和类型化路由器共用一份经过测试的 scheme/host/path 契约;冷启动/热启动 + 已签名状态的设备路径均通过。
- 跨运行时常量、模式和函数契约均有漂移测试;不在应用根目录限制文本缩放。
评论
支持 Markdown