Flutter报错:Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe
此前正常运行的LoginScreen代码现出现异常,错误指向loginUser方法中的Navigator.pushNamedAndRemoveUntil调用位置。我查阅过类似问题,但不清楚如何在dispose方法中使用该API,相关代码如下:
class LoginScreen extends StatelessWidget { // const LoginScreen({Key key}) : super(key: key); static const String idScreen = "login"; final TextEditingController nameTextEditingController = TextEditingController(); final TextEditingController phoneTextEditingController = TextEditingController(); final TextEditingController passwordTextEditingController = TextEditingController(); final TextEditingController emailTextEditingController = TextEditingController(); final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ SizedBox( height: 35.0, ), Image( image: AssetImage("images/images/logo.png"), width: 390.0, height: 350.0, alignment: Alignment.center, ), SizedBox( height: 1.0, ), Text( "Login as Driver", style: TextStyle(fontSize: 24, fontFamily: "Brand Bold"), textAlign: TextAlign.center, ), Padding( padding: EdgeInsets.all(20.0), child: Column( children: [ SizedBox( height: 1.0, ), TextField( controller: emailTextEditingController, keyboardType: TextInputType.emailAddress, decoration: InputDecoration( labelText: "Email", labelStyle: TextStyle( fontSize: 18.0, ), ), style: TextStyle(fontSize: 18.0), ), SizedBox( height: 1.0, ), TextField( controller: passwordTextEditingController, obscureText: true, decoration: InputDecoration( labelText: "Password", labelStyle: TextStyle( fontSize: 18.0, ), ), style: TextStyle(fontSize: 14.0), ), SizedBox(height: 10.0), ElevatedButton( //color: Colors.pink[200], //textColor: Colors.white, child: Container( height: 50.0, child: Center( child: Center( child: Text( "Login", style: TextStyle( fontSize: 18.0, fontFamily: "Brand-Bold"), ), ), ), ), //shape: new RoundedRectangleBorder( // borderRadius: new BorderRadius.circular(24.0)), onPressed: () { if (!emailTextEditingController.text.contains("@")) { displayToastMessage( "Email address is not valid", context); } else if (passwordTextEditingController.text.isEmpty) { displayToastMessage("password is not valid", context); } else { loginUser(context); } }, ), ], ), ), TextButton( onPressed: () { Navigator.pushNamedAndRemoveUntil( context, RegistrationScreen.idScreen, (route) => false); }, child: Text( "Account not found, Please register", style: TextStyle(color: Colors.black), ), ), ], ), ), ), ); } void loginUser(BuildContext context) async { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return ProgressDialog( message: "Authenticating, Please wait...", ); }); final User? firebaseUser = (await _firebaseAuth .signInWithEmailAndPassword( email: emailTextEditingController.text, password: passwordTextEditingController.text) .catchError((errMsg) { Navigator.pop(context); displayToastMessage("Error: " + errMsg.toString(), context); })) .user; if (firebaseUser != null) { driversRef.child(firebaseUser.uid).once().then((DatabaseEvent event) { if (event.snapshot.value != null) { Navigator.pushNamedAndRemoveUntil( context, MainScreen.idScreen, (route) => false); displayToastMessage("You are logged in", context); } else { Navigator.pop(context); _firebaseAuth.signOut(); displayToastMessage( "No record exists for this user, please create a new account", context); } }); } else { Navigator.pop(context); displayToastMessage("An error occured, cannot sign in", context); } } }
解决方案
核心问题
异常本质是异步操作完成时,当前Widget已被销毁(dispose),导致使用的context失效。你的LoginScreen是StatelessWidget,无法监听自身生命周期状态,异步操作(Firebase登录、数据库查询)完成后,可能页面已被用户操作退出,此时调用Navigator会抛出异常。
具体修复步骤
- 将StatelessWidget改为StatefulWidget
可通过State的mounted属性判断Widget是否还在组件树中,避免在dispose后执行导航操作。 - 在异步操作后检查mounted状态
执行Navigator前先判断mounted,确保Widget未被销毁。 - 处理Dialog的context嵌套问题
showDialog会创建新的context,弹出对话框后,后续的Navigator.pop(context)是关闭对话框而非当前页面,需注意context层级。
修改后的代码示例
class LoginScreen extends StatefulWidget { static const String idScreen = "login"; const LoginScreen({Key? key}) : super(key: key); @override State<LoginScreen> createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { final TextEditingController nameTextEditingController = TextEditingController(); final TextEditingController phoneTextEditingController = TextEditingController(); final TextEditingController passwordTextEditingController = TextEditingController(); final TextEditingController emailTextEditingController = TextEditingController(); final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; @override void dispose() { // 销毁所有控制器,避免内存泄漏 nameTextEditingController.dispose(); phoneTextEditingController.dispose(); passwordTextEditingController.dispose(); emailTextEditingController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ const SizedBox(height: 35.0), Image( image: const AssetImage("images/images/logo.png"), width: 390.0, height: 350.0, alignment: Alignment.center, ), const SizedBox(height: 1.0), const Text( "Login as Driver", style: TextStyle(fontSize: 24, fontFamily: "Brand Bold"), textAlign: TextAlign.center, ), Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ const SizedBox(height: 1.0), TextField( controller: emailTextEditingController, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: "Email", labelStyle: TextStyle(fontSize: 18.0), ), style: const TextStyle(fontSize: 18.0), ), const SizedBox(height: 1.0), TextField( controller: passwordTextEditingController, obscureText: true, decoration: const InputDecoration( labelText: "Password", labelStyle: TextStyle(fontSize: 18.0), ), style: const TextStyle(fontSize: 14.0), ), const SizedBox(height: 10.0), ElevatedButton( child: Container( height: 50.0, child: const Center( child: Text( "Login", style: TextStyle( fontSize: 18.0, fontFamily: "Brand-Bold"), ), ), ), onPressed: () { if (!emailTextEditingController.text.contains("@")) { displayToastMessage("邮箱地址无效", context); } else if (passwordTextEditingController.text.isEmpty) { displayToastMessage("密码不能为空", context); } else { loginUser(); } }, ), ], ), ), TextButton( onPressed: () { if (mounted) { Navigator.pushNamedAndRemoveUntil( context, RegistrationScreen.idScreen, (route) => false); } }, child: const Text( "没有账号?请注册", style: TextStyle(color: Colors.black), ), ), ], ), ), ), ); } void loginUser() async { showDialog( context: context, barrierDismissible: false, builder: (BuildContext dialogContext) { return const ProgressDialog( message: "验证中,请稍候...", ); }, ); try { final UserCredential userCredential = await _firebaseAuth.signInWithEmailAndPassword( email: emailTextEditingController.text, password: passwordTextEditingController.text, ); final User? firebaseUser = userCredential.user; if (firebaseUser != null) { final DatabaseEvent event = await driversRef.child(firebaseUser.uid).once(); if (mounted) { Navigator.pop(context); // 关闭加载对话框 if (event.snapshot.value != null) { Navigator.pushNamedAndRemoveUntil( context, MainScreen.idScreen, (route) => false); displayToastMessage("登录成功", context); } else { await _firebaseAuth.signOut(); displayToastMessage("该用户无记录,请注册新账号", context); } } } else { if (mounted) { Navigator.pop(context); displayToastMessage("登录失败,发生未知错误", context); } } } catch (errMsg) { if (mounted) { Navigator.pop(context); displayToastMessage("错误: ${errMsg.toString()}", context); } } } }
关键改动说明
- 改为StatefulWidget,在
dispose方法中销毁所有TextEditingController,避免内存泄漏。 - 所有异步操作改用
try-catch替代catchError,代码逻辑更清晰。 - 执行任何Navigator操作前都检查
mounted状态,确保Widget未被销毁。 - 移除loginUser方法的context参数,直接使用State的context,避免传递过程中context失效。
内容的提问来源于stack exchange,提问作者Pannam
相关产品推荐
相关产品推荐

