ChangeNotifier实现_Flutter在鸿蒙平台实现状态通知机制
作者付文龙红目香薰仓库地址https://gitcode.com/feng8403000/FlutterfromBeginnertoAdvancedForHarmonyOS.git联系邮箱372699828qq.com一、ChangeNotifier简介ChangeNotifier是Flutter自带的可监听对象继承自Listenable是Provider状态管理的核心数据模型。它提供了简单而强大的状态变化通知机制使得状态管理变得非常直观。1.1 ChangeNotifier的设计理念ChangeNotifier的设计理念非常简单当数据发生变化时通知所有监听者。这种设计模式被称为观察者模式Observer Pattern它将数据模型与UI组件解耦使得数据变化可以被多个组件同时监听。1.2 ChangeNotifier的优势特性说明轻量级仅100多行代码性能优秀简单易用API设计简洁易于理解和使用内置支持Flutter SDK自带无需额外依赖灵活扩展支持自定义监听逻辑二、ChangeNotifier核心特性2.1 继承ListenableChangeNotifier继承自Listenable接口提供了基本的监听管理能力addListener(VoidCallback listener)添加监听者removeListener(VoidCallback listener)移除监听者hasListeners检查是否有监听者2.2 notifyListeners()notifyListeners()是ChangeNotifier最核心的方法它会通知所有已注册的监听者数据已变化。当调用此方法时所有监听者的回调函数都会被执行。2.3 dispose()dispose()方法用于清理所有监听者防止内存泄漏。ChangeNotifierProvider会自动调用此方法无需手动管理。2.4 轻量级实现ChangeNotifier的实现非常轻量级主要包含以下几个部分一个列表存储所有监听者添加和移除监听者的方法通知所有监听者的方法清理监听者的方法classChangeNotifierimplementsListenable{ListVoidCallback??_listeners;overridevoidaddListener(VoidCallbacklistener){_listeners??[];_listeners!.add(listener);}overridevoidremoveListener(VoidCallbacklistener){_listeners?.remove(listener);}protectedvoidnotifyListeners(){if(_listeners!null){finallocalListenersListVoidCallback?.from(_listeners!);for(finallistenerinlocalListeners){listener?.call();}}}overridevoiddispose(){_listenersnull;}}三、创建ChangeNotifier数据模型3.1 基本结构创建ChangeNotifier数据模型的基本结构如下classCartProviderextendsChangeNotifier{// 私有字段存储状态数据ListCartItem_items[];double _discount0.0;// 公开getter暴露数据ListCartItemgetitems_items;doublegetdiscount_discount;intgetitemCount_items.length;// 计算属性计算派生数据doublegettotalPrice{return_items.fold(0.0,(sum,item)sumitem.price*item.quantity);}doublegetdiscountedPrice{returntotalPrice*(1-_discount);}// 操作方法修改数据并通知监听者voidaddItem(CartItemitem){_items.add(item);notifyListeners();}voidremoveItem(Stringid){_items.removeWhere((item)item.idid);notifyListeners();}voidupdateQuantity(Stringid,int quantity){finalindex_items.indexWhere((item)item.idid);if(index!-1){_items[index].quantityquantity;notifyListeners();}}voidsetDiscount(double discount){_discountdiscount.clamp(0.0,1.0);notifyListeners();}voidclearCart(){_items.clear();_discount0.0;notifyListeners();}}3.2 CartItem数据模型classCartItem{finalStringid;finalStringname;finaldouble price;int quantity;CartItem({requiredthis.id,requiredthis.name,requiredthis.price,this.quantity1,});CartItemcopyWith({String?id,String?name,double?price,int?quantity,}){returnCartItem(id:id??this.id,name:name??this.name,price:price??this.price,quantity:quantity??this.quantity,);}}四、ChangeNotifier使用要点4.1 私有字段与公开getterChangeNotifier的数据模型应该遵循以下原则字段通常设为私有以_开头通过getter暴露数据禁止外部直接修改这样可以确保数据修改只能通过方法进行便于管理和追踪classCartProviderextendsChangeNotifier{ListCartItem_items[];// 私有字段ListCartItemgetitems_items;// 公开getter}4.2 notifyListeners()的时机调用notifyListeners()的时机非常重要在所有数据修改完成后调用不要在循环中频繁调用批量修改时合并为一次调用voidaddMultipleItems(ListCartItemitems){_items.addAll(items);notifyListeners();// 在所有修改完成后调用}4.3 计算属性使用getter计算派生数据避免重复计算doublegettotalPrice{return_items.fold(0.0,(sum,item)sumitem.price*item.quantity);}doublegetdiscountedPrice{returntotalPrice*(1-_discount);}4.4 生命周期管理ChangeNotifierProvider会自动管理ChangeNotifier的生命周期创建Provider时自动创建ChangeNotifier实例Widget树销毁时自动调用dispose()方法ChangeNotifierProvider(create:(context)CartProvider(),// 创建实例child:constMyApp(),)五、批量更新优化5.1 优化前多次调用notifyListeners()voidaddMultipleItems(ListCartItemitems){for(variteminitems){_items.add(item);notifyListeners();// 不推荐每次循环都调用}}这种方式会导致多次UI重建影响性能。5.2 优化后批量修改后只调用一次voidaddMultipleItems(ListCartItemitems){_items.addAll(items);notifyListeners();// 推荐批量修改后只调用一次}这种方式可以减少UI重建次数提升性能。5.3 复杂操作的批量更新voidupdateCart(ListCartItemnewItems,double discount){_items.clear();_items.addAll(newItems);_discountdiscount;notifyListeners();// 一次调用多次修改}六、ChangeNotifier与其他方案对比6.1 ChangeNotifier vs StateNotifier方案复杂度学习曲线适用场景ChangeNotifier低低中小项目状态管理StateNotifier中中需要不可变状态的项目StateNotifier是Riverpod生态中的状态管理方案它强制使用不可变状态适合需要严格状态控制的项目。6.2 ChangeNotifier vs Bloc方案复杂度学习曲线适用场景ChangeNotifier低低中小项目状态管理Bloc高高复杂业务逻辑Bloc是一个基于流的状态管理方案适合处理复杂的业务逻辑和异步操作。七、高级用法7.1 自定义监听逻辑ChangeNotifier允许自定义监听逻辑例如只在特定条件下通知监听者classCartProviderextendsChangeNotifier{ListCartItem_items[];bool _shouldNotifytrue;voidaddItem(CartItemitem){_items.add(item);if(_shouldNotify){notifyListeners();}}voidpauseNotifications(){_shouldNotifyfalse;}voidresumeNotifications(){_shouldNotifytrue;notifyListeners();// 通知暂停期间的所有变化}}7.2 组合多个ChangeNotifier可以组合多个ChangeNotifier实现状态的分层管理classAppStateextendsChangeNotifier{finalCartProvidercartCartProvider();finalUserProvideruserUserProvider();finalThemeProviderthemeThemeProvider();voidupdateAll(){cart.notifyListeners();user.notifyListeners();theme.notifyListeners();notifyListeners();}}7.3 使用ValueNotifier管理单一值对于单一值的状态管理可以使用ValueNotifierfinalcounterValueNotifierint(0);// 使用ValueListenableBuilder监听变化ValueListenableBuilderint(valueListenable:counter,builder:(context,value,child){returnText(Count:$value);},);八、实际应用场景8.1 购物车状态管理classCartProviderextendsChangeNotifier{ListCartItem_items[];double _discount0.0;ListCartItemgetitems_items;doublegetdiscount_discount;intgettotalQuantity{return_items.fold(0,(sum,item)sumitem.quantity);}doublegettotalPrice{return_items.fold(0.0,(sum,item)sumitem.price*item.quantity);}doublegetdiscountedPrice{returntotalPrice*(1-_discount);}voidaddItem(CartItemitem){finalexistingItem_items.firstWhere((i)i.iditem.id,orElse:()item,);if(existingItemitem){_items.add(item);}else{existingItem.quantityitem.quantity;}notifyListeners();}voidremoveItem(Stringid){_items.removeWhere((item)item.idid);notifyListeners();}voidupdateQuantity(Stringid,int quantity){finalindex_items.indexWhere((item)item.idid);if(index!-1){if(quantity0){_items.removeAt(index);}else{_items[index].quantityquantity;}notifyListeners();}}voidsetDiscount(double discount){_discountdiscount.clamp(0.0,1.0);notifyListeners();}voidclearCart(){_items.clear();_discount0.0;notifyListeners();}}8.2 用户状态管理classUserProviderextendsChangeNotifier{User?_user;bool _isLoadingfalse;String?_error;User?getuser_user;boolgetisLoading_isLoading;String?geterror_error;FuturevoidfetchUser(StringuserId)async{_isLoadingtrue;_errornull;notifyListeners();try{_userawaitUserApi.fetchUser(userId);}catch(e){_errore.toString();}finally{_isLoadingfalse;notifyListeners();}}voidlogout(){_usernull;notifyListeners();}}8.3 主题状态管理classThemeProviderextendsChangeNotifier{bool _isDarkfalse;ThemeModegetthemeMode_isDark?ThemeMode.dark:ThemeMode.light;voidtoggleTheme(){_isDark!_isDark;notifyListeners();}ThemeDatagetthemeData{return_isDark?ThemeData.dark().copyWith(primaryColor:Colors.blue):ThemeData.light().copyWith(primaryColor:Colors.blue);}}九、常见问题与解决方案9.1 问题忘记调用notifyListeners()现象修改了数据但UI没有更新。解决方案确保在所有数据修改完成后调用notifyListeners()。voidaddItem(CartItemitem){_items.add(item);notifyListeners();// 不要忘记调用}9.2 问题在循环中频繁调用notifyListeners()现象性能问题UI卡顿。解决方案合并多次调用为一次。voidaddMultipleItems(ListCartItemitems){_items.addAll(items);notifyListeners();// 合并调用}9.3 问题内存泄漏现象应用内存占用持续增加。解决方案使用ChangeNotifierProvider自动管理生命周期不要手动创建ChangeNotifier实例。// 推荐ChangeNotifierProvider(create:(context)CartProvider(),child:constMyApp(),)// 不推荐finalcartCartProvider();// 手动创建容易忘记dispose9.4 问题并发修改导致状态不一致现象多个异步操作同时修改状态导致数据不一致。解决方案使用互斥锁或队列管理异步操作。classCartProviderextendsChangeNotifier{bool _isUpdatingfalse;finalListvoidFunction()_pendingUpdates[];FuturevoidupdateCart(ListCartItemnewItems)async{if(_isUpdating){_pendingUpdates.add(()updateCart(newItems));return;}_isUpdatingtrue;try{_itemsnewItems;notifyListeners();}finally{_isUpdatingfalse;if(_pendingUpdates.isNotEmpty){finalupdate_pendingUpdates.removeAt(0);update();}}}}十、总结ChangeNotifier是Provider状态管理的核心数据模型它提供了简单而强大的状态变化通知机制。通过合理使用ChangeNotifier可以实现高效的状态管理。在使用ChangeNotifier时需要注意以下几点使用私有字段和公开getter禁止外部直接修改数据在所有数据修改完成后调用notifyListeners()批量修改时合并多次调用为一次使用ChangeNotifierProvider自动管理生命周期对于复杂场景可以考虑使用StateNotifier或Bloc掌握ChangeNotifier的使用方法是学习Provider状态管理的基础。

相关新闻

【Rust自学】13.4. 闭包 Pt.4:使用闭包捕获环境

【Rust自学】13.4. 闭包 Pt.4:使用闭包捕获环境

13.4 闭包 Pt.4:使用闭包捕获环境 13.4.0. 写在正文之前 Rust语言在设计过程中受到了很多语言的启发,而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等…

2026/10/18 10:34:54 阅读更多 →
【无标题】简短的介绍

【无标题】简短的介绍

自我介绍大学生小伟目标:本人无太大志向只想能有一个好的工作安稳过日目前看来我只能努力学习加油

2026/10/18 10:35:35 阅读更多 →
【Rust自学】13.5. 迭代器 Pt.1:迭代器的定义、iterator trait和next方法

【Rust自学】13.5. 迭代器 Pt.1:迭代器的定义、iterator trait和next方法

13.5 迭代器 Pt.1:迭代器的定义、iterator trait和next方法 13.5.0. 写在正文之前 Rust语言在设计过程中受到了很多语言的启发,而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分…

2026/10/18 10:35:50 阅读更多 →

最新新闻

JsLeakWatcher 报出一批对象,真的是内存泄漏吗:页面缓存、监听器和可达路径怎么分

JsLeakWatcher 报出一批对象,真的是内存泄漏吗:页面缓存、监听器和可达路径怎么分

JsLeakWatcher 报出一批对象,真的是内存泄漏吗:页面缓存、监听器和可达路径怎么分 页面退出后 JsLeakWatcher 报出几十个对象,团队立刻把所有缓存清掉,结果二次进入变慢,告警却仍然存在。工具发现“对象仍可达”&…

2026/10/18 15:00:54 阅读更多 →
Aspose.Slides 25.5 JDK 16 集成指南:License校验与模块化配置

Aspose.Slides 25.5 JDK 16 集成指南:License校验与模块化配置

简介:本资源是面向Java开发者的企业级PowerPoint文档处理解决方案,适用于需在Spring Boot、Servlet等后台架构中实现PPT/PPTX自动化生成、格式转换与深度编辑的中高级开发场景。压缩包共2个文件(1个JAR组件 1个XML许可证)&#x…

2026/10/18 15:00:54 阅读更多 →
2026批量查快递轨迹用什么工具好:从预算成本看,小中大团队各自该花多少钱

2026批量查快递轨迹用什么工具好:从预算成本看,小中大团队各自该花多少钱

先说结论:批量查快递轨迹这件事,预算从48元/天到1288元/年,覆盖了所有规模的团队。关键不是花多少钱,是花的钱能不能对应上你的业务规模。下面用需求对照矩阵,把不同预算档位该匹配什么业务量讲清楚。固乔快递批量查询…

2026/10/18 15:00:54 阅读更多 →
CentOS 8.2 手动编译 libssh 0.9.5 完整指南:独立安装与版本共存

CentOS 8.2 手动编译 libssh 0.9.5 完整指南:独立安装与版本共存

1. 为什么在 CentOS 8.2 上要手动编译 libssh 0.9.5CentOS 8.2 自带的 libssh 版本是 0.9.4,这个版本在功能上其实已经能满足大部分基础场景,比如简单的 SSH 客户端连接、SFTP 文件传输等。但如果你正在做安全审计、渗透测试工具链搭建,或者需…

2026/10/18 15:00:54 阅读更多 →
Linux 开发工具(三) 深入理解编译器: gcc / g++

Linux 开发工具(三) 深入理解编译器: gcc / g++

目录 一. 编译的四个阶段 1.1 预处理 1.1.1 代码示例 1.1.2 命令行宏定义 1.1.3 条件编译的用处 1.2 编译 1.2.1 代码示例 1.3 汇编 1.3.1 代码示例 1.4 链接 1.4.1 代码示例 二. 动态库和静态库 2.1 什么是库 2.2 库文件的命名规则 2.3 动静态库 2.4 动态链接…

2026/10/18 15:00:54 阅读更多 →
对话工具升级为生成式界面引擎:免费开放与交互范式切换

对话工具升级为生成式界面引擎:免费开放与交互范式切换

1. 这次升级到底改了什么:从“对话工具”到“生成式界面引擎”先把结论摆在前面:这次更新的核心不是“模型又聪明了一点”,而是交互范式的切换。过去的对话产品,本质上是一个文本进、文本出的黑盒,你问它答&#xff0c…

2026/10/18 14:59:53 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →