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
相关产品推荐
相关产品推荐

