这个错误常出现在异步任务(Future)处理,比如某个页面请求一个网络API数据,根据数据刷新 Widget State。
异步任务结束在页面被pop之后,但没有检查State 是否还是 mounted
,继续调用 setState
就会出现这个错误。
示例代码
一段很常见的获取网络数据的代码,调用 requestApi()
,等待Future从中获取response
,进而setState
刷新 Widget:
1 2 3 4 5 6 7 8 9 10 | class AWidgetState extends State<AWidget> { // ... var data; void loadData() async { var response = await requestApi(...); setState((){ this.data = response.data; }) } } |
原因分析
response
的获取为async-await
异步任务,完全有可能在AWidgetState
被 dispose
之后才等到返回,那时候和该State
绑定的 Element
已经不在了。故而在setState
时需要容错。
解决办法: setState
之前检查是否 mounted
1 2 3 4 5 6 7 8 9 10 11 12 | class AWidgetState extends State { // ... var data; void loadData() async { var response = await requestApi(...); if (mounted) { setState((){ this.data = response.data; }) } } } |
这个mounted
检查很重要,其实只要涉及到异步还有各种回调(callback),都不要忘了检查该值。
比如,在 FrameCallback
里执行一个动画(AnimationController):
1 2 3 4 5 6 | @override void initState(){ WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _animationController.forward(); }); } |
AnimationController
有可能随着 State 一起 dispose
了,但是FrameCallback
仍然会被执行,进而导致异常。
又比如,在动画监听的回调里搞点事:
1 2 3 4 5 6 7 8 | @override void initState(){ _animationController.animation.addListener(_handleAnimationTick); } void _handleAnimationTick() { if (mounted) updateWidget(...); } |
同样的在_handleAnimationTick
被回调前,State 也有可能已经被dispose
了。