Provider(ChangeNotifier)内Stateful组件notifyListeners后不更新问题
问题场景
- 使用搭配
ChangeNotifier模型的Stateless类型Provider组件做状态管理,Provider内部嵌套了Stateful子组件 - 异常现象:调用
notifyListeners()方法时,Stateless组件内除Stateful子组件外的所有子组件均正常更新 - 复现表现:点击触发添加的按钮后,期望输出
First: The value is 1st,实际输出为First: The value is 2nd - 方案约束:生产环境将整个Provider类重构为Stateful组件的实现成本较高,不采用该方案
原始复现代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class Model extends ChangeNotifier { final List<ListElement> elements; Model({required this.elements}); void add() { elements.insert(0, ListElement(name: "First", value: "1st")); notifyListeners(); } } class ListElement { final String name; var value; ListElement({required this.name, required this.value}); } class ValueWidget extends StatefulWidget { final String value; ValueWidget({required this.value}); @override State<StatefulWidget> createState() => _ValueWidget(value: value); } class _ValueWidget extends State<ValueWidget> { String value; _ValueWidget({required this.value}); @override Widget build(BuildContext context) { return Text("The value is ${value}."); } } class StatelessPage extends StatelessWidget { final model = Model(elements: [ ListElement(name: "Second", value: "2nd"), ListElement(name: "Third", value: "3rd")]); @override Widget build(BuildContext context) { return Scaffold( body: ChangeNotifierProvider( create: (context) => model, child: ConsumerWidget()) ); } } class ConsumerWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer<Model>(builder: (context, model, _) { return SingleChildScrollView( child: Container( padding: EdgeInsets.fromLTRB(10, 30, 10, 10000), child: Column( children: [Column( children: model.elements.map((element) { return Row(children: [ Text("${element.name}: "), ValueWidget(value: element.value) ]); }).toList(), ), TextButton(onPressed: model.add, child: Text("Add element to beginning")), ], ), ), ); }); } }
问题根因
Flutter的组件更新采用增量对比机制:新旧组件树对比时,对于StatefulWidget节点,如果其构造传入的属性值没有发生变化,框架会判定该组件无需重建,对应的State对象会被直接复用。
原代码中ValueWidget对应的State类将传入的value存储为自身成员变量,仅在State初始化时完成一次赋值;当列表头部插入新元素后,对应位置的StatefulWidget节点被复用,State中缓存的旧值没有同步更新,最终显示错误内容。
修复方案
不要将依赖全局状态的业务数据直接传入StatefulWidget后缓存到State成员变量中,改为传入数据对应的索引标识,在build方法执行阶段直接从监听的Model中读取最新数据,确保每次状态更新触发重建时都能获取到最新值。
修复后可运行代码
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class Model extends ChangeNotifier { final List<ListElement> elements; Model({required this.elements}); void add() { elements.insert(0, ListElement(name: "First", value: "1st")); notifyListeners(); } } class ListElement { final String name; var value; ListElement({required this.name, required this.value}); } class ValueWidget extends StatefulWidget { final int ind; final Model model; ValueWidget({required this.ind, required this.model}); @override State<StatefulWidget> createState() => _ValueWidget( ind: ind, model: model); } class _ValueWidget extends State<ValueWidget> { final int ind; final Model model; _ValueWidget({required this.ind, required this.model}); @override Widget build(BuildContext context) { // 也可以直接通过Provider获取model,无需从上层传参 // final model = Provider.of<Model>(context, listen: true); // 每次build执行时重新读取数据,是组件能正确响应更新的核心 final element = model.elements[ind]; return Text("The value is ${element.value}."); } } class StatelessPage extends StatelessWidget { final model = Model( elements: [ ListElement(name: "Second", value: "2nd"), ListElement(name: "Third", value: "3rd")] ); @override Widget build(BuildContext context) { return Scaffold( body: ChangeNotifierProvider( create: (context) => model, child: ConsumerWidget()) ); } } class ConsumerWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer<Model>(builder: (context, model, _) { return SingleChildScrollView( child: Container( padding: EdgeInsets.fromLTRB(10, 30, 10, 10000), child: Column( children: [Column( children: model.elements.asMap().entries.map((ele) { return Row(children: [ Text("${ele.value.name}: "), ValueWidget(ind: ele.key, model: model), ]); }).toList(), ), TextButton(onPressed: model.add, child: Text("Add element to beginning")), ], ), ), ); }); } }
内容的提问来源于stack exchange,提问作者beria
相关产品推荐
相关产品推荐

