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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 05:21:24