showModalBottomSheet和Unhandled Exception: setState() called after dispose() on parent widget
showModalBottomSheet和Unhandled Exception: setState() called after dispose() on parent widget
背景:
我有一个模态底部表单,选择相机/相册后获取/选择一个图像XFile
,并返回进行处理(上传),使用image_picker的帮助。
这是一个示例代码:
ListTile(
onTap: () {
// definition: Future
showCamera(orientation).then((value) => Navigator.of(context).pop
},
...
),
使用showModalBottomSheet选择图像,通过返回选择的XFile
并在链式函数_handleFile(XFile, enum)
上进行处理:
return showModalBottomSheet
context: context,
builder: (context) {
return SingleChildScrollView(
child: ListBody(
children: [
...
ListTile(
onTap: () {
showCamera(orientation).then((value) => Navigator.of(context).pop
},
leading: Icon(Icons.camera),
title: Text("From Camera"),
),
...
],
),
);
},
).then((value) => _handleFile(value, orientation));
问题是什么:
在_handle(XFile?, int)
中处理文件时,我需要更新应用程序的状态以显示进度条更新、循环指示器、上传状态等。
Future
if (xfile == null) {
return Future.value();
}
// store locally with Uploading Status
var imageService = locator
setState(() { <-------- 冲突行(ui_partner_registration_id_photos.dart:103:5)
remoteImageStatus[xfile] = UploadStatus.Uploading;
images[orientation] = xfile;
});
// Upload and update result / error
return imageService.uploadIDPhoto(File(xfile.path), orientation).then((value) {
setState(() {
idPhotos[orientation] = value;
remoteImageStatus[xfile] = UploadStatus.Done;
});
print("Uploaded [${xfile.path}]");
}).onError((error, stackTrace) {
print("Error uploading image");
print(stackTrace);
setState(() {
remoteImageStatus[xfile] = UploadStatus.Error;
});
});
}
为什么会有问题:
在不再可见/活跃/聚焦的有状态小部件上调用setState()是不允许的,现在showModalBottomSheet
是这种情况。也就是说,在调用Navigator.pop()
后,这不再是问题,因为父有状态小部件现在处于聚焦状态,这让我感到困惑。
(临时)解决方案
一个临时解决方案(不完全满足期望的结果)是添加一个mounted
检查,如此处所述,示例在这里:
if (mounted) {
setState((){
// 执行操作
})
}
堆栈跟踪:
[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: setState() called after dispose(): _RegisterIDPhotosState#b75f9(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
#0 State.setState.
#1 State.setState (package:flutter/src/widgets/framework.dart:1087:6)
#2 _RegisterIDPhotosState._handleFile (my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:103:5)
#3 _RegisterIDPhotosState.pickImageWithModalPopup.
#4 _rootRunUnary (dart:async/zone.dart:1362:47)
#5 _CustomZone.runUnary (dart:async/zone.dart:1265:19)
问题:
在选择文件并开始上传过程后,我如何调用setState()
,就像上面_handleFile(XFile?, int)
的示例中那样?
在Flutter中,当使用showModalBottomSheet弹出一个底部对话框时,可能会遇到一个异常:setState() called after dispose() on parent widget。这个问题的原因是在弹出的对话框关闭后,父级widget已经被销毁,但是在这之后仍然调用了setState方法。
为了解决这个问题,可以将逻辑重构为在widget树的更高层次使用ChangeNotifier或ValueNotifier,并使得你的Widgets使用它来共享状态。这样做的好处是,不再需要使用setState方法来更新状态,而是通过Notifier来管理状态的改变。具体的实现方法可以参考官方文档(https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple)。
需要注意的是,使用setState方法处理状态改变的方式在这种情况下是行不通的,因为你处理的是两个不同的widget。setState方法只会在调用它的具体widget中执行回调函数并调用markNeedsBuild方法来标记需要重新构建,但是在你的情况下,该widget已经被销毁,因此无法再调用setState方法。
推荐使用官方文档中介绍的方法来在Flutter应用中共享状态,避免出现setState() called after dispose() on parent widget的异常。