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

Flutter延迟调用Navigator.pushReplacement报空值检查运算符异常

延迟调用Navigator.pushReplacement抛出空检查异常问题

异常信息

延迟执行路由替换跳转时,控制台抛出如下错误:

[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Null check operator used on a null value
E/flutter ( 8437): #0      StatefulElement.state (package:flutter/src/widgets/framework.dart:4926:44)
E/flutter ( 8437): #1      Navigator.of (package:flutter/src/widgets/navigator.dart:2542:47)
E/flutter ( 8437): #2      Navigator.pushReplacement (package:flutter/src/widgets/navigator.dart:2103:22)

复现场景

  • 基础触发逻辑:通过Timer延迟3秒执行Navigator.pushReplacement跳转,代码如下:
Timer(Duration(seconds: 3), () {
  Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MenuScreen()));
});
  • 跳转目标页MenuScreen代码:
class MenuScreen extends StatefulWidget {
  const MenuScreen({Key? key}) : super(key: key);

  @override
  State<MenuScreen> createState() => _MenuScreenState();
}

class _MenuScreenState extends State<MenuScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My Sessions'),),
      body: Container(),
    );
  }
}
  • 测试发现两个特殊现象:
    • 相同跳转逻辑绑定在按钮点击事件中触发时,无任何报错
    • 将pushReplacement替换为Navigator.push方法时,不会触发该异常
  • 问题对应的启动页SplashScreen完整代码:
class SplashScreen extends StatefulWidget {
  const SplashScreen({Key? key}) : super(key: key);

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  String token = '';

  @override
  void initState() {
    SharedPreferences.getInstance().then((pref) {
      String token = pref.getString('token') ?? '';
      setState(() {
        this.token = token;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Timer(Duration(seconds: 3), () {
      Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MenuScreen()));
    });

    return Scaffold(
      backgroundColor: const Color(0xff193C68),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
                'assets/strivright_logo.png',
                width: MediaQuery.of(context).size.width * 0.75),
            SizedBox(height: MediaQuery.of(context).size.height * 0.2),
            const CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(Colors.white)),
          ],
        ),
      ),
    );
  }
}

问题原因

异常由两个问题叠加导致:

  1. 定时器位置错误:跳转用的Timer写在了build方法内部。Flutter的build方法会在组件首次构建、setState触发更新、父组件重绘等多个时机反复执行,每执行一次build就会新建一个3秒延迟的Timer,最终会生成多个排队的跳转任务。
  2. 路由方法特性与失效context问题:pushReplacement执行后会将当前SplashScreen路由从导航栈中移除,对应的组件Element会被从组件树卸载,持有的context失效。当第一个Timer执行完跳转后,后续排队的Timer再触发时,传入的context已经是被卸载的无效状态,Navigator.of(context)内部尝试通过context查找挂载的NavigatorState时拿到null,触发空检查操作符异常。

两个特殊现象的原因:

  • 按钮点击无报错:点击事件触发时context处于正常挂载状态,且不会重复创建多个跳转任务,执行时context有效。
  • 换成push无报错:push是在当前路由上方压入新路由,原SplashScreen路由仍保留在导航栈中,即使后续Timer重复执行,context对应的Element未被完全销毁,不会触发空值异常(但仍会重复压入多个MenuScreen页面,属于隐藏逻辑bug)。

解决方案

  1. 将Timer跳转逻辑从build方法移到initState生命周期中,保证SplashScreen整个生命周期内只会创建一次定时器,避免重复生成跳转任务。
  2. 执行跳转前增加mounted状态判断,组件已销毁时不执行跳转逻辑,避免使用失效context。
  3. 在dispose生命周期中取消未执行的定时器,避免内存泄漏和无效任务残留。

修正后的SplashScreen代码如下:

class _SplashScreenState extends State<SplashScreen> {
  String token = '';
  Timer? _navigateTimer;

  @override
  void initState() {
    super.initState();
    // 初始化读取本地存储token
    SharedPreferences.getInstance().then((pref) {
      token = pref.getString('token') ?? '';
      if (mounted) {
        setState(() {
          this.token = token;
        });
      }
    });
    // 定时器仅初始化一次
    _navigateTimer = Timer(const Duration(seconds: 3), () {
      // 校验组件挂载状态再执行跳转
      if (mounted) {
        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (_) => const MenuScreen())
        );
      }
    });
  }

  @override
  void dispose() {
    // 组件销毁时取消定时器
    _navigateTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xff193C68),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
                'assets/strivright_logo.png',
                width: MediaQuery.of(context).size.width * 0.75),
            SizedBox(height: MediaQuery.of(context).size.height * 0.2),
            const CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(Colors.white)),
          ],
        ),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 23:21:24