如何通过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
相关产品推荐
相关产品推荐

