Flutter中Finish按钮调用reset()与pushNamed()引发并发迭代修改错误
Flutter 发票扫描页面导航后二次扫描报错的修复方案
问题根源分析
你遇到的Concurrent modification during iteration错误,本质是在遍历列表的同时修改了该列表。两种导航方式的差异在于:
- 连续三次
Navigator.pop()会把扫描页面从导航栈中彻底移除,页面的dispose()方法触发,所有监听SupplierModel列表的资源被释放,此时调用reset()修改列表不会有冲突。 - 用
Navigator.pushNamed()时,原扫描页面并未被销毁,仍存活在导航栈中(比如栈结构为Home → Scan → Result → Home),如果扫描页面还在监听SupplierModel的列表变化,此时调用reset()直接修改列表,就会和扫描页面中正在进行的列表遍历操作(比如UI渲染、扫描逻辑中的循环)产生并发冲突。
具体修复步骤
1. 确保扫描页面彻底释放监听资源
在扫描页面的dispose()方法中,取消所有与SupplierModel列表相关的订阅(比如StreamSubscription、ValueListenableSubscription):
class ScanPage extends StatefulWidget { @override _ScanPageState createState() => _ScanPageState(); } class _ScanPageState extends State<ScanPage> { StreamSubscription? _listSubscription; @override void initState() { super.initState(); // 假设SupplierModel有列表变化的流监听 _listSubscription = SupplierModel.instance.listStream.listen((_) { // 处理列表变化逻辑 }); } @override void dispose() { _listSubscription?.cancel(); // 取消订阅,释放关联资源 super.dispose(); } }
2. 安全修改SupplierModel的列表
不要直接修改原列表,而是替换为新列表,避免遍历中的并发冲突。如果SupplierModel是ChangeNotifier类型,修改reset()方法:
class SupplierModel extends ChangeNotifier { List<Item> _items = []; void reset() { // 替换为新列表,而非直接清空原列表 _items = []; notifyListeners(); } }
如果必须修改原列表,可通过Future.microtask延迟修改,确保当前遍历操作完成后再执行:
void reset() { Future.microtask(() { _items.clear(); notifyListeners(); }); }
3. 调整导航逻辑,清理旧页面
用Navigator.pushNamedAndRemoveUntil代替pushNamed,将扫描相关的页面从导航栈中移除,确保旧页面彻底销毁:
// Finish按钮点击逻辑 onPressed: () { SupplierModel.instance.reset(); Navigator.pushNamedAndRemoveUntil( context, '/home', (route) => route.isFirst, // 仅保留首页在栈中 ); },
4. 排查扫描逻辑中的遍历操作
检查扫描页面中是否存在在UI渲染(比如ListView.builder)或扫描处理逻辑中遍历SupplierModel列表的代码,确保这些遍历操作使用列表的不可变副本:
// 错误示例:直接遍历原列表 for (var item in SupplierModel.instance.items) { // 处理逻辑 } // 正确示例:使用副本遍历 var itemsCopy = List.from(SupplierModel.instance.items); for (var item in itemsCopy) { // 处理逻辑 }
内容的提问来源于stack exchange,提问作者Yogev Arye
相关产品推荐
相关产品推荐

