ChangeNotifier提前Dispose致Firestore事务错误无法处理求解决方案
问题背景
你原本用Firestore事务来避免多用户添加的竞态条件,超时会显示错误对话框。但把逻辑移到继承ChangeNotifier的Model2(结合Provider的ChangeNotifierProxyProvider)后,出现了Unhandled Exception: A Model2 was used after being disposed...的错误。移除ChangeNotifierProxyProvider前build方法里的fetchData()后问题消失,你想知道修复原因和完整解决方案。
为什么移除fetchData()就好了?
核心原因是**build方法会被频繁触发**(比如屏幕旋转、父组件重建、状态变化等),而你在build里调用的fetchData()大概率会修改Model1的状态——这会触发ChangeNotifierProxyProvider重建Model2的实例,旧的Model2会被调用dispose()。
但此时旧Model2发起的Firestore事务还在异步执行中,当事务完成(成功或失败)去调用notifyListeners()或者修改userAdded属性时,旧的Model2已经被销毁了,自然就抛出了“被disposed后仍被使用”的错误。
完整解决方案
我们需要从生命周期管理和异步操作安全两个层面来修复:
1. 把fetchData()移到正确的生命周期方法
永远不要在build里执行异步操作(比如网络请求、状态修改),因为build会重复执行,会导致重复请求、不必要的状态重建。应该把fetchData()放在StatefulWidget的initState或者didChangeDependencies里,并且加判断避免重复调用:
class YourWidget extends StatefulWidget { @override _YourWidgetState createState() => _YourWidgetState(); } class _YourWidgetState extends State<YourWidget> { bool _hasFetchedData = false; @override void didChangeDependencies() { super.didChangeDependencies(); // 只在首次加载时调用fetchData if (!_hasFetchedData) { fetchData(); _hasFetchedData = true; } } void fetchData() { // 你的fetchData逻辑,比如修改Model1的状态 } @override Widget build(BuildContext context) { return ChangeNotifierProxyProvider<Model1, Model2>( initialBuilder: (_) => Model2(), builder: (_, model1, model2) => model2..string = model1.string, child: Consumer<Model2>( builder: (context, model2, _) => ElevatedButton( onPressed: () async { await model2.addUser(); }, child: Text("Add User"), ), ), ); } }
2. 在Model2中添加“已销毁”标志,保护异步操作
即使我们优化了fetchData()的调用时机,还是要防止极端情况(比如用户在事务完成前退出页面,导致Model2被dispose)。在Model2里加一个_disposed标志,在dispose()时标记,异步操作完成后先检查这个标志再执行后续逻辑:
class Model2 extends ChangeNotifier { bool userAdded = false; bool _disposed = false; // 你的其他属性:chatRoomDocRef、chatUserRef等 Future<bool> addUser() async { try { await Firestore.instance.runTransaction((transaction) async { DocumentSnapshot chatRoomDocSnapshot = await transaction.get(chatRoomDocRef); bool userInChat = chatRoomDocSnapshot['userInChat']; if (!userInChat) { await transaction.update(chatRoomDocSnapshot.reference, { 'userInChat': true, }); } }); await chatUserRef.setData({ 'User': user, }); // 检查是否已销毁,再更新状态 if (!_disposed) { userAdded = true; notifyListeners(); } } catch (e) { print(e); // 同样检查后更新状态 if (!_disposed) { userAdded = false; notifyListeners(); } } return userAdded; } @override void dispose() { _disposed = true; super.dispose(); } }
3. 优化ChangeNotifierProxyProvider的使用
确保Model2的重建逻辑是必要的——只有当Model1的string属性变化时才更新Model2,避免不必要的重建。如果Model1的其他属性变化不需要触发Model2更新,可以在builder里做更精准的判断,或者使用select来监听特定属性。
这样调整后,既解决了Model2被提前dispose的问题,也保证了异步操作的安全性,同时符合Provider和Flutter生命周期的最佳实践。
内容的提问来源于stack exchange,提问作者FlutterFirebase

