查看: 143|回复: 3

Flutter鸿蒙SharedPreferences踩坑指南

[复制链接]
发表于 6 小时前 | 显示全部楼层 |阅读模式
在Flutter鸿蒙开发中,本地存储是绕不开的基础能力。之前遇到一个尴尬场景:用户选择了深色模式,关掉应用再打开又变回浅色,原因是主题状态只存在内存里,没做持久化。本文结合实践,详细梳理Flutter鸿蒙下使用shared_preferences包进行轻量存储的完整流程、常见踩坑点以及最佳实践。
  1. dependencies:
  2.   shared_preferences: ^2.3.3
复制代码
添加依赖后执行flutter pub get即可。该包由Flutter官方维护,鸿蒙社区也有适配版本(如shared_preferences_ohos),纯Dart接口,无需额外原生端配置。

SharedPreferences支持五种基础类型:int、double、bool、String、List<String>。读取时若key不存在返回null,通常用??赋默认值:
  1. final prefs = await SharedPreferences.getInstance();
  2. final count = prefs.getInt('count') ?? 0;
  3. final name = prefs.getString('name') ?? '匿名';
  4. final tags = prefs.getStringList('tags') ?? [];
复制代码
注意getInstance()是异步的,因其底层需读文件。不能在build方法中直接调用,应在initState里异步加载,用_loading状态控制UI:
  1. bool _loading = true;
  2. @override
  3. void initState() {
  4.   super.initState();
  5.   _loadAll();
  6. }
  7. Future<void> _loadAll() async {
  8.   final prefs = await SharedPreferences.getInstance();
  9.   setState(() {
  10.     _count = prefs.getInt('count') ?? 0;
  11.     _notes = prefs.getStringList('notes') ?? [];
  12.     _notifyEnabled = prefs.getBool('notify') ?? true;
  13.     _lang = prefs.getString('lang') ?? 'zh';
  14.     _fontScale = prefs.getDouble('font_scale') ?? 1.0;
  15.     _loading = false;
  16.   });
  17. }
复制代码

典型场景一:计数器持久化。写入时先更新内存状态再异步写文件:
  1. Future<void> _saveCount(int v) async {
  2.   setState(() => _count = v);
  3.   final prefs = await SharedPreferences.getInstance();
  4.   await prefs.setInt('count', v);
  5. }
复制代码
即使快速连续点击加减按钮,SharedPreferences内部有队列处理,后写覆盖先写,最终值正确。

典型场景二:记事本List<String>。用setStringList存列表,每次添加/删除都需重写整个列表,因为底层是将整个列表序列化为JSON字符串再存储,并非增量更新。数据量大时性能下降明显:
  1. Future<void> _addNote() async {
  2.   final text = _noteCtrl.text.trim();
  3.   if (text.isEmpty) return;
  4.   setState(() => _notes = [..._notes, text]);
  5.   _noteCtrl.clear();
  6.   final prefs = await SharedPreferences.getInstance();
  7.   await prefs.setStringList('notes', _notes);
  8. }
  9. Future<void> _removeNote(int index) async {
  10.   setState(() => _notes.removeAt(index));
  11.   final prefs = await SharedPreferences.getInstance();
  12.   await prefs.setStringList('notes', _notes);
  13. }
复制代码
实践中,几百条数据尚可,上千条建议改用SQLite。

典型场景三:用户偏好设置。开关、语言、字体缩放等每次改动立刻写入:
  1. Future<void> _setNotify(bool v) async {
  2.   setState(() => _notifyEnabled = v);
  3.   final prefs = await SharedPreferences.getInstance();
  4.   await prefs.setBool('notify', v);
  5. }
复制代码
可额外记录“上次保存”时间戳,让用户感知存储生效。

高级用法:JSON序列化存储对象。SharedPreferences不支持直接存复杂对象,需将对象序列化为字符串:
  1. class User {
  2.   final String name;
  3.   final int age;
  4.   final List<String> tags;
  5.   User({required this.name, required this.age, required this.tags});
  6.   factory User.fromJson(Map<String, dynamic> json) => User(
  7.     name: json['name'] as String,
  8.     age: json['age'] as int,
  9.     tags: (json['tags'] as List).cast<String>(),
  10.   );
  11.   Map<String, dynamic> toJson() => {'name': name, 'age': age, 'tags': tags};
  12. }
  13. // 存
  14. await prefs.setString('user', jsonEncode(user.toJson()));
  15. // 取
  16. final json = prefs.getString('user');
  17. if (json != null) {
  18.   final user = User.fromJson(jsonDecode(json));
  19. }
复制代码
清除数据用remove('key')或clear(),但clear()会清除应用所有SharedPreferences数据(包括其他模块),生产环境应避免直接调用clear(),改为只删除自己管理的key。

踩坑总结:
1. getInstance()是异步的,不能在build方法中直接调用,必须在initState里await并用_loading状态控制UI。
2. 写入操作(setInt等)返回Future<bool>,写入并非即时生效,若写入后立刻读取可能读到旧值,需要确保await完成。
3. setStringList是覆盖写而非增量更新,大数据量时性能差,建议几百条内使用,超限改用SQLite或Hive。
4. key命名冲突:SharedPreferences的key全局共享,应使用模块前缀,如"user_name"、"settings_theme",避免用"count"等通用名。
5. clear()太粗暴:会清空所有key,可能影响其他模块。删除单个key或自己管理好的key集合。
6. 平台兼容:鸿蒙上需安装对应的平台适配包(如shared_preferences_ohos),否则会抛MissingPluginException。建议在Demo中加入try/catch降级处理,平台不支持时给出提示,不崩掉应用。

与鸿蒙ArkTS存储方案对比:ArkTS的AppStorage是同步读取且支持存取对象(无需手动JSON序列化),还有@StorageProp装饰器能自动绑定数据与UI。Flutter的SharedPreferences异步设计不阻塞UI,但每个操作需await,略显啰嗦。两者各有优劣,按需选用。

最佳实践建议:
- 主题持久化:将themeMode和seedColor存进SharedPreferences,应用启动时恢复。
- 首次启动引导:用getBool('first_launch') ?? true判断,引导后设为false。
- 数据迁移:在SharedPreferences中存版本号,启动时根据版本执行迁移逻辑。
- 若数据量大,可考虑sqflite(关系型)或Hive(NoSQL),这两个在鸿蒙上的适配情况需另行验证。

SharedPreferences是Flutter鸿蒙开发中最简单的轻量存储方案,覆盖int、double、bool、String、List<String>五种类型及JSON序列化对象,适合存放用户偏好、小量计数器等场景。掌握上述踩坑点,能避免绝大多数线上问题。
回复

使用道具 举报

发表于 6 小时前 | 显示全部楼层

Re: Flutter鸿蒙SharedPreferences踩坑指南

看了楼主的分享,对于刚接触Flutter鸿蒙开发的人来说太实用了。特别是把主题状态丢内存这个坑,我也踩过,后来才意识到持久化的重要性。想问下楼主,鸿蒙版shared_preferences是不是直接替换包名就能用,还是需要在鸿蒙原生侧做额外配置?还有如果同时存多个键值对,用commit和await是否有性能差异?希望楼主有空能再补充下。
回复 支持 反对

使用道具 举报

发表于 6 小时前 | 显示全部楼层

Re: Flutter鸿蒙SharedPreferences踩坑指南

感谢鸿蒙专家的详细分享,非常实用!我之前也踩过 `getInstance()` 直接在 build 里调用的坑,看了你的 `_loading` 状态控制思路瞬间明白了。对于 `List` 存储的性能问题,我上次存了不到 200 条感觉还能接受,再大确实得换 SQLite。还想请教一下,在鸿蒙上使用 `shared_preferences_ohos` 这个适配包时,是否需要像一些原生插件那样配置鸿蒙的 `module.json` 之类的文件?还是直接 `flutter pub get` 就能用了?期待后续更多坑点合集!
回复 支持 反对

使用道具 举报

发表于 6 小时前 | 显示全部楼层

Re: Flutter鸿蒙SharedPreferences踩坑指南

楼主总结得很详细,尤其是异步加载和内存优先更新的思路非常实用。我补充一个点:对于频繁写入的场景(比如快速点击按钮),可以配合 `debounce` 或延迟写入,避免短期内多次重复写文件,能稍微减少I/O压力。另外,用JSON存储对象时记得处理异常和版本兼容,否则升级后可能出现反序列化失败。感谢分享!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-25 17:32 , Processed in 0.027935 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部