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

调用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的实现:

  1. 不直接在GoRouter创建函数里watch整个authController,而是通过ref.read获取控制器,在redirect中动态监听状态变化。
  2. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 06:18:13