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

如何通过Provider在GoRouter路由前正确初始化延迟变量?

问题:GoRouter路由跳转前初始化late变量,避免LateInitializationError

我需要在进入GoRouter路由前初始化authenticated变量,当前代码运行时会先抛出LateInitializationError,之后才会跳转到登录页。原因是checkAndNotify是异步方法,路由跳转时变量还未完成初始化。我不想移除late修饰符,想知道如何正确使用它,确保authenticated不会处于未初始化状态。

现有代码片段

AuthProvider代码

class AuthProvider with ChangeNotifier {
  late bool? authenticated;
  static late User? user;

  AuthProvider() {
    checkAndNotify();
  }

  Future<bool?> checkAndNotify() async {
    try {
      String? token = ((await AppStorage.appBox.get("token")) as String?);
      authenticated = token!=null;
      notifyListeners();
      return authenticated;
    } catch (err) {
      logError(
        message: "Seems like the user is not authenticated",
        error: err.toString(),
        method: "check",
      );
      return null;
    }
  }
}

routes.dart路由配置

final routes = GoRouter(
  initialLocation: "/",
  errorBuilder: (context,state) => const ErrorScreen(),
  routes: [
    GoRoute(
        path: "/",
        redirect: (context, state) {
          if (context.watch<AuthProvider>().authenticated != true) {
            return "/auth/login";
          } else {
            return "/home";
          }
        },
        routes: [
          GoRoute(
            path: "auth",
            redirect: (context, state) {
              return "/auth/login";
            },
            routes: [
              GoRoute(
                path: "login",
                builder: (context, state) => const LoginScreen(),
              ),
              GoRoute(
                path: "register",
                builder: (context, state) => const RegistrationScreen(),
              ),
            ],
          ),
          ShellRoute(
            restorationScopeId: "route.shell",
            routes: [
              GoRoute(
                path: "home",
                name: "home",
                builder: (context, state) => const HomeScreen(),
              ),
              GoRoute(
                path: "orders",
                name: "orders",
                builder: (context, state) => const OrdersScreen(),
              ),
              GoRoute(
                path: "profile",
                name: "profile",
                builder: (context, state) => const ProfileScreen(),
              ),
            ],
            builder: (context, state, child) {
              return Shell(child: child);
            },
          ),
        ]),
  ],
);

main.dart启动代码

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load();
  await AppStorage.init();
  await LanguageProvider.init();
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => LanguageProvider()),
        ChangeNotifierProvider(create: (_) => AuthProvider()),
      ],
      child: const Eshop(),
    ),
  );
}

class Eshop extends StatelessWidget {
  const Eshop({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerConfig: routes,
      theme: AppTheme.light,
    );
  }
}

解决方案

1. 给AuthProvider增加初始化状态标记

因为checkAndNotify是异步方法,必须跟踪初始化是否完成,避免直接访问未初始化的late变量。修改后的AuthProvider:

class AuthProvider with ChangeNotifier {
  late bool? authenticated;
  static late User? user;
  // 新增:标记认证状态是否完成初始化
  bool _isInitialized = false;

  AuthProvider() {
    _initAuthState();
  }

  // 封装异步初始化逻辑
  Future<void> _initAuthState() async {
    await checkAndNotify();
    _isInitialized = true;
    notifyListeners();
  }

  Future<bool?> checkAndNotify() async {
    try {
      String? token = ((await AppStorage.appBox.get("token")) as String?);
      authenticated = token != null;
      notifyListeners();
      return authenticated;
    } catch (err) {
      logError(
        message: "Seems like the user is not authenticated",
        error: err.toString(),
        method: "check",
      );
      // 异常时明确设置authenticated为false,避免变量未初始化
      authenticated = false;
      notifyListeners();
      return authenticated;
    }
  }

  // 提供安全访问入口,避免外部直接访问未初始化的变量
  bool? get safeAuthenticated => _isInitialized ? authenticated : null;
}

2. 修改GoRouter的redirect逻辑,等待初始化完成

在路由跳转前先判断初始化状态,未完成时不执行跳转,等待状态更新:

final routes = GoRouter(
  initialLocation: "/",
  errorBuilder: (context, state) => const ErrorScreen(),
  routes: [
    GoRoute(
      path: "/",
      redirect: (context, state) {
        final authProvider = context.watch<AuthProvider>();
        
        // 初始化未完成时返回null,不跳转,等待状态更新
        if (!authProvider._isInitialized) return null;
        
        // 初始化完成后再判断跳转方向
        if (authProvider.authenticated != true) {
          return "/auth/login";
        } else {
          return "/home";
        }
      },
      // 其余路由代码保持不变
    ),
  ],
);

3. 优化main.dart的初始化顺序,提前完成认证检查

在启动App前,提前初始化AuthProvider并等待异步检查完成,确保变量在路由启动前已经初始化:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load();
  await AppStorage.init();
  await LanguageProvider.init();
  
  // 提前初始化AuthProvider并等待认证状态检查完成
  final authProvider = AuthProvider();
  await authProvider.checkAndNotify();

  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => LanguageProvider()),
        ChangeNotifierProvider.value(value: authProvider),
      ],
      child: const Eshop(),
    ),
  );
}

核心思路

  • 通过_isInitialized标记跟踪异步初始化进度,杜绝直接访问未初始化的late变量。
  • 在异常分支中明确设置authenticated的值,确保变量始终处于已初始化状态。
  • 提前在main函数中完成认证检查,让App启动时变量已经就绪,从根源避免LateInitializationError。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:32:05