前言Flutter 的状态管理方案琳琅满目——BLoC、Riverpod、Redux、GetX……但对于一个中小型应用Provider 的刚好够用哲学往往是最务实的选择。它没有 BLoC 的样板代码地狱也没有 Riverpod 的概念曲线5 分钟就能上手。鸿蒙 Flutter 备忘录应用使用MultiProvider 4 个ChangeNotifier管理整个应用的状态。本文拆解这个架构的设计思路、常见陷阱和最佳实践。项目仓库todo_flutter_harmony为什么选 Provider方案学习成本代码量鸿蒙兼容性适合场景Provider低少✅ 纯 Dart中小应用BLoC高多✅ 纯 Dart大型应用/团队Riverpod中中✅ 纯 Dart中大型应用GetX低少⚠️ 框架耦合快速原型Redux高多✅ 纯 Dart复杂状态对于这个备忘录应用4 个数据实体10 个页面Provider 是最合适的——学习成本低鸿蒙天然兼容且社区庞大。应用入口MultiProvider 注册// main.dartvoidmain(){runApp(constMyApp());}classMyAppextendsStatelessWidget{constMyApp({super.key});overrideWidgetbuild(BuildContextcontext){returnMultiProvider(providers:[ChangeNotifierProvider(create:(_)MemoProvider()),ChangeNotifierProvider(create:(_)TodoProvider()),ChangeNotifierProvider(create:(_)DiaryProvider()),ChangeNotifierProvider(create:(_)CategoryProvider()),],child:constApp(),);}}MultiProvider的嵌套顺序不影响功能——这 4 个 Provider 之间没有依赖关系它们各自独立。如果 Provider 之间有依赖比如MemoProvider需要CategoryProvider的数据可以使用ProxyProviderChangeNotifierProxyProviderCategoryProvider,MemoProvider(create:(_)MemoProvider(),update:(_,categoryProvider,memoProvider){memoProvider!.updateCategoryProvider(categoryProvider);returnmemoProvider;},)但在本应用中每个 Provider 都直接访问DatabaseHelper单例没有相互依赖。MemoProvider 完整实现classMemoProviderextendsChangeNotifier{finalDatabaseHelper_dbDatabaseHelper.instance;ListMemo_allMemos[];bool _isSelectionModefalse;finalSetint_selectedIds{};int?_categoryFilter;String_searchQuery;// Getters ListMemogetallMemosList.unmodifiable(_allMemos);boolgetisSelectionMode_isSelectionMode;SetintgetselectedIdsSet.unmodifiable(_selectedIds);intgetselectedCount_selectedIds.length;ListMemogetfilteredMemos{varresultListMemo.from(_allMemos);if(_categoryFilter!null){resultresult.where((m)m.categoryId_categoryFilter).toList();}if(_searchQuery.isNotEmpty){finalq_searchQuery.toLowerCase();resultresult.where((m)m.title.toLowerCase().contains(q)||m.content.toLowerCase().contains(q)).toList();}result.sort((a,b){if(a.isPinned!b.isPinned)returna.isPinned?-1:1;returnb.createdAt.compareTo(a.createdAt);});returnresult;}// 数据加载 FuturevoidloadMemos()async{_allMemosawait_db.getAllMemos();notifyListeners();}// CRUD FuturevoidaddMemo(Memomemo)async{await_db.insertMemo(memo);awaitloadMemos();}FuturevoidupdateMemo(Memomemo)async{await_db.updateMemo(memo);awaitloadMemos();}FuturevoiddeleteMemo(int id)async{await_db.deleteMemo(id);awaitloadMemos();}// ... 选择模式、搜索、筛选等其他方法}三种 Consumer 模式的选用context.watch监听变化ConsumerMemoProvider(builder:(context,provider,_){returnText(共${provider.allMemos.length}条备忘录);},)等价写法// 在 build 方法中finalprovidercontext.watchMemoProvider();returnText(共${provider.allMemos.length}条备忘录);context.watch会注册一个 listener当 Provider 调用notifyListeners()时widget 自动重建。context.read一次性读取FloatingActionButton(onPressed:(){// 读取但不监听——适合事件处理context.readMemoProvider().addMemo(newMemo);},child:constIcon(Icons.add),)context.read不注册 listener——只在当前时刻读取 Provider不会导致 widget 重建。适合在onPressed、onTap等事件回调中使用。context.select精确订阅// 只监听 memo 数量变化不关心内容变化finalcountcontext.selectMemoProvider,int((provider)provider.allMemos.length,);context.select只在 selector 返回值变化时重建。当 Provider 通知了变化但 memo 数量没变时这个 widget 不重建——避免不必要的渲染。IndexedStack 页面数据加载应用使用IndexedStack保持 4 个 tab 的页面状态classHomePageextendsStatefulWidget{overrideStateHomePagecreateState()_HomePageState();}class_HomePageStateextendsStateHomePage{int _currentIndex0;overrideWidgetbuild(BuildContextcontext){returnScaffold(body:IndexedStack(index:_currentIndex,children:const[MemoListPage(),TodoListPage(),DiaryListPage(),StatsPage(),],),bottomNavigationBar:NavigationBar(selectedIndex:_currentIndex,onDestinationSelected:(index){setState(()_currentIndexindex);},destinations:const[NavigationDestination(icon:Icon(Icons.note_alt_outlined),label:备忘录),NavigationDestination(icon:Icon(Icons.checklist_outlined),label:待办),NavigationDestination(icon:Icon(Icons.book_outlined),label:日记),NavigationDestination(icon:Icon(Icons.bar_chart),label:统计),],),);}}数据在各页面的initState中通过addPostFrameCallback加载overridevoidinitState(){super.initState();WidgetsBinding.instance.addPostFrameCallback((_){context.readMemoProvider().loadMemos();});}addPostFrameCallback确保首帧已渲染后再触发数据加载避免在 build 阶段调用 Provider 方法Flutter 禁止在 build 中修改状态。await 后的 mounted 检查Futurevoid_deleteMemo(int id)async{finalconfirmedawaitshowDialogbool(context:context,builder:(ctx)AlertDialog(title:constText(确认删除),content:constText(确定要删除这条备忘录吗),actions:[TextButton(onPressed:()Navigator.pop(ctx,false),child:constText(取消)),TextButton(onPressed:()Navigator.pop(ctx,true),child:constText(删除)),],),);// 关键await 后检查 mountedif(!mounted)return;if(confirmed!true)return;awaitcontext.readMemoProvider().deleteMemo(id);if(!mounted)return;ScaffoldMessenger.of(context).showSnackBar(constSnackBar(content:Text(已删除)),);}showDialog返回一个Future在 await 期间用户可能按了系统返回键导航离开此时mounted false。之后任何调用setState或context的操作都会抛出异常。每个 await 后检查mounted是最基本的防御性编程。鸿蒙兼容性Provider 是纯 Dart 包不包含任何原生代码。它仅依赖 Flutter 的InheritedWidget机制和 Dart 的ChangeNotifier模式在 Android、iOS、鸿蒙 OHOS 上行为完全一致。总结Provider 状态管理架构的关键决策MultiProvider 注册 4 个 ChangeNotifier各司其职无相互依赖context.watch / read / select 三模式按场景选择监听策略addPostFrameCallback 触发初始加载避免在 build 中修改状态每个 await 后检查 mounted防止异步回调中的 widget 已 dispose 崩溃完整项目代码见todo_flutter_harmony