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

使用go_router包实现启动页的正确方式及报错修复

问题:启动页跳转时出现「Looking up a deactivated widget's ancestor is unsafe」错误

我希望用户打开应用时显示启动页(SplashScreen),同时加载数据,加载完成后根据数据跳转至对应页面。当前用go_router包实现了功能,但出现「Looking up a deactivated widget's ancestor is unsafe」错误,附上代码及错误日志,求问题原因与修复方法。

代码示例

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  final GoRouter _router = GoRouter(
    initialLocation: '/',
    routes: <RouteBase>[
      GoRoute(
        path: '/',
        builder: (context, state) => const SplashScreen(),
      ),
      GoRoute(
        path: '/a',
        builder: (context, state) => const ScreenA(),
      )
    ],
  );

  @override
  Widget build(BuildContext context) =>
      MaterialApp.router(routerConfig: _router);
}

class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key});

  @override
  Widget build(BuildContext context) {
    Future.delayed(const Duration(seconds: 1))
        .then((value) => GoRouter.of(context).go('/a')); // !!!
    return const Center(child: FlutterLogo());
  }
}

class ScreenA extends StatelessWidget {
  const ScreenA({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('Screen A'),
      ),
    );
  }
}

错误日志

Error: Looking up a deactivated widget's ancestor is unsafe.
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dar
t 266:49  throw_
packages/flutter/src/widgets/framework.dart 4241:9
<fn>
packages/flutter/src/widgets/framework.dart 4254:14
[_debugCheckStateIsActiveForAncestorLookup]
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 557:7                                     [_complete]
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future.dart 421:15                                         <fn>
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:19       internalCallback

问题原因

问题出在SplashScreen的build方法里:当Future.delayed延迟1秒执行跳转时,原SplashScreen组件可能已经被Flutter标记为失活状态(deactivated)。因为路由跳转后,Flutter会重建widget树,旧的SplashScreen实例会被销毁或从widget树中移除,此时再通过GoRouter.of(context)获取路由,相当于尝试访问一个已失效widget的上下文,触发了Flutter的安全检查机制,从而抛出错误。

另外,build方法可能会被Flutter多次调用(比如屏幕旋转、父组件重建),把异步跳转放在build里还会导致重复触发跳转的问题。

修复方法

方案1:改用StatefulWidget+mounted检查

把SplashScreen改成StatefulWidget,将异步操作放在initState(只会执行一次)里,并在跳转前检查组件是否还挂载在widget树上:

class SplashScreen extends StatefulWidget {
  const SplashScreen({super.key});

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

class _SplashScreenState extends State<SplashScreen> {
  @override
  void initState() {
    super.initState();
    // 异步操作放在initState,避免重复触发
    Future.delayed(const Duration(seconds: 1)).then((_) {
      if (mounted) { // 检查组件是否仍处于活跃状态
        GoRouter.of(context).go('/a');
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return const Center(child: FlutterLogo());
  }
}

方案2:直接传入GoRouter实例跳转

在MyApp中已经创建了_router实例,可以直接把它传递给SplashScreen,这样跳转时不需要依赖上下文,自然避免上下文失效问题:

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  final GoRouter _router = GoRouter(
    initialLocation: '/',
    routes: <RouteBase>[
      GoRoute(
        path: '/',
        builder: (context, state) => SplashScreen(router: _router), // 传递路由实例
      ),
      GoRoute(
        path: '/a',
        builder: (context, state) => const ScreenA(),
      )
    ],
  );

  @override
  Widget build(BuildContext context) =>
      MaterialApp.router(routerConfig: _router);
}

class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key, required this.router});

  final GoRouter router;

  @override
  Widget build(BuildContext context) {
    Future.delayed(const Duration(seconds: 1))
        .then((value) => router.go('/a')); // 使用传入的路由实例跳转
    return const Center(child: FlutterLogo());
  }
}

方案3:用FutureBuilder处理数据加载(推荐)

如果实际场景是加载数据,用FutureBuilder更符合Flutter的状态管理逻辑,同时能避免上下文失效:

class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key});

  // 模拟实际的数据加载逻辑
  Future<void> _loadAppData() async {
    await Future.delayed(const Duration(seconds: 1));
    // 这里可以添加数据解析、初始化等操作
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _loadAppData(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          // 加载完成后,在下一帧执行跳转
          WidgetsBinding.instance.addPostFrameCallback((_) {
            GoRouter.of(context).go('/a');
          });
        }
        return const Center(child: FlutterLogo());
      },
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 12:15:39