Flutter初始化SharedPreferences报LateInitializationError如何解决
错误根源
两处实现问题直接导致报错:
- 类成员
late SharedPreferences prefs声明后从未完成赋值,storeToken方法内获取的是同名局部prefs变量,和类级别的prefs字段无关,首次构建执行userLoggedIn()时直接访问未初始化的late字段,必然抛出LateInitializationError - 把prefs改成可空类型
SharedPreferences? prefs后,没有做非空判断就直接调用containsKey方法,自然触发空指针报错。
修复步骤
1. 应用启动时提前初始化SharedPreferences
SharedPreferences.getInstance()是异步方法,必须在runApp前完成全局初始化,避免组件构建时拿不到实例:
void main() async { // 初始化Flutter引擎绑定 WidgetsFlutterBinding.ensureInitialized(); // 全局获取SharedPreferences实例,后续全程复用 final SharedPreferences sharedPrefs = await SharedPreferences.getInstance(); runApp( ChangeNotifierProvider( create: (_) => AuthProvider(sharedPrefs), child: const MyApp(), ) ); }
2. 改造AuthProvider,通过构造函数注入已初始化的prefs实例
移除原来未赋值的late/可空prefs声明,通过构造函数传入初始化完成的实例,同时修正storeToken的实现,不要重复获取局部prefs变量:
class AuthProvider with ChangeNotifier{ bool _isLoading = false; bool get isLoading => _isLoading; User user = User(); // 构造函数注入prefs,声明为final确保不可变,不需要late/可空修饰 final SharedPreferences prefs; AuthProvider(this.prefs){ // 初始化时如果本地已存token,自动更新请求头 if(userLoggedIn()){ final savedToken = prefs.getString(ApiConstants.kEY_ACCESS_TOKEN)!; updateHeader(savedToken); token = savedToken; } } String token = ''; Map<String, String> _mainHeaders = { 'Content-Type': 'application/json; charset=UTF-8', }; void updateHeader(String token) { _mainHeaders = { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer $token', }; } Future<ResponseModel> login(String username, String password) async { print("Getting token"); _isLoading = true; notifyListeners(); http.Response response = await dologin(username, password); var answer = User.fromJson(jsonDecode(response.body)); late ResponseModel responseModel; if (response.statusCode == 200) { responseModel = ResponseModel(true, answer.token!); // 直接复用类成员prefs存储token,不需要重复调用getInstance await prefs.setString(ApiConstants.kEY_ACCESS_TOKEN, answer.token!); token = answer.token!; updateHeader(answer.token!); } else { responseModel = ResponseModel(false, response.reasonPhrase!); } _isLoading = false; notifyListeners(); return responseModel; } // 直接复用已初始化的prefs判断登录状态,无初始化/空指针风险 bool userLoggedIn() => prefs.containsKey(ApiConstants.kEY_ACCESS_TOKEN); // 补充登出方法,直接复用prefs清除本地凭证 Future<void> logout() async { await prefs.remove(ApiConstants.kEY_ACCESS_TOKEN); token = ''; _mainHeaders = { 'Content-Type': 'application/json; charset=UTF-8', }; notifyListeners(); } }
3. 保留原有路由判断逻辑即可
原来的Consumer逻辑不需要修改,此时AuthProvider初始化时已经持有可用的prefs实例,调用userLoggedIn()不会再触发初始化相关报错:
child: MaterialApp( home: Consumer<AuthProvider>( builder: ((context, auth, _){ return auth.userLoggedIn() ? const HomePage() : const loginScreen(); }) ), // 其余MaterialApp配置不变 )
注意:不要在业务方法里重复调用
SharedPreferences.getInstance()创建局部变量,既容易出现变量作用域导致的赋值遗漏问题,也会产生不必要的异步开销,全局初始化一次后全程复用实例即可。
内容的提问来源于stack exchange,提问作者Amir
相关产品推荐
相关产品推荐

