Flutter 工程构架设计(MVVM + Repository)

📅 2026/7/28 17:30:07 👁️ 阅读次数 📝 编程学习
Flutter 工程构架设计(MVVM + Repository)

Flutter 工程构架设计(MVVM + Repository)

在 Flutter 应用开发中,随着业务复杂度的提升,合理的工程架构设计显得尤为重要。MVVM(Model-View-ViewModel)结合 Repository 模式,能够有效分离关注点,提高代码的可维护性、可测试性和可扩展性。本文将从原理出发,深入剖析这一架构设计,并通过可运行的代码示例演示其实现方式。## 架构核心原理### MVVM 模式MVVM 将应用分为三层:-Model:数据层,负责业务逻辑和数据处理,通常包含数据实体和业务规则。-View:UI 层,负责展示用户界面,通常是一个 Widget。-ViewModel:连接 View 和 Model 的桥梁,负责将 Model 的数据转换为 View 可以直接使用的状态,并处理用户交互事件。MVVM 的核心优势在于数据绑定,即 ViewModel 中的状态变化会自动通知 View 更新,从而实现响应式 UI。在 Flutter 中,我们可以借助ChangeNotifierProvider来实现这一机制。### Repository 模式Repository 模式是数据访问的抽象层,它位于数据源和业务逻辑之间。数据源可以是远程 API、本地数据库(如 SQLite、Hive)或内存缓存。Repository 封装了数据获取和存储的逻辑,使得业务层(如 ViewModel)无需关心数据来源的细节,从而解耦了数据层与业务层。### 结合 MVVM 与 Repository将两者结合,我们可以构建如下分层架构:-View:仅负责 UI 渲染,通过监听 ViewModel 的状态来更新界面。-ViewModel:包含业务逻辑,调用 Repository 获取数据,并将数据转换为 View 的状态。-Repository:统一数据访问接口,协调多个数据源(如网络和缓存),确保数据一致性。这种架构的优点是:- 可测试性:ViewModel 和 Repository 可以独立测试。- 可维护性:各层职责清晰,修改数据源或 UI 不影响其他层。- 可扩展性:可以轻松替换数据源(如从 REST API 切换到 GraphQL)。## 代码示例:用户列表应用下面通过一个简单的用户列表应用来演示 MVVM + Repository 的实现。我们将使用Provider作为状态管理工具。### 1. 项目依赖首先,在pubspec.yaml中添加依赖:yamldependencies: flutter: sdk: flutter provider: ^6.0.0 http: ^0.13.0### 2. 定义数据模型创建User模型,表示用户数据:dart// models/user.dartclass User { final int id; final String name; final String email; User({required this.id, required this.name, required this.email}); // 从 JSON 解析 User 对象 factory User.fromJson(Map<String, dynamic> json) { return User( id: json['id'], name: json['name'], email: json['email'], ); }}### 3. 实现 RepositoryUserRepository封装数据访问逻辑,这里我们使用模拟数据来模拟网络请求:dart// repositories/user_repository.dartimport 'package:flutter/foundation.dart';import '../models/user.dart';class UserRepository { // 模拟从远程 API 获取用户列表 Future<List<User>> fetchUsers() async { // 模拟网络延迟 await Future.delayed(const Duration(seconds: 1)); // 模拟返回数据 return [ User(id: 1, name: 'Alice', email: 'alice@example.com'), User(id: 2, name: 'Bob', email: 'bob@example.com'), User(id: 3, name: 'Charlie', email: 'charlie@example.com'), ]; } // 模拟根据 ID 获取单个用户 Future<User> getUserById(int id) async { await Future.delayed(const Duration(milliseconds: 500)); return User(id: id, name: 'User $id', email: 'user$id@example.com'); }}### 4. 实现 ViewModelUserViewModel负责管理 UI 状态,并调用 Repository 获取数据:dart// view_models/user_view_model.dartimport 'package:flutter/foundation.dart';import '../models/user.dart';import '../repositories/user_repository.dart';// 定义 ViewModel 的状态类class UserViewModelState { final bool isLoading; final List<User> users; final String? errorMessage; UserViewModelState({ this.isLoading = false, this.users = const [], this.errorMessage, });}class UserViewModel extends ChangeNotifier { final UserRepository _repository; UserViewModelState _state = UserViewModelState(); UserViewModel(this._repository); // 暴露状态给 View 使用 UserViewModelState get state => _state; // 加载用户列表 Future<void> loadUsers() async { // 更新状态:开始加载 _state = UserViewModelState(isLoading: true); notifyListeners(); try { final users = await _repository.fetchUsers(); // 更新状态:加载成功 _state = UserViewModelState(users: users); } catch (e) { // 更新状态:加载失败 _state = UserViewModelState(errorMessage: 'Failed to load users: $e'); } notifyListeners(); } // 添加新用户(模拟业务操作) Future<void> addUser(String name, String email) async { // 这里可以调用 Repository 的添加方法 // 为简化演示,直接更新本地列表 final newUser = User(id: _state.users.length + 1, name: name, email: email); _state = UserViewModelState( users: [..._state.users, newUser], ); notifyListeners(); }}### 5. 构建 ViewView 层使用Provider获取 ViewModel,并监听状态变化:dart// screens/user_list_screen.dartimport 'package:flutter/material.dart';import 'package:provider/provider.dart';import '../view_models/user_view_model.dart';class UserListScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('User List')), body: Consumer<UserViewModel>( builder: (context, viewModel, _) { final state = viewModel.state; // 显示加载中 if (state.isLoading) { return Center(child: CircularProgressIndicator()); } // 显示错误信息 if (state.errorMessage != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(state.errorMessage!), ElevatedButton( onPressed: () => viewModel.loadUsers(), child: Text('Retry'), ), ], ), ); } // 显示用户列表 return ListView.builder( itemCount: state.users.length, itemBuilder: (context, index) { final user = state.users[index]; return ListTile( title: Text(user.name), subtitle: Text(user.email), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddUserDialog(context), child: Icon(Icons.add), ), ); } void _showAddUserDialog(BuildContext context) { final nameController = TextEditingController(); final emailController = TextEditingController(); showDialog( context: context, builder: (context) => AlertDialog( title: Text('Add User'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameController, decoration: InputDecoration(labelText: 'Name'), ), TextField( controller: emailController, decoration: InputDecoration(labelText: 'Email'), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Cancel'), ), TextButton( onPressed: () { final viewModel = context.read<UserViewModel>(); viewModel.addUser( nameController.text, emailController.text, ); Navigator.pop(context); }, child: Text('Add'), ), ], ), ); }}### 6. 注册依赖并启动应用在main.dart中,使用Provider注册 ViewModel:dart// main.dartimport 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'repositories/user_repository.dart';import 'view_models/user_view_model.dart';import 'screens/user_list_screen.dart';void main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ // 注册 UserRepository Provider<UserRepository>(create: (_) => UserRepository()), // 注册 UserViewModel,依赖 UserRepository ChangeNotifierProvider<UserViewModel>( create: (context) => UserViewModel(context.read<UserRepository>()), ), ], child: MaterialApp( title: 'MVVM + Repository Demo', home: UserListScreen(), ), ); }}## 架构扩展与最佳实践### 1. 数据源的多样化在实际项目中,Repository 可以整合多个数据源。例如,先从缓存获取数据,再异步从网络更新:dartclass UserRepository { final LocalDataSource _localDataSource; final RemoteDataSource _remoteDataSource; UserRepository(this._localDataSource, this._remoteDataSource); Future<List<User>> fetchUsers() async { // 优先从缓存获取 final cachedUsers = await _localDataSource.getUsers(); if (cachedUsers.isNotEmpty) { return cachedUsers; } // 缓存为空时从网络获取 final remoteUsers = await _remoteDataSource.fetchUsers(); await _localDataSource.saveUsers(remoteUsers); return remoteUsers; }}### 2. 错误处理与重试逻辑在 ViewModel 中,可以添加更完善的错误处理,例如重试机制:dartFuture<void> loadUsersWithRetry({int maxRetries = 3}) async { int retries = 0; while (retries < maxRetries) { try { await loadUsers(); return; // 成功则退出 } catch (e) { retries++; if (retries >= maxRetries) { _state = UserViewModelState(errorMessage: 'Failed after $maxRetries retries'); notifyListeners(); } } }}### 3. 状态管理的高级用法对于复杂场景,可以使用freezed库来生成不可变的状态类,避免手动编写copyWith方法。## 总结MVVM + Repository 架构为 Flutter 应用提供了一种清晰、可维护的工程组织方式。通过将 UI、业务逻辑和数据访问分离,开发者可以更专注于各自领域的实现。Repository 模式进一步抽象了数据来源,使得切换 API 或数据库变得简单。在实际项目中,结合 Provider、Riverpod 或 Bloc 等状态管理工具,可以充分发挥这一架构的优势。建议在项目初期就采用此架构,以避免后续重构的麻烦。