调用login方法后GoRouter重复初始化问题求助
问题:调用login方法时GoRouter重新初始化并返回启动页的原因
当调用login方法时,GoRouter会重新初始化并返回至启动页。已知调用login时authController的loginStatus会变为loading,触发GoRouter的redirect逻辑,但无法理解为何GoRouter会再次初始化。
代码
Router实现
// router final routeProvider = Provider<GoRouter>((ref) { final authController = ref.watch(authControllerProvider); return GoRouter( debugLogDiagnostics: true, initialLocation: '/${AppRoute.splash.name}', routes: [ GoRoute( path: '/${AppRoute.splash.name}', name: AppRoute.splash.name, builder: (context, state) => const SplashScreen(), ), GoRoute( path: '/${AppRoute.login.name}', name: AppRoute.login.name, builder: (context, state) => const LoginScreen(), ), GoRoute( path: '/${AppRoute.nickname.name}', name: AppRoute.nickname.name, builder: (context, state) => const NicknameScreen(), ), GoRoute( path: '/${AppRoute.home.name}', name: AppRoute.home.name, builder: (context, state) => const HomeScreen(), ), ], redirect: (context, state) { final isLoggedIn = authController.isLoggedIn; final isNewUser = authController.isNewUser; final currentLocation = state.matchedLocation; if (isLoggedIn && isNewUser) { if (currentLocation.contains(AppRoute.login.name)) { return '/${AppRoute.nickname.name}'; } } else if (isLoggedIn) { if (currentLocation.contains(AppRoute.login.name)) { return '/${AppRoute.home.name}'; } } return null; }, ); });
AuthController实现
// authController final authControllerProvider = StateNotifierProvider<AuthController, AuthState>( (ref) => AuthController(userAPI: ref.watch(userAPIProvider))); class AuthController extends StateNotifier<AuthState> { AuthController({required this.userAPI}) : super(AuthState.initial()); final UserAPI userAPI; void login({ required SocialLoginType providerType, required String providerToken, required DeviceType deviceType, required String deviceUid, }) async { state = state.copyWith(loginStatus: LoginNetworkStatus.loading); try { final response = await userAPI.login( providerType: providerType, providerToken: providerToken, ); state = state.copyWith( loginStatus: LoginNetworkStatus.success, isLoggedIn: true, isNewUser: response.isSignedUp, ); } catch (_) { state = state.copyWith(loginStatus: LoginNetworkStatus.error); return; } } } enum LoginNetworkStatus { initial, loading, success, error } class AuthState { AuthState({ required this.loginStatus, required this.isLoggedIn, required this.isNewUser, }); final LoginNetworkStatus loginStatus; final bool isLoggedIn; final bool isNewUser; factory AuthState.initial() { return AuthState( loginStatus: LoginNetworkStatus.initial, isLoggedIn: false, isNewUser: false, ); } AuthState copyWith({ LoginNetworkStatus? loginStatus, bool? isLoggedIn, bool? isNewUser, }) { return AuthState( loginStatus: loginStatus ?? this.loginStatus, isLoggedIn: isLoggedIn ?? this.isLoggedIn, isNewUser: isNewUser ?? this.isNewUser, ); } }
运行日志
I/flutter ( 9257): 🐶 Google Login Succeed! W/e2why.twogather( 9257): Cleared Reference was only reachable from finalizer (only reported once) [GoRouter] Full paths for routes: => /splash => /login => /nickname => /home known full paths for route names: splash => /splash login => /login nickname => /nickname home => /home [GoRouter] setting initial location /splash I/flutter ( 9257): 🐶 REDIRECT I/flutter ( 9257): isLoggedIn: false I/flutter ( 9257): isNewUser: false I/flutter ( 9257): currentLocation: /splash [GoRouter] Using MaterialApp configuration D/EGL_emulation( 9257): app_time_stats: avg=1142.15ms min=1142.15ms max=1142.15ms count=1 I/flutter ( 9257): 🥚 REQUEST [POST] => http://10.0.2.2:8000/api/user/login
原因分析与解决方案
核心原因
你在routeProvider中使用了ref.watch(authControllerProvider),当AuthState发生变化(比如调用login时设置loading状态),Provider会重新执行routeProvider的创建函数,生成一个全新的GoRouter实例。新实例初始化时会使用你设置的initialLocation: '/splash',所以会跳转到启动页。
简单来说:AuthState变化 → routeProvider重建 → 新GoRouter实例以splash为初始页启动 → 页面回到启动页。
验证依据
从日志里能看到,在Google登录成功后,GoRouter重新打印了路由列表、设置初始位置为/splash,这完全是GoRouter实例重新初始化的特征,和应用启动时的日志一致。
解决方案
要避免GoRouter被重建,需要修改routeProvider的实现:
- 不直接在GoRouter创建函数里
watch整个authController,而是通过ref.read获取控制器,在redirect中动态监听状态变化。 - 使用
GoRouter的refreshListenable参数,传入authController的listenable,这样状态变化只会触发redirect,不会重建GoRouter。
修改后的Router代码示例
final routeProvider = Provider<GoRouter>((ref) { final authController = ref.read(authControllerProvider.notifier); final authState = ref.watch(authControllerProvider); return GoRouter( debugLogDiagnostics: true, initialLocation: '/${AppRoute.splash.name}', // 添加refreshListenable,让GoRouter监听AuthState变化但不重建 refreshListenable: GoRouterRefreshStream(authController.stream), routes: [ // 路由列表保持不变 GoRoute( path: '/${AppRoute.splash.name}', name: AppRoute.splash.name, builder: (context, state) => const SplashScreen(), ), GoRoute( path: '/${AppRoute.login.name}', name: AppRoute.login.name, builder: (context, state) => const LoginScreen(), ), GoRoute( path: '/${AppRoute.nickname.name}', name: AppRoute.nickname.name, builder: (context, state) => const NicknameScreen(), ), GoRoute( path: '/${AppRoute.home.name}', name: AppRoute.home.name, builder: (context, state) => const HomeScreen(), ), ], redirect: (context, state) { // 这里直接使用当前最新的authState final isLoggedIn = authState.isLoggedIn; final isNewUser = authState.isNewUser; final currentLocation = state.matchedLocation; if (isLoggedIn && isNewUser) { if (currentLocation.contains(AppRoute.login.name)) { return '/${AppRoute.nickname.name}'; } } else if (isLoggedIn) { if (currentLocation.contains(AppRoute.login.name)) { return '/${AppRoute.home.name}'; } } return null; }, ); }); // 辅助类:将StateNotifier的stream转为GoRouter的Listenable class GoRouterRefreshStream extends ChangeNotifier { GoRouterRefreshStream(Stream<dynamic> stream) { _subscription = stream.listen((_) => notifyListeners()); } late final StreamSubscription<dynamic> _subscription; @override void dispose() { _subscription.cancel(); super.dispose(); } }
这样修改后,AuthState变化只会触发GoRouter的redirect逻辑,不会重新创建GoRouter实例,也就不会回到启动页了。
内容的提问来源于stack exchange,提问作者Nala
相关产品推荐
相关产品推荐

