You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter报错:Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe

问题:LoginScreen中Navigator.pushNamedAndRemoveUntil调用异常

此前正常运行的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会抛出异常。

具体修复步骤

  1. 将StatelessWidget改为StatefulWidget
    可通过State的mounted属性判断Widget是否还在组件树中,避免在dispose后执行导航操作。
  2. 在异步操作后检查mounted状态
    执行Navigator前先判断mounted,确保Widget未被销毁。
  3. 处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:30:43