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

Flutter使用Provider执行pop时空检查运算符作用于空值报错

Flutter 登录页pop操作偶发空指针错误修复

原有实现代码

我有一个登录按钮,点击后会执行如下函数:

static Future loginPress(BuildContext context, TextEditingController emailController, TextEditingController passwordController) async {
    Loader(context);
    try {
      await MyUser.signIn(emailController.text.trim(), passwordController.text.trim());
      Navigator.of(context).pop();
      Navigator.of(context).pushReplacementNamed(Routes.authPage);
        
    } on CustomException catch (e) {
      Navigator.of(context).pop();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(e.message.toString()))
      );
    }
    return null;
  }

上述代码中调用的Loader加载组件实现逻辑如下:

Future<dynamic> Loader(BuildContext context) {
  return showDialog(
    context: context, 
    builder: (context){
      return Center(child: CircularProgressIndicator(color: COLOR_PRIMARY),);
    }
  );
}

第一段代码中调用的MyUser.signIn方法实现如下:

static Future signIn(String email, String password) async {
    if(email.isEmpty || password.isEmpty) throw CustomException('Either email or password field is empty');
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password);
    } on FirebaseAuthException catch (e) {
      print(e.message);
      throw CustomException('Wrong password or email');
    }
  }

报错日志

_dirty:false
_hadUnsatisfiedDependencies:false
_inDirtyList:false
_inheritedWidgets:null
_lifecycleState:_ElementLifecycle (_ElementLifecycle.defunct)
_owner:BuildOwner
_parent:null
_slot:null
_state:null
_widget:null
debugDoingBuild:false
debugIsDefunct:true
depth:121
dirty:false
hashCode:138
owner:BuildOwner
renderObject:null
runtimeType:Type (StatefulElement)
size:<Cannot get size of inactive element.
In order for an element to have a valid size, the element must be active, which means it is part of the tree.
Instead, this element is in the _ElementLifecycle.defunct state.
The size getter was called for the following element:
  StatefulElement#0008a(DEFUNCT)
#0      Element.size.<anonymous closure> (package:flutter/src/widgets/framework.dart:4029:9)
#1      Element.size (package:flutter/src/widgets/framework.dart:4062:6)
#2      StatefulElement.Eval ()
#3      AuthViewModel.loginPress (package:better_health/view_model/auth_view_model.dart:49:17)
<asynchronous suspension>>
slot:null
state:<Null check operator used on a null value
#0      StatefulElement.state (package:flutter/src/widgets/framework.dart:4877:44)
#1      StatefulElement.Eval ()
#2      AuthViewModel.loginPress (package:better_health/view_model/auth_view_model.dart:49:17)
<asynchronous suspension>>
widget:<Null check operator used on a null value

问题表现

在上述第一段代码中执行Navigator.of(context).pop()操作时,会偶发*"Null check operator used on a null value"*错误,该问题仅在部分场景下出现,其余场景功能可正常运行。

问题根因

这个错误的核心原因是异步操作执行期间,传入的BuildContext已经失效:

  • 调用Loader(context)弹出加载弹窗后,代码会执行await MyUser.signIn(...)等待登录请求返回,这个等待过程如果出现用户手动按返回键、页面因其他逻辑被销毁(组件unmount、路由被提前弹出)的情况,原来传入的context对应的Element就会进入defunct(失效)状态,和组件树完全解绑。
  • 这时候再调用Navigator.of(context)获取导航器实例会得到空值,后续执行pop、pushReplacementNamed等操作时就会触发空指针报错。
  • 问题偶发的原因很明确:只有登录请求等待时间较长(比如弱网环境)、用户有时间在请求返回前操作关闭当前页面时才会触发;网络状态好、请求返回快的时候用户来不及操作,功能就表现正常。

修复方案

  • 第一步:所有await异步操作执行完成后,调用context相关API前必须先校验当前组件是否仍处于挂载状态,禁止对失效context执行操作。
  • 第二步:加载弹窗默认设置为不可手动关闭,避免用户在请求过程中点击返回/遮罩层关闭页面,从场景上减少context提前失效的概率。
  • 第三步:修改对应代码实现:

首先修改登录方法的逻辑:

static Future loginPress(BuildContext context, TextEditingController emailController, TextEditingController passwordController) async {
    // 进入方法先校验context有效性
    if (!context.mounted) return;
    // 弹出加载框,禁止加载期间通过返回键/点击遮罩关闭
    Loader(context, barrierDismissible: false);
    try {
      await MyUser.signIn(emailController.text.trim(), passwordController.text.trim());
      // 异步请求返回后,再次校验context是否有效
      if (!context.mounted) return;
      // 关闭加载弹窗
      Navigator.of(context).pop();
      // 路由跳转前第三次校验
      if (!context.mounted) return;
      Navigator.of(context).pushReplacementNamed(Routes.authPage);
        
    } on CustomException catch (e) {
      // 异常分支同样先校验context有效性
      if (!context.mounted) return;
      Navigator.of(context).pop();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(e.message.toString()))
      );
    }
    return null;
  }

同步修改Loader组件,支持控制弹窗是否可手动关闭:

Future<dynamic> Loader(BuildContext context, {bool barrierDismissible = true}) {
  return showDialog(
    context: context, 
    barrierDismissible: barrierDismissible,
    builder: (context){
      return const Center(child: CircularProgressIndicator(color: COLOR_PRIMARY),);
    }
  );
}

注意:context.mounted是Flutter 3.7及以上版本提供的内置校验属性,如果你的项目使用的Flutter版本低于3.7,在State类中可以直接调用内置的mounted属性判断状态,不要在跨异步的场景中不加校验直接使用context。

内容的提问来源于stack exchange,提问作者FiFi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 00:18:19