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

Flutter GoRouter嵌套导航安卓返回键点击后响应异常问题

Flutter GoRouter嵌套导航安卓返回键异常问题解决

问题现象

  • 用GoRouter实现嵌套导航后,正常导航功能正常,但点击安卓设备返回键时,底部导航栏包裹的嵌套导航出现加载缓慢或无法点击的情况
  • 触发时伴随日志:
W/WindowOnBackDispatcher( 9978): sendCancelIfRunning: isInProgress=falsecallback=io.flutter.embedding.android.FlutterActivity$1@2a3415f
D/EGL_emulation( 9978): app_time_stats: avg=165.14ms min=5.89ms max=2133.05ms count=14
  • 再次点击返回键会直接回到手机主屏幕
  • 此前曾触发错误:
OnBackInvokedCallback is not enabled for the application.
Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.

已尝试的无效操作

  • 在AndroidManifest.xml的application层级添加android:enableOnBackInvokedCallback="true"
  • 更新Flutter及GoRouter版本
  • 升级AndroidX Activity依赖至implementation "androidx.activity:activity:1.6.0-alpha05"

路由配置代码

// GoRouter configuration
static final GoRouter router = GoRouter(
  initialLocation: initial,
  debugLogDiagnostics: true,
  navigatorKey: _rootNavigatorKey,
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => WelcomeScreen(),
      routes: [
        GoRoute(
          path: 'login',
          builder: (context, state) => LoginScreen(),
          routes: [
            GoRoute(
                path: 'register',
                name: 'Register',
                builder: (context, state) => RegisterScreen()),
            GoRoute(
                path: 'forgotPassword',
                name: 'forgotPassword',
                builder: (context, state) => ForgotPasswordScreen(),
                routes: [
                  GoRoute(
                      path: 'OTPVerification',
                      name: 'OTPVerification',
                      builder: (context, state) => OtpVerificationScreen(),
                      routes: [
                        GoRoute(
                          path: 'createNewPassword',
                          name: 'createNewPassword',
                          builder: (context, state) =>
                              CreateNewPasswordScreen(),
                          routes: [
                            GoRoute(
                                path: 'passwordChanged',
                                name: 'passwordChanged',
                                builder: (context, state) =>
                                    PasswordChangedScreen()),
                          ],
                        ),
                      ]),
                ]),
          ],
        ),
      ],
    ),

    /// MainWrapper
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        return CustomBottomBar(
          navigationShell: navigationShell,
        );
      },
      branches: <StatefulShellBranch>[
        /// Branch Home
        StatefulShellBranch(
          navigatorKey: _shellNavigatorHome,
          routes: <RouteBase>[
            GoRoute(
              path: "/home",
              name: "Home",
              builder: (BuildContext context, GoRouterState state) =>
                  HomePage(),
              routes: [
                GoRoute(
                  path: 'routesAndGamesList',
                  name: 'routesAndGamesList',
                  pageBuilder: (context, state) => CustomTransitionPage<void>(
                    key: state.pageKey,
                    child: RoutesAndGameList(),
                    transitionsBuilder:
                        (context, animation, secondaryAnimation, child) =>
                            FadeTransition(opacity: animation, child: child),
                  ),
                ),
                GoRoute(
                  path: 'routesList',
                  name: 'routesList',
                  pageBuilder: (context, state) => CustomTransitionPage<void>(
                    key: state.pageKey,
                    child: RoutesList(),
                    transitionsBuilder:
                        (context, animation, secondaryAnimation, child) =>
                            FadeTransition(opacity: animation, child: child),
                  ),
                ),
              ],
            ),
          ],
        ),
        StatefulShellBranch(
          navigatorKey: _shellNavigatorInfo,
          routes: <RouteBase>[
            GoRoute(
              path: "/info",
              name: "Info",
              builder: (BuildContext context, GoRouterState state) =>
                  InfoPage(),
            ),
          ],
        ),
        StatefulShellBranch(
          navigatorKey: _shellNavigatorProfile,
          routes: <RouteBase>[
            GoRoute(
              path: "/profile",
              name: "Profile",
              builder: (BuildContext context, GoRouterState state) =>
                  ProfilePage(),
              routes: <RouteBase>[
                GoRoute(
                  path: 'achievements',
                  name: 'Achievements',
                  pageBuilder: (context, state) => CustomTransitionPage<void>(
                    key: state.pageKey,
                    child: AchievementsScreen(),
                    transitionsBuilder:
                        (context, animation, secondaryAnimation, child) =>
                            FadeTransition(opacity: animation, child: child),
                  ),
                  routes: [
                    GoRoute(
                      path: 'trophy',
                      name: 'Trophy',
                      builder: (context, state) => TrophyScreen(),
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        StatefulShellBranch(
          navigatorKey: _shellNavigatorStories,
          routes: <RouteBase>[
            GoRoute(
              path: "/stories",
              name: "Stories",
              builder: (BuildContext context, GoRouterState state) =>
                  StoriesPage(),
            ),
          ],
        ),
      ],
    ),
  ],
);

解决方案

1. 给GoRouter添加onPopPage回调处理嵌套栈返回

问题核心是安卓返回键触发时,GoRouter没有优先处理嵌套分支内的页面栈,直接触发了根导航的弹出。添加onPopPage可以让嵌套分支的导航栈优先处理返回:

static final GoRouter router = GoRouter(
  initialLocation: initial,
  debugLogDiagnostics: true,
  navigatorKey: _rootNavigatorKey,
  // 新增onPopPage回调
  onPopPage: (route, result) {
    // 判断当前页面是否属于嵌套shell分支
    final isShellRoute = route.settings.name?.startsWith('/home') == true ||
        route.settings.name?.startsWith('/info') == true ||
        route.settings.name?.startsWith('/profile') == true ||
        route.settings.name?.startsWith('/stories') == true;

    if (isShellRoute) {
      // 获取对应分支的导航器
      final navigator = switch (route.settings.name) {
        String name when name.startsWith('/home') => _shellNavigatorHome.currentState,
        String name when name.startsWith('/info') => _shellNavigatorInfo.currentState,
        String name when name.startsWith('/profile') => _shellNavigatorProfile.currentState,
        String name when name.startsWith('/stories') => _shellNavigatorStories.currentState,
        _ => null,
      };

      // 如果分支导航器有可弹出的页面,优先弹出分支内页面
      if (navigator != null && navigator.canPop()) {
        navigator.pop();
        return false; // 阻止GoRouter处理根导航的pop
      }
    }
    // 非嵌套分支页面,正常处理返回
    return route.didPop(result);
  },
  routes: [
    // 原路由配置...
  ],
);

2. 替换AndroidX Activity依赖为稳定版

不要使用alpha测试版,改用最新稳定版依赖:

// 在app/build.gradle中替换
implementation "androidx.activity:activity:1.8.2"

3. 检查嵌套导航器Key初始化

确保所有StatefulShellBranch对应的导航器Key已正确初始化:

final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> _shellNavigatorHome = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> _shellNavigatorInfo = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> _shellNavigatorProfile = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> _shellNavigatorStories = GlobalKey<NavigatorState>();

原理说明

安卓的WindowOnBackDispatcher和GoRouter的嵌套导航栈未正确协同:点击返回键时,系统优先触发原生回调,跳过了GoRouter嵌套分支内的页面栈处理,直接触发根导航弹出,导致界面出现卡顿或交互失效。通过onPopPage回调可以强制GoRouter优先处理嵌套分支的返回逻辑,避免系统直接跳过嵌套栈。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 15:35:37