flutter-apply-architecture-best-practices

flutter-apply-architecture-best-practices

热门

使用推荐的分层方法(UI、逻辑、数据)架构 Flutter 应用程序。适用于构建新项目或为可扩展性重构时使用。

2525Star
152Fork
更新于 2026/6/18
SKILL.md
只读
名称
flutter-apply-architecture-best-practices
描述

使用推荐的分层方法(UI、逻辑、数据)架构 Flutter 应用程序。适用于构建新项目或为可扩展性重构时使用。

架构 Flutter 应用程序

目录

架构分层

通过将应用程序划分为不同的层来强制实现严格的关注点分离。切勿将 UI 渲染与业务逻辑或数据获取混在一起。

UI 层(表示层)

实现 MVVM(Model-View-ViewModel)模式来管理 UI 状态和逻辑。

  • 视图: 编写可重用、精简的 widget。将视图中的逻辑限制为 UI 特定的操作(例如动画、布局约束、简单路由)。从 ViewModel 传递所有所需数据。
  • ViewModel: 管理 UI 状态并处理用户交互。扩展 ChangeNotifier(或使用 Listenable)来暴露状态。向视图暴露不可变的状态快照。通过构造函数将 Repository 注入 ViewModel。

数据层

实现 Repository 模式以隔离数据访问逻辑并创建单一数据源。

  • 服务: 创建无状态类来封装外部 API(HTTP 客户端、本地数据库、平台插件)。返回原始 API 模型或 Result 包装器。
  • 仓库: 使用一个或多个服务。将原始 API 模型转换为干净的领域模型。处理缓存、离线同步和重试逻辑。向 ViewModel 暴露领域模型。

逻辑层(领域层 - 可选)

  • 用例: 仅当应用程序包含使 ViewModel 变得臃肿的复杂业务逻辑,或者逻辑需要在多个 ViewModel 之间复用时,才实现此层。将此逻辑提取到专用的用例(交互器)类中,这些类位于 ViewModel 和 Repository 之间。

项目结构

使用混合方法组织代码库:按功能分组 UI 组件,按类型分组数据/领域组件。

lib/
├── data/
│   ├── models/         # API 模型
│   ├── repositories/   # 仓库实现
│   └── services/       # API 客户端、本地存储包装器
├── domain/
│   ├── models/         # 干净的领域模型
│   └── use_cases/      # 可选的业务逻辑类
└── ui/
    ├── core/           # 共享 widget、主题、排版
    └── features/
        └── [feature_name]/
            ├── view_models/
            └── views/

工作流程:实现新功能

在向应用程序添加新功能时,请遵循以下顺序工作流程。复制清单以跟踪进度。

任务进度

  • [ ] 步骤 1:定义领域模型。 使用 freezedbuilt_value 为该功能创建不可变的数据类。
  • [ ] 步骤 2:实现服务。 创建或更新服务类以处理外部 API 通信。
  • [ ] 步骤 3:实现仓库。 创建仓库以使用服务并返回领域模型。
  • [ ] 步骤 4:应用条件逻辑(领域层)。
    • 如果该功能需要复杂的数据转换或跨仓库逻辑: 创建一个用例类。
    • 如果该功能是简单的 CRUD 操作: 跳到步骤 5。
  • [ ] 步骤 5:实现 ViewModel。 创建扩展 ChangeNotifier 的 ViewModel。注入所需的仓库/用例。暴露不可变的状态和命令方法。
  • [ ] 步骤 6:实现视图。 创建 UI widget。使用 ListenableBuilderAnimatedBuilder 监听 ViewModel 的变化。
  • [ ] 步骤 7:注入依赖。 在依赖注入容器(例如 providerget_it)中注册新的服务、仓库和 ViewModel。
  • [ ] 步骤 8:运行验证器。 执行 ViewModel 和仓库的单元测试。
    • 反馈循环: 运行测试 -> 审查失败 -> 修复逻辑 -> 重新运行直到通过。

示例

数据层:服务和仓库

// 1. 服务(原始 API 交互)
class ApiClient {
  Future<UserApiModel> fetchUser(String id) async {
    // HTTP GET 实现...
  }
}

// 2. 仓库(单一数据源,返回领域模型)
class UserRepository {
  UserRepository({required ApiClient apiClient}) : _apiClient = apiClient;
  
  final ApiClient _apiClient;
  User? _cachedUser;

  Future<User> getUser(String id) async {
    if (_cachedUser != null) return _cachedUser!;
    
    final apiModel = await _apiClient.fetchUser(id);
    _cachedUser = User(id: apiModel.id, name: apiModel.fullName); // 转换为领域模型
    return _cachedUser!;
  }
}

UI 层:ViewModel 和视图

// 3. ViewModel(状态管理和表示逻辑)
class ProfileViewModel extends ChangeNotifier {
  ProfileViewModel({required UserRepository userRepository}) 
      : _userRepository = userRepository;

  final UserRepository _userRepository;

  User? _user;
  User? get user => _user;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  Future<void> loadProfile(String id) async {
    _isLoading = true;
    notifyListeners();

    try {
      _user = await _userRepository.getUser(id);
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

// 4. 视图(哑 UI 组件)
class ProfileView extends StatelessWidget {
  const ProfileView({super.key, required this.viewModel});

  final ProfileViewModel viewModel;

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, _) {
        if (viewModel.isLoading) {
          return const Center(child: CircularProgressIndicator());
        }
        
        final user = viewModel.user;
        if (user == null) {
          return const Center(child: Text('用户未找到'));
        }

        return Column(
          children: [
            Text(user.name),
            ElevatedButton(
              onPressed: () => viewModel.loadProfile(user.id),
              child: const Text('刷新'),
            ),
          ],
        );
      },
    );
  }
}