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

Flutter中GoRouter打开键盘时跳转至路由栈顶部问题排查

问题分析与解决方案

问题根源

  1. 重复创建GoRouter实例:每次Consumer重建(如键盘弹出触发Widget树重绘)时,都会调用router(authService)生成新的GoRouter实例,而新实例会使用initialLocation重置路由,导致跳转到首页/登录页。
  2. AuthService不必要的notifyListeners:初始化时异步调用_loadFromPrefs会触发notifyListeners,进而触发Consumer重建和GoRouter重新创建,引发路由跳转。

解决方案

1. 缓存GoRouter实例,避免重复创建

不要在Consumer的builder中每次都生成新的GoRouter,而是缓存实例,仅在认证状态变化时更新:

方法一:使用Memoizer(依赖provider包)
return Consumer<AuthService>(
  builder: (context, authService, child) {
    // 仅当认证状态变化时重新创建GoRouter
    final router = Memoizer().memoize(
      () => GoRouter.router(authService),
      [authService.isAuthenticated],
    );
    return MaterialApp.router(
      routerConfig: router,
    );
  },
);
方法二:用StatefulWidget保存实例

将MyApp改为StatefulWidget,在状态中缓存GoRouter并监听认证状态变化:

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late GoRouter _router;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final authService = Provider.of<AuthService>(context, listen: false);
    _router = GoRouter.router(authService);
    // 监听认证状态变化,更新路由
    authService.addListener(() {
      setState(() {
        _router = GoRouter.router(authService);
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(routerConfig: _router);
  }
}

2. 修复AuthService初始化逻辑

避免初始化时不必要的notifyListeners,确保仅当认证状态实际变化时才通知:

class AuthService extends ChangeNotifier {
  static final AuthService _instance = AuthService._internal();

  factory AuthService() => _instance;

  AuthService._internal() {
    _initAuthStatus();
  }

  bool _isAuthenticated = false;

  bool get isAuthenticated => _isAuthenticated;

  Future<void> _initAuthStatus() async {
    // 异步读取本地缓存的认证状态
    final savedStatus = await CorePrefs.isAuthenticatedAsync();
    if (_isAuthenticated != savedStatus) {
      _isAuthenticated = savedStatus;
      notifyListeners();
    }
  }

  Future<void> setAuthenticated(bool value) async {
    if (_isAuthenticated != value) {
      _isAuthenticated = value;
      await CorePrefs.isAuthenticated(value: value);
      notifyListeners();
    }
  }
}

如果CorePrefs只能同步读取,修改_loadFromPrefs:

void _loadFromPrefs() {
  final savedStatus = CorePrefs.isAuthenticated();
  if (_isAuthenticated != savedStatus) {
    _isAuthenticated = savedStatus;
    notifyListeners();
  }
}

3. 优化GoRouter的redirect逻辑

确保仅在必要时跳转,避免重复触发路由变更:

redirect: (context, state) {
  final loggedIn = authService.isAuthenticated;
  final currentPath = state.subloc;

  // 未登录且不在登录/注册/重置密码页面,跳转登录页
  if (!loggedIn) {
    final authPaths = [RouteNames.login, RouteNames.register, RouteNames.passwordReset];
    if (!authPaths.contains(currentPath)) return RouteNames.login;
  } 
  // 已登录但在登录页,跳转首页
  else if (currentPath == RouteNames.login) {
    return RouteNames.dashboard;
  }

  return null;
},

4. 统一AuthService实例获取方式

移除MyApp中手动创建的AuthService实例,统一通过Provider获取:

// 移除这行代码:final AuthService authService = AuthService();
// 在需要的地方通过Provider获取
final authService = Provider.of<AuthService>(context);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 13:03:21