在Flutter鸿蒙开发中,本地存储是绕不开的基础能力。之前遇到一个尴尬场景:用户选择了深色模式,关掉应用再打开又变回浅色,原因是主题状态只存在内存里,没做持久化。本文结合实践,详细梳理Flutter鸿蒙下使用shared_preferences包进行轻量存储的完整流程、常见踩坑点以及最佳实践。
- dependencies:
- shared_preferences: ^2.3.3
复制代码 添加依赖后执行flutter pub get即可。该包由Flutter官方维护,鸿蒙社区也有适配版本(如shared_preferences_ohos),纯Dart接口,无需额外原生端配置。
SharedPreferences支持五种基础类型:int、double、bool、String、List<String>。读取时若key不存在返回null,通常用??赋默认值:- final prefs = await SharedPreferences.getInstance();
- final count = prefs.getInt('count') ?? 0;
- final name = prefs.getString('name') ?? '匿名';
- final tags = prefs.getStringList('tags') ?? [];
复制代码 注意getInstance()是异步的,因其底层需读文件。不能在build方法中直接调用,应在initState里异步加载,用_loading状态控制UI:- bool _loading = true;
- @override
- void initState() {
- super.initState();
- _loadAll();
- }
- Future<void> _loadAll() async {
- final prefs = await SharedPreferences.getInstance();
- setState(() {
- _count = prefs.getInt('count') ?? 0;
- _notes = prefs.getStringList('notes') ?? [];
- _notifyEnabled = prefs.getBool('notify') ?? true;
- _lang = prefs.getString('lang') ?? 'zh';
- _fontScale = prefs.getDouble('font_scale') ?? 1.0;
- _loading = false;
- });
- }
复制代码
典型场景一:计数器持久化。写入时先更新内存状态再异步写文件:- Future<void> _saveCount(int v) async {
- setState(() => _count = v);
- final prefs = await SharedPreferences.getInstance();
- await prefs.setInt('count', v);
- }
复制代码 即使快速连续点击加减按钮,SharedPreferences内部有队列处理,后写覆盖先写,最终值正确。
典型场景二:记事本List<String>。用setStringList存列表,每次添加/删除都需重写整个列表,因为底层是将整个列表序列化为JSON字符串再存储,并非增量更新。数据量大时性能下降明显:- Future<void> _addNote() async {
- final text = _noteCtrl.text.trim();
- if (text.isEmpty) return;
- setState(() => _notes = [..._notes, text]);
- _noteCtrl.clear();
- final prefs = await SharedPreferences.getInstance();
- await prefs.setStringList('notes', _notes);
- }
- Future<void> _removeNote(int index) async {
- setState(() => _notes.removeAt(index));
- final prefs = await SharedPreferences.getInstance();
- await prefs.setStringList('notes', _notes);
- }
复制代码 实践中,几百条数据尚可,上千条建议改用SQLite。
典型场景三:用户偏好设置。开关、语言、字体缩放等每次改动立刻写入:- Future<void> _setNotify(bool v) async {
- setState(() => _notifyEnabled = v);
- final prefs = await SharedPreferences.getInstance();
- await prefs.setBool('notify', v);
- }
复制代码 可额外记录“上次保存”时间戳,让用户感知存储生效。
高级用法:JSON序列化存储对象。SharedPreferences不支持直接存复杂对象,需将对象序列化为字符串:- class User {
- final String name;
- final int age;
- final List<String> tags;
- User({required this.name, required this.age, required this.tags});
- factory User.fromJson(Map<String, dynamic> json) => User(
- name: json['name'] as String,
- age: json['age'] as int,
- tags: (json['tags'] as List).cast<String>(),
- );
- Map<String, dynamic> toJson() => {'name': name, 'age': age, 'tags': tags};
- }
- // 存
- await prefs.setString('user', jsonEncode(user.toJson()));
- // 取
- final json = prefs.getString('user');
- if (json != null) {
- final user = User.fromJson(jsonDecode(json));
- }
复制代码 清除数据用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序列化对象,适合存放用户偏好、小量计数器等场景。掌握上述踩坑点,能避免绝大多数线上问题。 |