Flutter错误:已停用Widget祖先查找不安全及弹窗返回失效
解决Flutter弹窗跳转后点击按钮报错“Looking up a deactivated widget's ancestor is unsafe”
问题场景
我实现了两个弹窗:showAlertDialog(双按钮确认弹窗)和showAlertDialogWithSingleBtn(单按钮提示弹窗)。流程是:第一个弹窗询问用户是否删除账号,用户点击“Yes”后,先执行Preferences.clearData(context),再通过Navigator.pushAndRemoveUntil跳转到EnterNum页面,随后弹出第二个弹窗。但点击第二个弹窗的“Ok”按钮时无法正常关闭,同时抛出错误:Looking up a deactivated widget's ancestor is unsafe。
相关业务代码
showAlertDialog(context, () { Preferences.clearData(context); Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => EnterNum()), (Route<dynamic> route) => false, ); showAlertDialogWithSingleBtn(context,(){ Navigator.pop(context); },"Alert","Your request for deletion of the account has been accepted and action will be taken in next 30 days. To cancel your request, kindly login in to your account within 30 days.","Ok"); }, "Alert", "Are you sure, you want to delete your account?");
错误信息
Looking up a deactivated widget's ancestor is unsafe. At this point the state of the widget's element tree is no longer stable. To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method. When the exception was thrown, this was the stack: #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:4241:9) #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4255:6) #2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:4322:12) #3 Navigator.of (package:flutter/src/widgets/navigator.dart:2549:40) #4 Navigator.pop (package:flutter/src/widgets/navigator.dart:2433:15) #5 showAlertDialogWithSingleBtn.<anonymous closure> (package:bellaz/Components/alertDialogWithSingleBtn.dart:22:19) #6 _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1072:21) #7 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:253:24) #8 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:627:11) #9 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:306:5) #10 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:239:7) #11 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:615:9) #12 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:98:12) #13 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:143:9) #14 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:617:13) #15 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:141:18) #16 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:127:7) #17 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:460:19) #18 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:440:22) #19 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:337:11) #20 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:395:7) #21 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:357:5) #22 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:314:7) #23 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:295:7) #24 _invoke1 (dart:ui/hooks.dart:167:13) #25 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:341:7) #26 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31) Handler: "onTap" Recognizer: TapGestureRecognizer#2141e debugOwner: GestureDetector state: possible won arena finalPosition: Offset(205.7, 555.4) finalLocalPosition: Offset(42.5, 18.3) button: 1 sent tap down
弹窗实现代码
showAlertDialog 实现
showAlertDialog(BuildContext context,VoidCallback? onPressed,String heading,String msg) { Widget cancelButton = TextButton( style: TextButton.styleFrom( backgroundColor: Color(0xfff64e60), padding: EdgeInsetsDirectional.only(start: 36,end: 36), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.0), ), ), child: Text( "No", style: TextStyle(color: Colors.white), ), onPressed: () { Navigator.pop(context); }, ); Widget continueButton = TextButton( style: TextButton.styleFrom( backgroundColor:Color(0xff007669), padding: EdgeInsetsDirectional.only(start: 34,end: 34), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.0), ), ), child: Text( "Yes", style: TextStyle(color: Colors.white), ), onPressed:onPressed ); AlertDialog alert = AlertDialog( titlePadding: EdgeInsets.only(top: 13), title: Container( height: 40, child: Center( child: Text( "$heading", style: TextStyle(color:apptitleColor), ), ), ), content: Text("$msg",textAlign: TextAlign.center,style: TextStyle(fontSize: t3Size),), actions: [ cancelButton, continueButton, ], actionsAlignment: MainAxisAlignment.center, ); showDialog( context: context, builder: (BuildContext context) { return alert; }, ); }
showAlertDialogWithSingleBtn 实现
showAlertDialogWithSingleBtn(BuildContext context,VoidCallback? onPressed,String heading,String msg,String btnText) { Widget continueButton = TextButton( style: TextButton.styleFrom( backgroundColor:Color(0xff007669), padding: EdgeInsetsDirectional.only(start: 34,end: 34), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.0), ), ), child: Text( "$btnText", style: TextStyle(color: Colors.white), ), onPressed:onPressed ); AlertDialog alert = AlertDialog( titlePadding: EdgeInsets.only(top: 13), title: Container( height: 40, child: Center( child: Text( "$heading", style: TextStyle(color:apptitleColor), ), ), ), content: Text("$msg",textAlign: TextAlign.center,style: TextStyle(fontSize: t3Size),), actions: [ Row(mainAxisAlignment: MainAxisAlignment.center, children: [ continueButton, ], ), ], actionsAlignment: MainAxisAlignment.center, ); showDialog( context: context, builder: (BuildContext context) { return alert; }, ); }
问题原因
核心问题是使用了已失效的Context:
- 执行
Navigator.pushAndRemoveUntil后,原页面的Widget已经被从Widget树中移除并销毁,传入的context对应的Element已经失效。 - 用这个失效的
context调用showAlertDialogWithSingleBtn,弹窗的回调Navigator.pop(context)尝试通过已销毁的Element查找Navigator,就会触发这个错误。
解决方案
方式一:在新页面的initState中弹出提示弹窗(推荐)
把第二个弹窗的逻辑移到EnterNum页面的初始化方法中,使用新页面的有效Context:
- 修改业务代码,去掉跳转后的弹窗调用:
showAlertDialog(context, () { Preferences.clearData(context); Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => EnterNum(showDeletionAlert: true)), (Route<dynamic> route) => false, ); }, "Alert", "Are you sure, you want to delete your account?");
- 修改
EnterNum页面,添加参数和弹窗逻辑:
class EnterNum extends StatefulWidget { final bool showDeletionAlert; const EnterNum({Key? key, this.showDeletionAlert = false}) : super(key: key); @override State<EnterNum> createState() => _EnterNumState(); } class _EnterNumState extends State<EnterNum> { @override void initState() { super.initState(); if(widget.showDeletionAlert) { WidgetsBinding.instance.addPostFrameCallback((_) { showAlertDialogWithSingleBtn(context,(){ Navigator.pop(context); },"Alert","Your request for deletion of the account has been accepted and action will be taken in next 30 days. To cancel your request, kindly login in to your account within 30 days.","Ok"); }); } } // 其他页面代码... }
方式二:修改弹窗函数,使用弹窗内部的Context
修改showAlertDialogWithSingleBtn,让回调使用弹窗builder内部的Context,避免依赖外部传入的失效Context:
void showAlertDialogWithSingleBtn( BuildContext context, VoidCallback? onPressed, String heading, String msg, String btnText, ) { showDialog( context: context, builder: (BuildContext dialogContext) { // 使用弹窗内部的dialogContext return AlertDialog( titlePadding: EdgeInsets.only(top: 13), title: Container( height: 40, child: Center( child: Text( heading, style: TextStyle(color: apptitleColor), ), ), ), content: Text(msg, textAlign: TextAlign.center, style: TextStyle(fontSize: t3Size)), actions: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( style: TextButton.styleFrom( backgroundColor: Color(0xff007669), padding: EdgeInsetsDirectional.only(start: 34, end: 34), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.0), ), ), child: Text( btnText, style: TextStyle(color: Colors.white), ), onPressed: () { Navigator.pop(dialogContext); // 用dialogContext关闭弹窗 onPressed?.call(); // 执行外部传入的回调(如果需要) }, ), ], ), ], actionsAlignment: MainAxisAlignment.center, ); }, ); }
内容的提问来源于stack exchange,提问作者Deepak
相关产品推荐
相关产品推荐

