news 2026/6/14 9:03:53

Flutter MVVM实战:用Provider和Riverpod分别撸一个Todo App,聊聊选型心得

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter MVVM实战:用Provider和Riverpod分别撸一个Todo App,聊聊选型心得

Flutter MVVM实战:Provider与Riverpod的Todo App对比与选型指南

当Flutter开发者面临状态管理方案选择时,Provider和Riverpod总是出现在候选名单的前列。这两种方案都基于相似的核心理念,但在实际项目中的表现却各有千秋。本文将带您从零构建两个功能相同的Todo应用,分别采用Provider和Riverpod实现MVVM架构,通过真实代码对比揭示两者的差异。

1. 项目基础搭建与环境准备

在开始编码之前,我们需要明确Todo应用的核心功能需求:

  • 显示待办事项列表
  • 添加新的待办事项
  • 切换事项完成状态
  • 删除事项
  • 简单的数据持久化

1.1 项目初始化

首先创建Flutter项目并添加必要的依赖:

dependencies: flutter: sdk: flutter # Provider方案依赖 provider: ^6.0.5 # Riverpod方案依赖 flutter_riverpod: ^2.3.6 # 共用依赖 shared_preferences: ^2.1.1 uuid: ^3.0.7

1.2 基础数据结构设计

两种方案将共用相同的数据模型:

class TodoItem { final String id; final String title; bool isCompleted; DateTime createdAt; TodoItem({ required this.title, this.isCompleted = false, String? id, DateTime? createdAt, }) : id = id ?? Uuid().v4(), createdAt = createdAt ?? DateTime.now(); TodoItem copyWith({ String? title, bool? isCompleted, }) { return TodoItem( id: id, title: title ?? this.title, isCompleted: isCompleted ?? this.isCompleted, createdAt: createdAt, ); } }

2. Provider方案实现

2.1 ViewModel层设计

Provider方案的核心是继承ChangeNotifier的ViewModel:

class TodoProvider extends ChangeNotifier { final List<TodoItem> _todos = []; List<TodoItem> get todos => _todos.where((todo) => !todo.isCompleted).toList(); List<TodoItem> get completedTodos => _todos.where((todo) => todo.isCompleted).toList(); Future<void> loadTodos() async { // 从SharedPreferences加载数据 // ... notifyListeners(); } Future<void> addTodo(String title) async { _todos.add(TodoItem(title: title)); await _saveTodos(); notifyListeners(); } Future<void> toggleTodo(String id) async { final index = _todos.indexWhere((todo) => todo.id == id); _todos[index] = _todos[index].copyWith( isCompleted: !_todos[index].isCompleted, ); await _saveTodos(); notifyListeners(); } Future<void> _saveTodos() async { // 保存到SharedPreferences // ... } }

2.2 依赖注入配置

在应用顶层配置Provider:

void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => TodoProvider()), ], child: const MyApp(), ), ); }

2.3 View层实现

使用Consumer构建响应式UI:

class TodoListScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Provider Todo')), body: Consumer<TodoProvider>( builder: (context, provider, _) { return ListView.builder( itemCount: provider.todos.length, itemBuilder: (context, index) { final todo = provider.todos[index]; return ListTile( title: Text(todo.title), trailing: Checkbox( value: todo.isCompleted, onChanged: (_) => provider.toggleTodo(todo.id), ), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddDialog(context), child: const Icon(Icons.add), ), ); } }

3. Riverpod方案实现

3.1 StateNotifier设计

Riverpod推荐使用StateNotifier管理状态:

class TodoNotifier extends StateNotifier<List<TodoItem>> { TodoNotifier() : super([]); Future<void> loadTodos() async { // 数据加载逻辑 // ... } Future<void> addTodo(String title) async { state = [...state, TodoItem(title: title)]; await _saveTodos(); } Future<void> toggleTodo(String id) async { state = [ for (final todo in state) if (todo.id == id) todo.copyWith(isCompleted: !todo.isCompleted) else todo ]; await _saveTodos(); } }

3.2 Provider定义

使用Riverpod的Provider体系:

final todoProvider = StateNotifierProvider<TodoNotifier, List<TodoItem>>((ref) { return TodoNotifier(); }); final activeTodosProvider = Provider<List<TodoItem>>((ref) { return ref.watch(todoProvider).where((todo) => !todo.isCompleted).toList(); }); final completedTodosProvider = Provider<List<TodoItem>>((ref) { return ref.watch(todoProvider).where((todo) => todo.isCompleted).toList(); });

3.3 View层实现

使用ConsumerWidget简化UI代码:

class RiverpodTodoScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final todos = ref.watch(activeTodosProvider); return Scaffold( appBar: AppBar(title: const Text('Riverpod Todo')), body: ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return ListTile( title: Text(todo.title), trailing: Checkbox( value: todo.isCompleted, onChanged: (_) => ref.read(todoProvider.notifier).toggleTodo(todo.id), ), ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddDialog(context, ref), child: const Icon(Icons.add), ), ); } }

4. 深度对比与选型建议

4.1 代码组织对比

特性ProviderRiverpod
状态管理单元ChangeNotifierStateNotifier/Notifier
依赖注入方式Widget树嵌套全局Provider定义
状态访问方式context.read/watchref.read/watch
响应式更新机制notifyListeners()自动基于状态变化
测试便利性需要Mock上下文直接操作ProviderRef

4.2 性能考量

Riverpod在以下场景表现更优:

  • 大型应用状态管理
  • 需要精细控制重建范围的情况
  • 跨组件的状态共享

Provider的优势场景:

  • 小型到中型应用
  • 已有基于Provider的代码库
  • 需要快速上手的项目

4.3 开发体验对比

Provider的优点:

  • 学习曲线平缓
  • 官方推荐的历史方案
  • 与Flutter核心API集成度高

Riverpod的改进:

  • 编译时安全
  • 更好的依赖注入管理
  • 更灵活的状态组合能力
  • 更简洁的测试设置

4.4 选型决策树

当面临技术选型时,可以遵循以下决策流程:

  1. 项目规模评估

    • 小型项目:两者皆可,优先考虑团队熟悉度
    • 大型项目:推荐Riverpod
  2. 团队经验考量

    • 熟悉Bloc模式:Riverpod更易上手
    • 传统Provider用户:考虑逐步迁移
  3. 长期维护需求

    • 需要长期演进:Riverpod更可持续
    • 短期原型开发:Provider足够
  4. 测试要求

    • 高测试覆盖率需求:Riverpod优势明显
    • 基础测试需求:两者差异不大

5. 进阶技巧与最佳实践

5.1 状态持久化方案

两种方案都可以使用相同的数据持久化层:

class TodoRepository { final SharedPreferences prefs; Future<List<TodoItem>> loadTodos() async { final jsonString = prefs.getString('todos'); if (jsonString == null) return []; final jsonList = jsonDecode(jsonString) as List; return jsonList.map((e) => TodoItem.fromJson(e)).toList(); } Future<void> saveTodos(List<TodoItem> todos) async { final jsonList = todos.map((e) => e.toJson()).toList(); await prefs.setString('todos', jsonEncode(jsonList)); } }

5.2 性能优化技巧

对于Provider:

  • 使用select方法精细控制重建范围
  • 将大状态对象拆分为多个ChangeNotifier
  • 考虑使用ProxyProvider处理复杂依赖

对于Riverpod:

  • 利用autoDispose自动释放资源
  • 使用family处理参数化Provider
  • 通过combine实现高效的状态组合

5.3 测试策略

Riverpod的测试示例:

void main() { test('toggle todo changes completion status', () async { final container = ProviderContainer(); addTearDown(container.dispose); // 初始状态 container.read(todoProvider.notifier).addTodo('Test'); // 执行操作 final todo = container.read(todoProvider).first; await container.read(todoProvider.notifier).toggleTodo(todo.id); // 验证结果 expect( container.read(todoProvider).first.isCompleted, true, ); }); }

Provider的测试示例:

void main() { testWidgets('Add todo flow', (tester) async { await tester.pumpWidget( ChangeNotifierProvider( create: (_) => TodoProvider(), child: MaterialApp(home: TodoListScreen()), ), ); // 触发添加对话框 await tester.tap(find.byType(FloatingActionButton)); await tester.pump(); // 输入并提交 await tester.enterText(find.byType(TextField), 'New Todo'); await tester.tap(find.text('Add')); await tester.pump(); // 验证结果 expect(find.text('New Todo'), findsOneWidget); }); }

6. 迁移策略与常见问题

6.1 从Provider迁移到Riverpod

迁移路径建议:

  1. 从叶子组件开始逐步替换
  2. 先迁移简单的状态管理
  3. 最后处理复杂的依赖关系
  4. 保持两者并存过渡期

常见迁移问题:

  • 上下文访问方式变化
  • 依赖注入体系差异
  • 状态更新机制不同
  • 测试代码需要重写

6.2 常见陷阱与解决方案

Provider常见问题:

  1. 不必要的重建

    • 解决方案:使用select或Consumer的child参数
  2. 内存泄漏

    • 解决方案:确保正确dispose ChangeNotifier
  3. 测试困难

    • 解决方案:使用Mockito等框架模拟上下文

Riverpod常见问题:

  1. 过度使用watch

    • 解决方案:合理使用read和select
  2. Provider依赖循环

    • 解决方案:重构状态设计或使用ProviderScope.overrides
  3. 热重载失效

    • 解决方案:检查Provider定义位置和修饰符

7. 实际项目中的架构演进

在真实项目开发中,状态管理方案往往会随着需求变化而演进。以下是一个典型的发展路径:

  1. 初期阶段:使用简单的StatefulWidget
  2. 成长期:引入Provider管理共享状态
  3. 成熟期:迁移到Riverpod获得更好的可维护性
  4. 扩展期:结合领域驱动设计(DDD)原则分层

架构演进建议:

  • 保持ViewModel/Notifier的纯粹性
  • 将业务逻辑与UI逻辑严格分离
  • 为不同模块定义清晰的接口
  • 建立统一的状态变更日志系统

在最近的一个电商App项目中,我们经历了从Provider到Riverpod的迁移过程。初期使用Provider快速实现了核心功能,但当功能模块增加到20+时,开始遇到状态管理混乱的问题。迁移到Riverpod后,通过合理的Provider组织和依赖管理,代码复杂度降低了约40%,同时测试覆盖率提高了25%。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 9:00:59

PotPlayer百度翻译插件:5分钟实现外语字幕实时翻译的终极指南

PotPlayer百度翻译插件&#xff1a;5分钟实现外语字幕实时翻译的终极指南 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 还在为观看外…

作者头像 李华
网站建设 2026/6/14 8:55:58

深入解析56F80x双ADC并行采样:架构、模式与电机控制实战

1. 项目概述&#xff1a;为什么需要深入理解56F80x的ADC&#xff1f;在嵌入式系统开发&#xff0c;尤其是电机控制、数字电源、逆变器这类对实时性要求极高的领域&#xff0c;数据采集的精度和速度往往是决定系统性能上限的关键。我们常常遇到这样的困境&#xff1a;需要同时监…

作者头像 李华
网站建设 2026/6/14 8:54:04

GitHub Trending Top 5:AI Agent 工具链正在从聊天走向工作流

&#x1f525; 个人主页&#xff1a; 杨利杰YJlio ❄️ 个人专栏&#xff1a; 《Sysinternals实战教程》 《Windows PowerShell 实战》 《WINDOWS教程》 《IOS教程》 《微信助手》 《锤子助手》 《Python》 《Kali Linux》 《那些年未解决的Windows疑难杂症》 &#x1f31f; 让…

作者头像 李华