mcpField
F
flutter-apply-architecture-best-practices

flutter-apply-architecture-best-practices

github.com/flutter开发3.1k43.7k 次安装2 天前更新
代码仓库

关于 flutter-apply-architecture-best-practices

为 Flutter 开发提供分层架构与功能实现流程指导,适合需要组织 UI、数据访问和业务逻辑的开发者。内容强调 MVVM、Repository 模式及逐步验证。

主要功能

  1. 01MVVM 管理界面状态
  2. 02Repository 隔离数据访问
  3. 03按需提取 Use Case
  4. 04按步骤实现新功能
  5. 05通过单元测试验证

适用场景

  1. 01规划 Flutter 项目分层
  2. 02实现依赖服务的数据功能
  3. 03拆分复杂或复用业务逻辑
  4. 04验证 ViewModel 与 Repository

安装

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

$npx skills add https://github.com/flutter/agent-plugins --skill flutter-apply-architecture-best-practices

安装后在 Claude Code 中输入/flutter-apply-architecture-best-practices确认已加载。

详细介绍

Flutter 应用架构设计

目录

架构层

通过将应用划分为不同层,严格遵循关注点分离原则。切勿将 UI 渲染与业务逻辑或数据获取混在一起。

UI 层(展示层)

采用 MVVM(Model-View-ViewModel)模式管理 UI 状态和逻辑。

  • **视图:**编写可复用、精简的组件。视图中的逻辑仅限于 UI 特定操作(例如动画、布局约束、简单路由)。将所有必需的数据从 ViewModel 传入。
  • **ViewModel:**管理 UI 状态并处理用户交互。继承 ChangeNotifier(或使用 Listenable)以公开状态。向视图公开不可变的状态快照。通过构造函数将 Repository 注入 ViewModel。
数据层

采用 Repository 模式隔离数据访问逻辑,并创建单一事实来源。

  • **服务:**创建无状态类,用于封装外部 API(HTTP 客户端、本地数据库、平台插件)。返回原始 API 模型或 Result 封装对象。
  • **Repository:**使用一个或多个服务。将原始 API 模型转换为简洁的领域模型。处理缓存、离线同步和重试逻辑。向 ViewModel 公开领域模型。
逻辑层(领域层——可选)
  • **用例:**仅当应用包含会使 ViewModel 变得臃肿的复杂业务逻辑,或逻辑需要在多个 ViewModel 间复用时,才实现此层。将这些逻辑提取到专用的用例(交互器)类中,置于 ViewModel 和 Repository 之间。

项目结构

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

lib/
├── data/
│   ├── models/         # API models
│   ├── repositories/   # Repository implementations
│   └── services/       # API clients, local storage wrappers
├── domain/
│   ├── models/         # Clean domain models
│   └── use_cases/      # Optional business logic classes
└── ui/
    ├── core/           # Shared widgets, themes, typography
    └── features/
        └── [feature_name]/
            ├── view_models/
            └── views/

工作流:实现新功能

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

任务进度
  • **步骤 1:定义领域模型。**使用 freezed 或 built_value 为该功能创建不可变数据类。
  • **步骤 2:实现服务。**创建或更新服务类,以处理外部 API 通信。
  • **步骤 3:实现 Repository。**创建 Repository 以使用服务并返回领域模型。
  • 步骤 4:应用条件逻辑(领域层)。
    • *如果该功能需要复杂的数据转换或跨 Repository 逻辑:*创建一个用例类。
    • *如果该功能是简单的 CRUD 操作:*跳至步骤 5。
  • **步骤 5:实现 ViewModel。**创建继承 ChangeNotifier 的 ViewModel。注入所需的 Repository/用例。公开不可变状态和命令方法。
  • **步骤 6:实现视图。**创建 UI 组件。使用 ListenableBuilder 或 AnimatedBuilder 监听 ViewModel 的变化。
  • **步骤 7:注入依赖项。**在依赖注入容器(例如 provider 或 get_it)中注册新的服务、Repository 和 ViewModel。
  • **步骤 8:运行验证器。**为 ViewModel 和 Repository 执行单元测试。
    • *反馈循环:*运行测试 -> 查看失败项 -> 修复逻辑 -> 重新运行,直到测试通过。

示例

数据层:服务和 Repository
// 1. Service (Raw API interaction)
class ApiClient {
  Future<UserApiModel> fetchUser(String id) async {
    // HTTP GET implementation...
  }
}

// 2. Repository (Single source of truth, returns Domain Model)
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); // Transform to Domain Model
    return _cachedUser!;
  }
}
UI 层:ViewModel 和视图
// 3. ViewModel (State management and presentation logic)
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. View (Dumb UI component)
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('User not found'));
        }

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

评论

支持 Markdown