Flutter中SharedPreferences杀进程后数据未持久化读取为null
问题核心原因
数据持久化失败、读取为null是几个低级逻辑错误叠加导致的:
- 调用异步方法
saveUserData时未加await,IO写入操作还没完成就继续执行后续逻辑,此时杀死进程会直接中断写入,数据根本没有落到本地存储 FutureBuilder绑定的future是未定义的loadData()方法,和你在initState里初始化的persistData完全无关,等于启动时根本没执行正确的读数据逻辑- 没有明确的登录状态标识,
getUser方法永远会返回带默认值的UserModel实例,导致路由判断逻辑完全失效 main函数中没有提前初始化Flutter引擎绑定,可能导致SharedPreferences插件初始化失败- token写入和用户信息写入拆分在两个地方,容易出现时序问题
完整修复方案
1. 修正入口初始化
首先在main函数中提前初始化插件绑定,避免插件初始化失败:
void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); }
2. 重构UserPreference工具类
统一读写逻辑,增加明确的登录状态标记,所有写入操作强制等待完成:
class UserPreference { static const String _isLoginKey = 'is_user_logged_in'; Future<bool> saveUserData(UserModel user) async { final prefs = await SharedPreferences.getInstance(); // 批量等待所有写入操作完成,避免写入中断 await Future.wait([ prefs.setString('email', user.email), prefs.setString('first_name', user.first_name ?? ''), prefs.setString('last_name', user.last_name ?? ''), prefs.setString('profile_picture', user.profile_picture ?? ''), prefs.setString('token', user.token ?? ''), prefs.setBool(_isLoginKey, true), ]); return true; } Future<bool> isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool(_isLoginKey) ?? false; } Future<UserModel> getSavedUser() async { final prefs = await SharedPreferences.getInstance(); return UserModel( email: prefs.getString('email') ?? "default@gmail.com", first_name: prefs.getString('first_name') ?? "user", last_name: prefs.getString('last_name') ?? "default", profile_picture: prefs.getString('profile_picture') ?? "", token: prefs.getString('token') ?? "", ); } Future<bool> clearUserData() async { final prefs = await SharedPreferences.getInstance(); await prefs.clear(); return true; } }
3. 修正登录成功后的保存逻辑
调用保存方法必须加await,移除重复的SharedPreferences实例初始化:
if (response.statusCode == 200) { final data = jsonDecode(response.body)['data']; final UserModel user = UserModel.fromJson(data['user']['user_customer_detail']); user.token = 'Bearer ${data['access_token']}'; // 必须等待写入完成再返回 await UserPreference().saveUserData(user); return user; }
4. 修正启动页的FutureBuilder逻辑
绑定正确的读取future,修正路由判断规则:
class _MyAppState extends State<MyApp> { late Future<Map<String, dynamic>> _initDataFuture; @override void initState() { super.initState(); _initDataFuture = _loadInitData(); } Future<Map<String, dynamic>> _loadInitData() async { final isLogin = await UserPreference().isLoggedIn(); UserModel? user; if (isLogin) { user = await UserPreference().getSavedUser(); } return {'isLogin': isLogin, 'user': user}; } @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => AuthProvider()), ], child: MaterialApp( title: 'Tailorine', routes: { '/main': (context) => MainScreen(), '/intro': (context) => IntroScreen(), '/sign-in': (context) => SignInPage(), '/sign-up': (context) => SignUpPage(), }, home: FutureBuilder( future: _initDataFuture, builder: (c, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Scaffold( body: Center( child: CircularProgressIndicator(color: primaryColor), ), ); } if (snapshot.hasError) { return Center(child: Text(snapshot.error.toString())); } final res = snapshot.data as Map<String, dynamic>; if (res['isLogin'] == true) { // 已登录场景下可以把user数据同步到AuthProvider再进入主页 // Provider.of<AuthProvider>(context, listen: false).updateUser(res['user']); return const MainScreen(); } // 未登录进入引导页 return const IntroScreen(); }, ), ), ); } }
注意事项
- 所有
SharedPreferences的写入方法都是异步IO操作,调用时必须加await,否则进程被杀时写入队列会被直接清空 - 不要在
FutureBuilder的future参数里直接调用异步方法,否则每次组件重建都会重复执行读逻辑,要在initState里初始化后复用 - 测试持久化效果时必须冷启动应用(完全停止进程后重新打开),热重载、热重启都不会触发本地存储的完整读取流程
- 建议将
shared_preferences插件升级到最新稳定版,避免旧版本的平台兼容问题
内容的提问来源于stack exchange,提问作者Muhammad Yafi A
相关产品推荐
相关产品推荐

