Flutter中GoRouter+SharedPreferences认证流程context.goNamed失效问题
问题描述
我尝试用GoRouter和SharedPreferences实现Flutter认证流程:用户未登录时自动跳转至LoginScreen,已登录则跳转至MainLayout。但目前SplashScreen中的context.goNamed方法完全无法生效,控制台也没有输出任何报错信息。
SplashScreen核心逻辑代码
@override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { checkLoginState(); }); } void checkLoginState() { print("in"); if (locator.get<AppPreferences>().isLoggedIn()) { print("in questMainLayoutRoute"); context.goNamed(AppRouteName.questMainLayoutRoute); } else { print("in loginScreen"); context.goNamed(AppRouteName.loginScreen); } } @override void dispose() { super.dispose(); }
路由配置代码
abstract class AppRoute { static GoRouter routes = GoRouter( redirect: (context, state) => AppRouteName.splashScreen, routes: [ GoRoute( name: AppRouteName.splashScreen, path: AppRouteName.splashScreen, builder: (context, state) => SplashScreen(), ), GoRoute( name: AppRouteName.loginScreen, path: AppRouteName.loginScreen, builder: (context, state) => LoginUI(), ), GoRoute( name: AppRouteName.questMainLayoutRoute, path: AppRouteName.questMainLayoutRoute, builder: (context, state) => QuestMainLayout(), ), ] ); }
问题根源
你的GoRouter全局redirect配置强制所有路由请求都跳转到SplashScreen,导致SplashScreen内的跳转刚触发,就被全局redirect拉回启动页,所以视觉上完全没变化,也不会报错。
修复方案
方案1:修正全局redirect逻辑
只在用户未处于SplashScreen时才跳转启动页,避免循环拦截:
abstract class AppRoute { static GoRouter routes = GoRouter( redirect: (context, state) { // 检查当前是否已经在启动页 final isOnSplash = state.location == AppRouteName.splashScreen; // 只有不在启动页时,才跳转过去做登录检查 return isOnSplash ? null : AppRouteName.splashScreen; }, routes: [ // 原有路由配置保持不变 GoRoute( name: AppRouteName.splashScreen, path: AppRouteName.splashScreen, builder: (context, state) => SplashScreen(), ), GoRoute( name: AppRouteName.loginScreen, path: AppRouteName.loginScreen, builder: (context, state) => LoginUI(), ), GoRoute( name: AppRouteName.questMainLayoutRoute, path: AppRouteName.questMainLayoutRoute, builder: (context, state) => QuestMainLayout(), ), ], ); }
方案2:优化登录状态检查(避免同步读取SharedPreferences的坑)
SharedPreferences的读取本质是异步操作,同步读取可能拿到旧值或空值,建议修改为异步实现:
- 更新
AppPreferences的isLoggedIn方法:
Future<bool> isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool('is_logged_in') ?? false; }
- 对应修改SplashScreen的
checkLoginState:
void checkLoginState() async { print("in"); final isLoggedIn = await locator.get<AppPreferences>().isLoggedIn(); // 确保页面未销毁时再调用context if (!mounted) return; if (isLoggedIn) { print("in questMainLayoutRoute"); context.goNamed(AppRouteName.questMainLayoutRoute); } else { print("in loginScreen"); context.goNamed(AppRouteName.loginScreen); } }
方案3:直接在全局redirect中处理登录逻辑(更简洁)
如果不需要SplashScreen做过渡,可以把登录检查直接放到全局redirect里,省去启动页的跳转逻辑:
abstract class AppRoute { static GoRouter routes = GoRouter( redirect: (context, state) async { final isLoggedIn = await locator.get<AppPreferences>().isLoggedIn(); final currentPath = state.location; // 未登录且不在登录页 → 跳登录 if (!isLoggedIn && currentPath != AppRouteName.loginScreen) { return AppRouteName.loginScreen; } // 已登录且不在主页面 → 跳主页面 if (isLoggedIn && currentPath != AppRouteName.questMainLayoutRoute) { return AppRouteName.questMainLayoutRoute; } // 其他情况不做跳转 return null; }, routes: [ GoRoute( name: AppRouteName.loginScreen, path: AppRouteName.loginScreen, builder: (context, state) => LoginUI(), ), GoRoute( name: AppRouteName.questMainLayoutRoute, path: AppRouteName.questMainLayoutRoute, builder: (context, state) => QuestMainLayout(), ), ], ); }
内容的提问来源于stack exchange,提问作者soufiane ben yaala
相关产品推荐
相关产品推荐

