关于 flutter-architecture
为 Flutter 与 Dart 应用提供基于 Clean Architecture 的开发规范和功能实现流程,面向需要清晰分层、数据流和错误处理的开发者。内容涵盖从数据源到 UI 的职责划分及新功能清单。
主要功能
- 01按功能组织 Clean Architecture 分层
- 02划分 API、领域和 UI 数据模型
- 03使用 DataSource 与 Repository 处理数据
- 04规定 BLoC 状态管理和依赖注入流程
- 05采用 Dart 3 模式与分层错误处理
- 06提供平台通道及原生集成规范
适用场景
- 01规划 Flutter 功能的目录和架构
- 02实现 API 数据到 UI 状态的映射
- 03为新功能安排逐层开发步骤
- 04规范原生通道和错误处理
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill flutter-architecture安装后在 Claude Code 中输入/flutter-architecture确认已加载。
详细介绍
技术栈
- 使用 Flutter 进行跨平台开发
- 使用 Dart 作为主要编程语言
- 使用 bloc 进行状态管理
- 使用 injectable 进行依赖注入
- 使用 Dart Mappable 定义不可变数据模型
- 使用 Dio 进行 HTTP 网络通信
- 使用 isar 作为本地数据库
- 使用 Firebase 提供后端服务
整洁架构
- 领域纯粹性:
domain层必须是纯 Dart。不得导入package:flutter。 - 层级依赖:
Presentation -> Domain <- Data。Data 层实现 Domain 接口。 - Feature-First 2.0:严格区分
DataSources(外部/原始数据)与Repositories(领域抽象)。
目录结构
- 按照基于功能的整洁架构模式组织代码(
featureA/bloc、featureA/models、featureA/views) - 将跨功能组件放在
core目录中 - 按类型将共享组件分组放在
core/views/widgets中
三层数据模型模式
-
API 层(ItemApiModel)
- 表示从服务端接收的原始数据结构
- 包含 API 提供的所有字段
- 应与 API 契约完全一致
-
领域层(Item)
- 表示内部数据模型
- 只包含业务逻辑所需的字段
- 去除不必要的 API 字段
-
UI 层(ItemUiState)
- 表示针对 UI 渲染优化的数据模型
- 包含已解析和格式化、可直接显示的数据
- 负责处理所有 UI 专属转换
数据模型规则
- 使用 Dart Mappable 定义不可变 UI 状态
- 每一层都应有自己的类型定义
- UI 层应使用 UI 状态数据模型,绝不能直接使用领域模型或 API 模型
- UI 状态模型应由领域模型派生,而不是由 API 模型派生
- 领域模型应由 API 模型派生,而不是由 UI 状态模型派生
Repository 与 DataSource 模式
- Repository 在各数据源之间进行协调(返回领域模型)
- 数据源访问原始数据(返回 API 模型)
- Repository 应作为真实数据源,并返回领域模型
- DataSources 必须只包含原始 SDK/API 调用,不得包含映射或业务逻辑。
- 除 DataSources 外,不得直接调用后端 SDK/API
数据流
UI Event → BLoC (emit Loading) → Repository → DataSource (API/SDK)
↓
Response → Repository (map to Domain Entity) → BLoC (emit Success/Error) → UI
Dart 3 语言特性
- 密封类:使用
sealed class表示领域层故障,以便在各层进行穷尽式模式匹配。 - 记录:在定义完整类显得过于繁琐时,使用记录进行轻量级多值返回(例如
(String name, int age))。 - If-Case 模式:进行带绑定的空值检查时,优先使用
if (value case final v?),而不是if (value != null)。 - 类修饰符:使用
final、interface、base和sealed类修饰符来表达 API 意图。
错误处理
- 函数式错误处理:使用
Either<Failure, T>或Result<T>密封类。绝不能跨层边界抛出异常。 - 模式匹配:在 UI 和 BLoC 中使用 Dart 3.x
switch表达式,穷尽处理所有密封类状态。 - 在需要时抛出错误,并在适当的边界处捕获
- 记录错误及其上下文
- 在 UI 中向用户显示友好的错误消息
- 避免静默失败;始终处理或传递错误
平台通道与原生集成
- 使用
MethodChannel执行一次性的原生代码调用(例如,读取设备信息、触发原生 API) - 使用
EventChannel接收从原生端到 Flutter 的连续数据流(例如,传感器数据、连接状态变化) - 将所有通道代码放在相关功能专用的
platform/目录中 - 将通道名称定义为常量:
static const channel = MethodChannel('com.app.feature/method') - 将所有通道调用封装在 DataSource 中,绝不能直接从 BLoC 或 UI 调用
MethodChannel - 对于未实现该通道的平台,妥善处理
MissingPluginException - 使用
defaultTargetPlatform检查来保护特定平台的行为
FFI(外部函数接口)
- 使用
dart:ffi处理对性能要求较高的原生 C/C++ 代码 - 在单独的类中定义绑定,并提供清晰的文档说明
- 跨多个软件包共享原生代码时,优先使用 Federated Plugins
特定平台的代码
- 使用
Platform.isAndroid/Platform.isIOS进行运行时检查(导入dart:io) - 对于 Web,使用
package:flutter/foundation.dart中的kIsWeb - 尽可能优先使用自适应组件(
Switch.adaptive、Slider.adaptive),而不是手动进行平台检查
编码指南与维护
- 简洁性:文件保持在 300 行以内,函数保持在 50 行以内。每个类的公开方法少于 10 个。
- 强类型:严格禁止使用
dynamic。请使用Object?或显式类型。 - 守卫子句:使用提前返回(例如,
if (user == null) return;)减少嵌套并提升可读性。 - 可释放对象的生命周期:
TextEditingController、ScrollController、FocusNode、StreamSubscription、AnimationController等,必须在initState()中使用late初始化,并在dispose()中释放。 - 禁止使用打印语句:严格禁止使用
print()。所有日志记录都使用AppLogger。 - 复用:将多次使用的组件或代码块提取到
core/views/widgets或工具类中。
文档
- 说明原因,而非内容:注释必须解释设计依据(意图),而非代码的功能。
- 公开 API:使用三斜线(
///)注释为公开类和方法编写文档。 - 历史记录:不要添加版本历史或“修复者”注释。Git 才是事实来源。
工作流:实现新功能
为应用添加新功能时,请按顺序遵循以下工作流。复制此检查清单以跟踪进度。
任务进度
- 步骤 1:定义领域模型。 使用 Dart Mappable 为该功能创建不可变数据类。
- 步骤 2:实现数据源。 创建或更新 DataSource 类,以处理原始 SDK/API 调用(Dio、Isar、Firebase)。
- 步骤 3:实现仓库。 创建 Repository 以使用数据源并返回纯领域模型。
- 步骤 4:实现 BLoC/Cubit。 创建状态管理。注入所需的 Repository。公开不可变 UI 状态。
- 步骤 5:实现 UI。 创建 View 组件。使用
BlocBuilder或BlocConsumer监听状态变化。 - 步骤 6:注入依赖项。 在依赖注入容器(
injectable)中注册新的 DataSource、Repository 和 BLoC。
评论
支持 Markdown