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

Flutter go_router重定向异常 重启应用后数据拉取失败

问题根因

故障由4个时序和逻辑问题共同导致:

  • 路由重定向逻辑缺失初始化态判断:仅判断loggedIn布尔值,应用重启时本地登录态恢复、用户数据拉取未完成就直接跳转根路由,此时鉴权参数未就绪,服务端请求直接失败。
  • 路由刷新监听不全:AppRouter仅监听LoginState变化,未监听Auth实例的本地状态恢复流程,重启后Auth读取SharedPreferences的过程不会触发路由重算,拿到的登录态是初始默认值,和实际存储状态不一致。
  • 初始化逻辑放错位置:将依赖上下文的用户数据拉取逻辑放在TabsScreen的initState中,go_router默认对路由页面做缓存,重启后路由重建时initState可能不执行,或执行时上下文未完成挂载,导致Provider读取、路由跳转调用异常。
  • 路由表存在语法错误:splashScreen路由定义末尾缺失逗号,会导致路由注册流程异常。
修复方案

1. 修复路由配置,补充初始化态判断

修改router.dart,补全语法缺失项,调整重定向逻辑,同时监听Auth和LoginState的状态变化:

class AppRouter with ChangeNotifier {
  final LoginState authState;
  final Auth authProvider;
  AppRouter(this.authState, this.authProvider) {
    // 同时监听两个状态的变化,任意状态更新都触发路由刷新
    authState.addListener(notifyListeners);
    authProvider.addListener(notifyListeners);
  }

  late final router = GoRouter(
    redirect: (state) {
      final loginLoc = state.namedLocation(authScreen);
      final loggingIn = state.subloc == loginLoc;
      final splashLoc = state.namedLocation(splashScreen);
      final onSplash = state.subloc == splashLoc;
      final rootLoc = state.namedLocation(root);

      // 未完成初始化时统一跳转闪屏页,避免提前跳转首页/登录页
      if (!authProvider.initialized && !onSplash) {
        return splashLoc;
      }

      final loggedIn = authState.loggedIn;
      if (!loggedIn && !loggingIn) return loginLoc;
      if (loggedIn && loggingIn) return rootLoc;
      return null;
    },
    refreshListenable: this,
    routes: [
      GoRoute(
        name: root,
        path: '/',
        builder: (context, state) => TabsScreen(),
      ),
      GoRoute(
        name: mainScreen,
        path: '/main-screen',
        builder: (context, state) => HomeScreen(),
      ),
      GoRoute(
        name: splashScreen,
        path: '/splash-screen',
        builder: (context, state) => SplashScreen(),
      ), // 补全缺失的逗号
      GoRoute(
        name: authScreen,
        path: '/auth-screen',
        builder: (context, state) => AuthScreen(),
      ),
      GoRoute(
        name: orderSreen,
        path: '/order-screen',
        builder: (context, state) => OrderScreen(),
      ),
      // 补全代码中引用的无网络页面路由
      GoRoute(
        name: noNetwork,
        path: '/no-network',
        builder: (context, state) => NoNetworkScreen(),
      ),
    ],
    errorBuilder: (context, state) => Scaffold(
      body: Center(
        child: Text(state.error.toString()),
      ),
    ),
  );

  @override
  void dispose() {
    authState.removeListener(notifyListeners);
    authProvider.removeListener(notifyListeners);
    super.dispose();
  }
}

2. 调整main.dart的初始化时序

在initState中完成核心实例初始化,避免build阶段重复创建路由和Provider实例:

class _MyAppState extends State<MyApp> {
  late LoginState loginState;
  late Auth auth;
  late AppRouter appRouter;

  @override
  void initState() {
    super.initState();
    loginState = LoginState(widget.sharedPreferences);
    auth = Auth(widget.sharedPreferences);
    // 启动时先恢复本地持久化的鉴权信息
    auth.restorePersistedAuth();
    appRouter = AppRouter(loginState, auth);
  }

  @override
  void dispose() {
    appRouter.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider.value(value: auth),
          ChangeNotifierProvider.value(value: loginState),
          ChangeNotifierProvider.value(value: appRouter),
          ListenableProxyProvider<Auth, Orders>(
            update: (_, authObj, prevOrders) =>
                Orders(authObj.usrName, authObj.objId),
          ),
          ListenableProxyProvider<Auth, FavMeals>(
            update: (_, authObj, prevFav) =>
                FavMeals(authObj.usrName, authObj.objId),
          ),
          // 补全代码中用到的用户、用餐数据Provider
          ChangeNotifierProvider(create: (ctx) => Users()),
          ChangeNotifierProvider(create: (ctx) => EatenMeals()),
        ],
        child: Sizer(builder: (context, orientation, deviceType) {
          return MaterialApp.router(
            // 补全路由三个必须参数,避免新版本状态同步异常
            routeInformationProvider: appRouter.router.routeInformationProvider,
            routeInformationParser: appRouter.router.routeInformationParser,
            routerDelegate: appRouter.router.routerDelegate,
            scaffoldMessengerKey: scaffoldMessengerKey,
            theme: ThemeData(primarySwatch: myMaterialColor),
            localizationsDelegates: [
              GlobalCupertinoLocalizations.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
            ],
          );
        }));
  }
}

3. 给Auth类补充初始化状态标记

在Auth类中增加初始化状态字段,控制路由跳转时机:

class Auth extends ChangeNotifier {
  final SharedPreferences prefs;
  Auth(this.prefs);

  bool _initialized = false;
  bool get initialized => _initialized;

  String? usrName;
  String? objId;
  // 其他原有业务字段保留

  // 读取本地持久化存储的鉴权信息
  Future<void> restorePersistedAuth() async {
    usrName = prefs.getString('usrName');
    objId = prefs.getString('objId');
    // 其他本地字段读取逻辑保留
    notifyListeners();
  }

  // 闪屏页数据拉取完成后标记初始化完成
  void markInitialized() {
    _initialized = true;
    notifyListeners();
  }

  // 原有登录、登出业务逻辑保留
}

4. 迁移初始化逻辑到闪屏页

把原本放在TabsScreen的用户数据拉取逻辑移到闪屏页,避免页面生命周期时序问题:

class SplashScreen extends StatefulWidget {
  const SplashScreen({Key? key}) : super(key: key);

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

class _SplashScreenState extends State<SplashScreen> {
  @override
  void initState() {
    super.initState();
    _initAppData();
  }

  Future<void> _initAppData() async {
    try {
      final userProvider = Provider.of<Users>(context, listen: false);
      await userProvider.userRetrieving();
      await Provider.of<EatenMeals>(context, listen: false).serverList();

      final user = userProvider.initialUser;
      dailyLimitAlert(user);
      if (user.isPremium) {
        final sub = await userProvider.subRemainDays();
        if (sub < 1) {
          await userProvider.changePremiumStatus();
        }
      }
      if (mounted) {
        Provider.of<Auth>(context, listen: false).markInitialized();
      }
    } catch (error) {
      if (mounted) {
        GoRouter.of(context).goNamed(noNetwork);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: CircularProgressIndicator()),
    );
  }
}

5. 精简TabsScreen初始化逻辑

移除TabsScreen的initState中initUserStatus相关调用,仅保留页面本身的分页控制器、选中下标初始化逻辑即可。如果需要页面每次可见时刷新数据,使用RouteAware mixin或go_router的路由监听实现,不要依赖initState生命周期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 16:48:18