使用 Flutter 实现 MVVM 架构

涛声依旧在  金牌会员 | 2024-6-13 14:14:22 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 492|帖子 492|积分 1476

在本文中,我们将探讨如安在 Flutter 应用中实现 MVVM(Model-View-ViewModel)架构。MVVM 架构有助于保持代码整齐、可维护,同时进步开辟服从。我们将通过高维度的架构计划和具体的代码案例来介绍如安在 Flutter 中实现 MVVM。
一、什么是 MVVM 架构?

MVVM(Model-View-ViewModel)是一种软件架构计划模式,用于将业务逻辑、界面表现和用户交互分离。MVVM 架构包罗以下三个重要组件:


  • Model:负责处理数据和业务逻辑。
  • View:负责显示 UI,并将用户操作通报给 ViewModel。
  • ViewModel:负责处理 View 的输入,并更新 Model,同时关照 View 更新。
通过将这三个组件分离,我们可以更轻易地维护和扩展代码,同时保持代码整齐。
二、MVVM 架构计划

要在 Flutter 中实现 MVVM 架构,我们需要创建 Model、View 和 ViewModel 类,并使用 Flutter 的数据绑定机制将它们连接起来。这里是一个高维度的架构计划:

  • Model:创建一个包罗业务逻辑的 Model 类。这个类可以包罗数据模型、网络请求、数据库操作等。
  • ViewModel:创建一个继承 ChangeNotifier 的 ViewModel 类。这个类将处理 View 的输入,并更新 Model。同时,通过调用 notifyListeners() 关照 View 更新。
  • View:创建一个 StatefulWidget,并在其 build 方法中构建 UI。将 ViewModel 作为 ChangeNotifierProvider 的参数,以便在子组件中访问 ViewModel。
  • 数据绑定:使用 Consumer 或 context.watch() 监听 ViewModel 的变化,并在 View 中更新 UI。
三、代码案例

接下来,我们将通过一个简单的计数器应用来演示如安在 Flutter 中实现 MVVM 架构。
3.1 Model

首先,我们创建一个简单的 Counter 类作为 Model,用于存储计数器的值并举行增长操作。
  1. class Counter {
  2.   int _value = 0;
  3.   int get value => _value;
  4.   void increment() {
  5.     _value++;
  6.   }
  7. }
复制代码
3.2 ViewModel

接下来,我们创建一个继承 ChangeNotifier 的 CounterViewModel 类。这个类将处理 View 的输入,并更新 Model。同时,通过调用 notifyListeners() 关照 View 更新。
  1. import 'package:flutter/foundation.dart';
  2. import 'counter.dart';
  3. class CounterViewModel extends ChangeNotifier {
  4.   final Counter _counter = Counter();
  5.   int get value => _counter.value;
  6.   void increment() {
  7.     _counter.increment();
  8.     notifyListeners();
  9.   }
  10. }
复制代码
3.3 View

然后,我们创建一个 StatefulWidget,并在其 build 方法中构建 UI。将 ViewModel 作为 ChangeNotifierProvider 的参数,以便在子组件中访问 ViewModel。
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. import 'counter_view_model.dart';
  4. void main() {
  5.   runApp(MyApp());
  6. }
  7. class MyApp extends StatelessWidget {
  8.   @override
  9.   Widget build(BuildContext context) {
  10.     return MaterialApp(
  11.       home: ChangeNotifierProvider(
  12.         create: (context) => CounterViewModel(),
  13.         child: CounterPage(),
  14.       ),
  15.     );
  16.   }
  17. }
  18. class CounterPage extends StatefulWidget {
  19.   @override
  20.   _CounterPageState createState() => _CounterPageState();
  21. }
  22. class _CounterPageState extends State<CounterPage> {
  23.   @override
  24.   Widget build(BuildContext context) {
  25.     return Scaffold(
  26.       appBar: AppBar(title: Text('MVVM Counter')),
  27.       body: Center(
  28.         child: Consumer<CounterViewModel>(
  29.           builder: (context, viewModel, child) {
  30.             return Text('Counter: ${viewModel.value}');
  31.           },
  32.         ),
  33.       ),
  34.       floatingActionButton: FloatingActionButton(
  35.         onPressed: () {
  36.           context.read<CounterViewModel>().increment();
  37.         },
  38.         child: Icon(Icons.add),
  39.       ),
  40.     );
  41.   }
  42. }
复制代码
在这个示例中,我们使用 ChangeNotifierProvider 将 ViewModel 提供给子组件,并使用 Consumer 监听 ViewModel 的变化。当 ViewModel 更新时,Consumer 会自动重修 UI,以显示最新的计数器值。另外,我们使用 context.read<CounterViewModel>().increment() 来调用 ViewModel 中的 increment 方法,以更新计数器。
四、扩展 MVVM 架构

虽然我们已经演示了一个简单的 MVVM 架构实现,但在实际项目中,你可能会遇到更复杂的场景。接下来,我们将探讨一些扩展 MVVM 架构的方法,以满意不同的需求。
4.1 引入服务层

在大型项目中,我们可能需要处理更复杂的业务逻辑,如网络请求、数据库操作等。为了保持 Model 的简便,我们可以引入服务层,将这些逻辑封装为独立的服务类。然后,在 ViewModel 中调用这些服务类来更新 Model。
例如,我们可以创建一个 CounterService 类,负责从远程服务器获取和更新计数器值。然后,在 CounterViewModel 中调用这个服务类,而不是直接操作 Model。
4.2 使用依赖注入

依赖注入是一种编程技术,用于将对象的依赖项(如服务类)动态地通报给它们。通过使用依赖注入,我们可以更灵活地管理和测试应用的各个组件。
在 Flutter 中,我们可以使用 provider 包提供的 ProxyProvider 或其他第三方库(如 get_it、injectable 等)来实现依赖注入。如许,我们可以将服务类和其他依赖项注入到 ViewModel 中,而不是在 ViewModel 内部创建它们。
4.3 状态管理

虽然 MVVM 架构提供了一种有效的状态管理方法,但在复杂的应用中,我们可能需要更强大的状态管理解决方案。在 Flutter 中,有许多状态管理库可供选择,如 provider、bloc、mobx 等。这些库可以与 MVVM 架构结合使用,以实现更高效的状态管理。
例如,我们可以使用 bloc 库来实现 ViewModel,将业务逻辑和状态管理进一步解耦。如许,我们可以在不修改 View 的情况下,更轻易地重构和测试 ViewModel。
4.4 遵循最佳实践

在实现 MVVM 架构时,我们应遵循一些最佳实践,以确保代码的可维护性和可扩展性。例如:


  • 保持 Model、View 和 ViewModel 的职责单一,避免将过多的逻辑放入一个类中。
  • 使用接口和抽象类来定义 Model 和 ViewModel,以便在不同的实现和平台之间共享代码。
  • 编写单元测试和集成测试,确保应用的准确性和稳定性。
  • 优化性能,避免不须要的重绘和重修。
通过遵循这些最佳实践,我们可以确保 MVVM 架构在实际项目中发挥最大的作用。
五、实战案例:待服务项应用

为了更好地理解如安在实际项目中应用 MVVM 架构,我们将通过一个待服务项应用的实战案例来演示。这个应用将包罗以下功能:


  • 显示待服务项列表
  • 添加新的待服务项
  • 标记待服务项为完成或未完成
  • 删除待服务项
5.1 Model

首先,我们创建一个简单的 TodoItem 类作为 Model,用于存储待服务项的信息。
  1. class TodoItem {
  2.   String title;
  3.   bool isDone;
  4.   TodoItem({required this.title, this.isDone = false});
  5. }
复制代码
5.2 服务层

接着,我们创建一个 TodoService 类,负责处理待服务项的业务逻辑,如添加、删除、更新等。
  1. class TodoService {
  2.   List<TodoItem> _todos = [];
  3.   List<TodoItem> get todos => _todos;
  4.   void addTodo(TodoItem todo) {
  5.     _todos.add(todo);
  6.   }
  7.   void removeTodo(TodoItem todo) {
  8.     _todos.remove(todo);
  9.   }
  10.   void toggleTodoStatus(TodoItem todo) {
  11.     todo.isDone = !todo.isDone;
  12.   }
  13. }
复制代码
5.3 ViewModel

然后,我们创建一个继承 ChangeNotifier 的 TodoViewModel 类。这个类将处理 View 的输入,并调用 TodoService 来更新 Model。同时,通过调用 notifyListeners() 关照 View 更新。
  1. import 'package:flutter/foundation.dart';
  2. import 'todo_item.dart';
  3. import 'todo_service.dart';
  4. class TodoViewModel extends ChangeNotifier {
  5.   final TodoService _todoService = TodoService();
  6.   List<TodoItem> get todos => _todoService.todos;
  7.   void addTodo(String title) {
  8.     _todoService.addTodo(TodoItem(title: title));
  9.     notifyListeners();
  10.   }
  11.   void removeTodo(TodoItem todo) {
  12.     _todoService.removeTodo(todo);
  13.     notifyListeners();
  14.   }
  15.   void toggleTodoStatus(TodoItem todo) {
  16.     _todoService.toggleTodoStatus(todo);
  17.     notifyListeners();
  18.   }
  19. }
复制代码
5.4 View

最后,我们创建 View 层,包括一个 TodoListPage 和一个 TodoItemWidget。TodoListPage 负责显示待服务项列表和处理用户输入,而 TodoItemWidget 负责渲染单个待服务项。
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. import 'todo_view_model.dart';
  4. import 'todo_item.dart';
  5. void main() {
  6.   runApp(MyApp());
  7. }
  8. class MyApp extends StatelessWidget {
  9.   @override
  10.   Widget build(BuildContext context) {
  11.     return MaterialApp(
  12.       home: ChangeNotifierProvider(
  13.         create: (context) => TodoViewModel(),
  14.         child: TodoListPage(),
  15.       ),
  16.     );
  17.   }
  18. }
  19. class TodoListPage extends StatelessWidget {
  20.   @override
  21.   Widget build(BuildContext context) {
  22.     return Scaffold(
  23.       appBar: AppBar(title: Text('Todo List')),
  24.       body: Consumer<TodoViewModel>(
  25.         builder: (context, viewModel, child) {
  26.           return ListView.builder(
  27.             itemCount: viewModel.todos.length,
  28.             itemBuilder: (context, index) {
  29.               return TodoItemWidget(
  30.                 todo: viewModel.todos[index],
  31.                 onToggle: () {
  32.                   viewModel.toggleTodoStatus(viewModel.todos[index]);
  33.                 },
  34.                 onDelete: () {
  35.                   viewModel.removeTodo(viewModel.todos[index]);
  36.                 },
  37.               );
  38.             },
  39.           );
  40.         },
  41.       ),
  42.       floatingActionButton: FloatingActionButton(
  43.         onPressed: () async {
  44.           String? newTitle = await showDialog<String>(
  45.             context: context,
  46.             builder: (context) {
  47.               return AddTodoDialog();
  48.             },
  49.           );
  50.           if (newTitle != null && newTitle.isNotEmpty) {
  51.             context.read<TodoViewModel>().addTodo(newTitle);
  52.           }
  53.         },
  54.         child: Icon(Icons.add),
  55.       ),
  56.     );
  57.   }
  58. }
  59. class TodoItemWidget extends StatelessWidget {
  60.   final TodoItem todo;
  61.   final VoidCallback onToggle;
  62.   final VoidCallback onDelete;
  63.   TodoItemWidget({required this.todo, required this.onToggle, required this.onDelete});
  64.   @override
  65.   Widget build(BuildContext context) {
  66.     return ListTile(
  67.       title: Text(todo.title),
  68.       leading: Checkbox(
  69.         value: todo.isDone,
  70.         onChanged: (bool? value) {
  71.           onToggle();
  72.         },
  73.       ),
  74.       trailing: IconButton(
  75.         icon: Icon(Icons.delete),
  76.         onPressed: onDelete,
  77.       ),
  78.     );
  79.   }
  80. }
  81. class AddTodoDialog extends StatelessWidget {
  82.   final TextEditingController _controller = TextEditingController();
  83.   @override
  84.   Widget build(BuildContext context) {
  85.     return AlertDialog(
  86.       title: Text('Add Todo'),
  87.       content: TextField(
  88.         controller: _controller,
  89.         decoration: InputDecoration(hintText: 'Todo title'),
  90.       ),
  91.       actions: [
  92.         TextButton(
  93.           child: Text('Cancel'),
  94.           onPressed: () {
  95.             Navigator.of(context).pop();
  96.           },
  97.         ),
  98.         TextButton(
  99.           child: Text('Add'),
  100.           onPressed: () {
  101.             Navigator.of(context).pop(_controller.text);
  102.           },
  103.         ),
  104.       ],
  105.     );
  106.   }
  107. }
复制代码
在这个示例中,我们创建了一个待服务项应用,使用 MVVM 架构将业务逻辑、界面表现和用户交互分离。通过这种方式,我们可以更轻易地维护和扩展代码,同时保持代码整齐。
六、使用 Riverpod 库实现一个基于 MVVM 架构的计数器应用

在这个示例中,我们将使用 Riverpod 库实现一个基于 MVVM 架构的计数器应用。我们将遵循上文中描述的 MVVM 架构计划,并使用 Riverpod 更换 Provider 作为状态管理库。
首先,请确保已在 pubspec.yaml 文件中添加了 riverpod 和 flutter_riverpod 依赖:
  1. dependencies:
  2.   flutter:
  3.     sdk: flutter
  4.   flutter_riverpod: ^1.0.0
复制代码
接下来,我们将创建 Model、ViewModel 和 View 类,并使用 Riverpod 将它们连接起来。
6.1 Model

创建一个简单的 Counter 类作为 Model,用于存储计数器的值并举行增长操作。
  1. class Counter {
  2.   int _value = 0;
  3.   int get value => _value;
  4.   void increment() {
  5.     _value++;
  6.   }
  7. }
复制代码
6.2 ViewModel

创建一个继承 StateNotifier 的 CounterViewModel 类。这个类将处理 View 的输入,并更新 Model。同时,通过调用 state 属性关照 View 更新。
  1. import 'package:flutter_riverpod/flutter_riverpod.dart';
  2. import 'counter.dart';
  3. class CounterViewModel extends StateNotifier<Counter> {
  4.   CounterViewModel() : super(Counter());
  5.   int get value => state.value;
  6.   void increment() {
  7.     state = Counter().._value = state.value + 1;
  8.   }
  9. }
复制代码
6.3 View

创建一个 StatelessWidget,并在其 build 方法中构建 UI。将 ViewModel 作为 StateNotifierProvider 的参数,以便在子组件中访问 ViewModel。
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'counter_view_model.dart';
  4. void main() {
  5.   runApp(ProviderScope(child: MyApp()));
  6. }
  7. class MyApp extends StatelessWidget {
  8.   @override
  9.   Widget build(BuildContext context) {
  10.     return MaterialApp(
  11.       home: CounterPage(),
  12.     );
  13.   }
  14. }
  15. class CounterPage extends StatelessWidget {
  16.   @override
  17.   Widget build(BuildContext context) {
  18.     return Scaffold(
  19.       appBar: AppBar(title: Text('Riverpod MVVM Counter')),
  20.       body: Center(
  21.         child: Consumer(
  22.           builder: (context, watch, child) {
  23.             final viewModel = watch(counterViewModelProvider);
  24.             return Text('Counter: ${viewModel.value}');
  25.           },
  26.         ),
  27.       ),
  28.       floatingActionButton: FloatingActionButton(
  29.         onPressed: () {
  30.           context.read(counterViewModelProvider.notifier).increment();
  31.         },
  32.         child: Icon(Icons.add),
  33.       ),
  34.     );
  35.   }
  36. }
复制代码
在这个示例中,我们使用 StateNotifierProvider 将 ViewModel 提供给子组件,并使用 Consumer 监听 ViewModel 的变化。当 ViewModel 更新时,Consumer 会自动重修 UI,以显示最新的计数器值。另外,我们使用 context.read(counterViewModelProvider.notifier).increment() 来调用 ViewModel 中的 increment 方法,以更新计数器。
最后,别忘了在 ViewModel 文件中定义 Riverpod provider:
  1. import 'package:flutter_riverpod/flutter_riverpod.dart';
  2. final counterViewModelProvider = StateNotifierProvider<CounterViewModel, Counter>((ref) {
  3.   return CounterViewModel();
  4. });
复制代码
6.4 Riverpod 和 Provider 的区别

Riverpod 是一个用于状态管理的 Flutter 库,它提供了一种声明式、灵活且安全的方式来管理和访问应用步伐的状态。在我们的示例中,我们使用 Riverpod 更换了前文案例中的 Provider 库,以实现 MVVM 架构。
以下是 Riverpod 在示例中的作用以及与前文案例的区别:

  • 声明式 Provider:在 Riverpod 中,我们使用声明式的方式来定义 Provider。这意味着我们可以在全局范围内定义 Provider,而不需要将其放入 Widget 树中。这使得代码更易于阅读和维护。与前文案例相比,我们不再使用 ChangeNotifierProvider,而是使用 StateNotifierProvider 来创建 ViewModel 的实例。
  1. final counterViewModelProvider = StateNotifierProvider<CounterViewModel, Counter>((ref) {
  2.   return CounterViewModel();
  3. });
复制代码

  • StateNotifier:在 Riverpod 示例中,我们使用 StateNotifier 更换了 ChangeNotifier。StateNotifier 是 Riverpod 库的一部分,它提供了一种简单的状态管理机制。与 ChangeNotifier 相比,StateNotifier 具有更简便的 API,而且避免了一些潜在的性能问题。在 ViewModel 中,我们继承 StateNotifier 并使用 state 属性来关照 View 更新。
  1. class CounterViewModel extends StateNotifier<Counter> {
  2.   // ...
  3. }
复制代码

  • Consumer 和 watch:在 Riverpod 示例中,我们使用 Consumer 和 watch 函数来监听 ViewModel 的变化。Consumer 和 watch 函数是 Riverpod 库提供的数据绑定机制,它们可以自动重修 UI 以显示最新的状态。与前文案例中的 context.watch() 相比,Riverpod 的 watch 函数提供了更简便的语法。
  1. child: Consumer(
  2.   builder: (context, watch, child) {
  3.     final viewModel = watch(counterViewModelProvider);
  4.     return Text('Counter: ${viewModel.value}');
  5.   },
  6. ),
复制代码

  • 访问 ViewModel:在 Riverpod 示例中,我们使用 context.read() 函数来访问 ViewModel 并调用其方法。与前文案例中的 context.read() 相比,Riverpod 的 context.read() 函数提供了更简便的语法。此外,我们需要使用 notifier 属性来访问 StateNotifier 的实例。
  1. onPressed: () {
  2.   context.read(counterViewModelProvider.notifier).increment();
  3. },
复制代码
总之,Riverpod 在示例中的作用重要是提供状态管理和数据绑定。与前文案例相比,Riverpod 提供了更简便的 API、更灵活的状态管理机制以及更安全的访问方式。这些特性使得 Riverpod 成为实现 MVVM 架构的一个很好的选择。
七、结论

MVVM 架构是一种强大的计划模式,可以帮助我们构建可维护、可测试、可扩展的应用。在 Flutter 中,我们可以利用其强大的数据绑定和状态管理特性,轻松实现 MVVM 架构。
在本文中,我们介绍了 MVVM 架构的基本概念,展示了如安在 Flutter 中实现 MVVM 架构,并通过一个待服务项应用的实战案例,演示了如安在实际项目中应用 MVVM 架构。
希望这篇文章能帮助你理解和掌握 MVVM 架构,为你的 Flutter 项目提供有用的参考。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

涛声依旧在

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表