使用已经完成鸿蒙化适配的Flutter本地持久化存储三方库shared_preferences让你的应用能够保存用户偏好设置、缓存数据等

欢迎加入开源鸿蒙跨平台社区

你好!👋 欢迎来到这篇关于 shared_preferences 在 HarmonyOS (OpenHarmony) 上使用的实战教程。当前已经完成鸿蒙化适配的Flutter三方库均可在flutter_packages仓库下查看。

本文将手把手带你实现本地数据持久化存储,让你的应用能够保存用户偏好设置、缓存数据等!📦


📦 1. 引入依赖:pubspec.yaml

首先,我们需要在项目的配置文件中引入适配后的 shared_preferences 库。

修改文件pubspec.yaml

操作 :在 dependencies 节点下添加 shared_preferences 的 git 依赖配置。

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  
  # 👇 新增:引入 shared_preferences 鸿蒙适配版本
  shared_preferences:
    git: 
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/shared_preferences/shared_preferences
      ref: br_shared_preferences-v2.5.3_ohos

💡 提示 :修改完成后,别忘了运行终端命令 flutter pub get 来下载依赖!

🎯 为什么需要 shared_preferences?

shared_preferences 是 Flutter 官方提供的一个插件,用于简单数据的本地持久化存储

想象一下,你的应用需要:

  • 🔐 记住用户的登录状态
  • 🌙 保存用户的主题偏好(深色/浅色模式)
  • 🌍 存储用户选择的语言设置
  • 📊 缓存一些简单的配置数据
  • 🎯 保存用户的使用习惯

传统方式需要为每个平台编写不同的存储代码。而 shared_preferences 就像一个 🗄️ 跨平台小型数据库,一个方法搞定所有平台!

核心优势:

  • 跨平台统一:一套代码适配多个平台
  • 简单易用:键值对存储,API 简洁
  • 类型安全:支持多种数据类型
  • 异步操作:不阻塞主线程
  • 持久化存储:应用重启后数据仍然保留

👶 新手小课堂:shared_preferences 是什么?

shared_preferences 可以理解为一个 🗃️ 简单的键值对存储系统

就像一个 📒 小笔记本

  • 你可以在上面写下 "用户名 = 张三"
  • 下次打开笔记本,"张三" 还在那里
  • 你也可以修改或删除这些记录

它与其他存储方式的区别:

存储方式 适用场景 数据量 复杂度
💾 shared_preferences 简单配置、偏好设置
📁 文件存储 大文件、日志
🗄️ 数据库 (SQLite) 结构化数据、关系查询

支持的数据类型:

  • 📝 String:字符串(如用户名、配置项)
  • 🔢 int:整数(如年龄、计数器)
  • 📊 double:浮点数(如分数、价格)
  • bool:布尔值(如开关状态)
  • 📋 List<String>:字符串列表(如标签、历史记录)

🛠️ 2. 二次封装:lib/services/shared_preferences_service.dart

为了提高代码复用性和可维护性,我们创建一个服务类对 shared_preferences 进行二次封装。

2.1 为什么需要二次封装?

问题场景:

  • ❌ 每次使用都要获取 SharedPreferences 实例
  • ❌ 异常处理代码重复
  • ❌ 缺少统一的日志记录
  • ❌ 键名管理混乱

二次封装的好处:

  • 代码复用:封装常用功能,避免重复编写
  • 统一管理:集中处理初始化、错误、日志
  • 易于维护:修改只需在一处进行
  • 类型安全:提供类型明确的 API

2.2 创建服务类

新建文件lib/services/shared_preferences_service.dart

📊 SharedPreferences 服务架构流程:
未初始化
已初始化
写入
读取
删除
清空
🎨 UI层调用
📦 SharedPreferencesService单例
🔧 初始化检查
⚙️ getInstance获取实例
🎯 选择操作类型
💾 setXxx方法
📖 getXxx方法
🗑️ remove方法
🧹 clear方法
✅ 返回操作结果
📱 UI更新显示

核心代码实现:

dart 复制代码
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart';

/// 💾 本地存储服务类
/// 对 shared_preferences 进行二次封装,提供统一的本地存储接口
class SharedPreferencesService {
  // 单例模式
  static final SharedPreferencesService _instance = SharedPreferencesService._internal();
  factory SharedPreferencesService() => _instance;
  SharedPreferencesService._internal();

  SharedPreferences? _prefs;

  /// 🔧 初始化 SharedPreferences
  Future<void> init() async {
    _prefs ??= await SharedPreferences.getInstance();
  }

  /// 🔍 获取 SharedPreferences 实例
  Future<SharedPreferences> get prefs async {
    await init();
    return _prefs!;
  }

  // ==================== String 类型 ====================
  
  /// 📝 保存字符串
  Future<bool> setString(String key, String value) async {
    try {
      await init();
      return await _prefs!.setString(key, value);
    } catch (e) {
      if (kDebugMode) {
        print('📝 保存字符串错误: $e');
      }
      return false;
    }
  }

  /// 📖 读取字符串
  Future<String?> getString(String key) async {
    try {
      await init();
      return _prefs!.getString(key);
    } catch (e) {
      if (kDebugMode) {
        print('📖 读取字符串错误: $e');
      }
      return null;
    }
  }

  // ==================== int 类型 ====================
  
  /// 🔢 保存整数
  Future<bool> setInt(String key, int value) async {
    try {
      await init();
      return await _prefs!.setInt(key, value);
    } catch (e) {
      if (kDebugMode) {
        print('🔢 保存整数错误: $e');
      }
      return false;
    }
  }

  /// 🔢 读取整数
  Future<int?> getInt(String key) async {
    try {
      await init();
      return _prefs!.getInt(key);
    } catch (e) {
      if (kDebugMode) {
        print('🔢 读取整数错误: $e');
      }
      return null;
    }
  }

  // ==================== double 类型 ====================
  
  /// 📊 保存浮点数
  Future<bool> setDouble(String key, double value) async {
    try {
      await init();
      return await _prefs!.setDouble(key, value);
    } catch (e) {
      if (kDebugMode) {
        print('📊 保存浮点数错误: $e');
      }
      return false;
    }
  }

  /// 📊 读取浮点数
  Future<double?> getDouble(String key) async {
    try {
      await init();
      return _prefs!.getDouble(key);
    } catch (e) {
      if (kDebugMode) {
        print('📊 读取浮点数错误: $e');
      }
      return null;
    }
  }

  // ==================== bool 类型 ====================
  
  /// ✅ 保存布尔值
  Future<bool> setBool(String key, bool value) async {
    try {
      await init();
      return await _prefs!.setBool(key, value);
    } catch (e) {
      if (kDebugMode) {
        print('✅ 保存布尔值错误: $e');
      }
      return false;
    }
  }

  /// ✅ 读取布尔值
  Future<bool?> getBool(String key) async {
    try {
      await init();
      return _prefs!.getBool(key);
    } catch (e) {
      if (kDebugMode) {
        print('✅ 读取布尔值错误: $e');
      }
      return null;
    }
  }

  // ==================== List<String> 类型 ====================
  
  /// 📋 保存字符串列表
  Future<bool> setStringList(String key, List<String> value) async {
    try {
      await init();
      return await _prefs!.setStringList(key, value);
    } catch (e) {
      if (kDebugMode) {
        print('📋 保存字符串列表错误: $e');
      }
      return false;
    }
  }

  /// 📋 读取字符串列表
  Future<List<String>?> getStringList(String key) async {
    try {
      await init();
      return _prefs!.getStringList(key);
    } catch (e) {
      if (kDebugMode) {
        print('📋 读取字符串列表错误: $e');
      }
      return null;
    }
  }

  // ==================== 通用操作 ====================
  
  /// 🗑️ 删除指定键
  Future<bool> remove(String key) async {
    try {
      await init();
      return await _prefs!.remove(key);
    } catch (e) {
      if (kDebugMode) {
        print('🗑️ 删除键错误: $e');
      }
      return false;
    }
  }

  /// 🧹 清空所有数据
  Future<bool> clear() async {
    try {
      await init();
      return await _prefs!.clear();
    } catch (e) {
      if (kDebugMode) {
        print('🧹 清空数据错误: $e');
      }
      return false;
    }
  }

  /// 🔍 检查键是否存在
  Future<bool> containsKey(String key) async {
    try {
      await init();
      return _prefs!.containsKey(key);
    } catch (e) {
      if (kDebugMode) {
        print('🔍 检查键错误: $e');
      }
      return false;
    }
  }

  /// 📊 获取所有键
  Future<Set<String>> getKeys() async {
    try {
      await init();
      return _prefs!.getKeys();
    } catch (e) {
      if (kDebugMode) {
        print('📊 获取所有键错误: $e');
      }
      return {};
    }
  }
}

👶 新手小课堂:为什么使用单例模式?

你可能注意到了这段代码:

dart 复制代码
static final SharedPreferencesService _instance = SharedPreferencesService._internal();
factory SharedPreferencesService() => _instance;
SharedPreferencesService._internal();

这是 单例模式(Singleton Pattern) 的实现。

为什么 SharedPreferences 需要单例?

  1. 💾 资源共享:SharedPreferences 实例获取是异步的,创建多个实例浪费资源
  2. 🔄 状态一致:确保整个应用使用同一个存储实例
  3. 性能优化:避免重复初始化,提高访问速度
  4. 🎯 管理方便:集中管理所有存储操作

✨ 单例模式的优势:

  • 💾 节省内存:避免重复创建对象
  • 提高性能:只初始化一次
  • 🎯 统一管理:全局共享同一实例
  • 🔒 线程安全:Dart 的单例实现天然线程安全

💻 3. 使用方法:lib/shared_preferences_demo.dart

接下来,我们创建一个完整的演示页面,展示如何使用封装好的服务类。

新建文件lib/shared_preferences_demo.dart

3.1 页面功能概览

我们的演示页面将实现以下功能:

  • 📝 字符串存取:保存和读取用户名等文本
  • 🔢 整数存取:保存和读取年龄、计数器等
  • 📊 浮点数存取:保存和读取分数、价格等
  • 布尔值存取:保存和读取开关状态
  • 📋 列表存取:保存和读取标签、历史记录等
  • 💾 数据展示:查看所有已存储的数据
  • 🗑️ 数据管理:删除单条或清空所有数据

3.2 核心代码实现

导入依赖:

dart 复制代码
import 'package:flutter/material.dart';
import 'services/shared_preferences_service.dart';

初始化服务:

dart 复制代码
final SharedPreferencesService _prefsService = SharedPreferencesService();

@override
void initState() {
  super.initState();
  _initPrefs();
}

Future<void> _initPrefs() async {
  await _prefsService.init();
  _loadAllData();  // 加载已存储的数据
}

保存字符串示例:

dart 复制代码
Future<void> _saveString() async {
  final key = _stringKeyController.text.trim();
  final value = _stringValueController.text.trim();
  
  if (key.isEmpty || value.isEmpty) {
    _showMessage('请输入键和值', isError: true);
    return;
  }
  
  setState(() => _isLoading = true);
  final success = await _prefsService.setString(key, value);
  _showMessage(success ? '✅ 字符串保存成功' : '❌ 保存失败', isError: !success);
  await _loadAllData();  // 刷新数据列表
}

读取字符串示例:

dart 复制代码
Future<void> _readString() async {
  final key = _stringKeyController.text.trim();
  
  if (key.isEmpty) {
    _showMessage('请输入键名', isError: true);
    return;
  }
  
  setState(() => _isLoading = true);
  final value = await _prefsService.getString(key);
  _showMessage(value != null ? '📖 读取成功: $value' : '⚠️ 键不存在', isError: value == null);
  setState(() => _isLoading = false);
}

保存布尔值示例:

dart 复制代码
Future<void> _saveBool() async {
  final key = _boolKeyController.text.trim();
  
  if (key.isEmpty) {
    _showMessage('请输入键名', isError: true);
    return;
  }
  
  setState(() => _isLoading = true);
  final success = await _prefsService.setBool(key, _boolValue);
  _showMessage(success ? '✅ 布尔值保存成功' : '❌ 保存失败', isError: !success);
  await _loadAllData();
}

保存列表示例:

dart 复制代码
Future<void> _saveList() async {
  final key = _listKeyController.text.trim();
  final valueStr = _listValueController.text.trim();
  
  if (key.isEmpty || valueStr.isEmpty) {
    _showMessage('请输入键和值', isError: true);
    return;
  }
  
  // 将逗号分隔的字符串转为列表
  final list = valueStr.split(',').map((e) => e.trim()).toList();
  
  setState(() => _isLoading = true);
  final success = await _prefsService.setStringList(key, list);
  _showMessage(success ? '✅ 列表保存成功' : '❌ 保存失败', isError: !success);
  await _loadAllData();
}

清空所有数据:

dart 复制代码
Future<void> _clearAll() async {
  final confirmed = await showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('⚠️ 确认清空'),
      content: const Text('确定要清空所有存储的数据吗?此操作不可恢复!'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context, false),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, true),
          style: TextButton.styleFrom(foregroundColor: Colors.red),
          child: const Text('确认清空'),
        ),
      ],
    ),
  );
  
  if (confirmed == true) {
    setState(() => _isLoading = true);
    final success = await _prefsService.clear();
    _showMessage(success ? '🧹 数据已清空' : '❌ 清空失败', isError: !success);
    await _loadAllData();
  }
}

👶 新手小课堂:异步操作与 async/await

你可能注意到代码中大量使用了 asyncawait

dart 复制代码
Future<void> _saveString() async {
  final success = await _prefsService.setString(key, value);
}

为什么需要异步?

SharedPreferences 的读写操作涉及到磁盘 I/O,如果同步执行会阻塞主线程,导致界面卡顿。

异步操作的好处:

  • 不阻塞 UI:用户界面保持响应
  • 🔄 并发执行:可以同时执行多个操作
  • 🎯 代码清晰:async/await 使异步代码像同步一样易读

使用模式:

dart 复制代码
// ❌ 错误:不等待异步操作
void saveData() {
  _prefsService.setString('key', 'value');  // 可能还没保存完就继续了
  print('已保存');  // 实际可能还没保存
}

// ✅ 正确:等待异步操作完成
Future<void> saveData() async {
  await _prefsService.setString('key', 'value');  // 等待保存完成
  print('已保存');  // 确保已经保存
}

🚀 4. 配置应用入口:lib/main.dart

最后,我们在应用的主页添加入口,跳转到演示页面。

修改文件lib/main.dart

操作:

  1. 导入演示页面文件
  2. 在按钮列表中添加跳转按钮
dart 复制代码
// 1. 导入头文件
import 'shared_preferences_demo.dart'; 

// 2. 在 build 方法中添加跳转按钮
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const SharedPreferencesDemoPage(),
      ),
    );
  },
  child: const Text('Go to SharedPreferences Demo'),
),

⚠️ 5. 常见错误与解决方案

❌ 错误 1:初始化失败

😱 错误现象:

  • 调用 SharedPreferences.getInstance() 卡住
  • 抛出 MissingPluginException 异常

🔍 原因分析:

  • ❌ 依赖未正确安装
  • ❌ 原生插件未注册
  • ❌ Flutter 绑定未初始化

✅ 解决方案:

1. 确保依赖已安装:

bash 复制代码
flutter pub get

2. 在 main() 中确保初始化:

dart 复制代码
void main() async {
  // 👇 确保 Flutter 绑定初始化
  WidgetsFlutterBinding.ensureInitialized();
  
  // 可选:预先初始化 SharedPreferences
  await SharedPreferences.getInstance();
  
  runApp(const MyApp());
}

3. 清理并重新构建:

bash 复制代码
flutter clean
flutter pub get
flutter run

❌ 错误 2:类型不匹配

😱 错误现象:

  • 保存的是 int,读取时用 getString,返回 null
  • 保存的是 String,读取时用 getInt,返回 null

🔍 原因分析:

  • ❌ 保存和读取使用了不同的类型方法
  • ❌ 键名拼写错误

✅ 解决方案:

1. 保持类型一致:

dart 复制代码
// ❌ 错误:类型不匹配
await prefs.setInt('age', 25);
final age = prefs.getString('age');  // 返回 null

// ✅ 正确:类型一致
await prefs.setInt('age', 25);
final age = prefs.getInt('age');  // 返回 25

2. 使用常量管理键名:

dart 复制代码
// 定义键名常量,避免拼写错误
class PrefsKeys {
  static const String username = 'username';
  static const String age = 'age';
  static const String isVip = 'isVip';
}

// 使用常量
await prefs.setString(PrefsKeys.username, '张三');
final name = prefs.getString(PrefsKeys.username);

❌ 错误 3:数据未持久化

😱 错误现象:

  • 保存成功,但重启应用后数据丢失
  • 热重载后数据消失

🔍 原因分析:

  • ❌ 保存操作未完成就退出
  • ❌ 使用了错误的存储路径
  • ❌ 鸿蒙系统权限问题

✅ 解决方案:

1. 确保保存操作完成:

dart 复制代码
// ❌ 错误:没有等待保存完成
void onExit() {
  prefs.setString('key', 'value');  // 可能还没保存就退出了
}

// ✅ 正确:等待保存完成
Future<void> onExit() async {
  await prefs.setString('key', 'value');  // 确保保存完成
}

2. 检查返回值:

dart 复制代码
final success = await prefs.setString('key', 'value');
if (!success) {
  print('保存失败,请检查存储权限');
}

❌ 错误 4:存储大量数据导致卡顿

😱 错误现象:

  • 保存大量数据时界面卡顿
  • 读取数据时响应缓慢

🔍 原因分析:

  • ❌ SharedPreferences 不适合存储大量数据
  • ❌ 频繁读写操作

✅ 解决方案:

1. 合理使用存储:

数据类型 推荐存储方式
简单配置(<100KB) ✅ SharedPreferences
大量结构化数据 🗄️ SQLite / Hive
大文件 📁 文件存储

2. 批量操作优化:

dart 复制代码
// ❌ 错误:多次调用
await prefs.setString('key1', 'value1');
await prefs.setString('key2', 'value2');
await prefs.setString('key3', 'value3');

// ✅ 正确:一次性操作(如需批量存储,考虑用 Map 转 JSON)
final data = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'};
await prefs.setString('batchData', jsonEncode(data));

❌ 错误 5:键名冲突

😱 错误现象:

  • 不同功能模块使用了相同的键名
  • 数据被意外覆盖

🔍 原因分析:

  • ❌ 没有统一管理键名
  • ❌ 键名命名不规范

✅ 解决方案:

使用命名空间或前缀:

dart 复制代码
// 定义键名常量类,使用前缀区分模块
class PrefsKeys {
  // 用户模块
  static const String userUsername = 'user_username';
  static const String userAge = 'user_age';
  static const String userIsVip = 'user_isVip';
  
  // 设置模块
  static const String settingTheme = 'setting_theme';
  static const String settingLanguage = 'setting_language';
  
  // 缓存模块
  static const String cacheLastUpdate = 'cache_lastUpdate';
  static const String cacheTags = 'cache_tags';
}

📋 6. 数据类型与用途对比表

数据类型 方法 适用场景 示例
📝 String setString/getString 文本配置 用户名、Token、配置项
🔢 int setInt/getInt 整数值 年龄、计数器、页码
📊 double setDouble/getDouble 浮点数 分数、价格、进度
✅ bool setBool/getBool 开关状态 是否登录、是否VIP、是否首次启动
📋 List<String> setStringList/getStringList 字符串数组 标签、搜索历史、收藏列表

使用场景建议:

  • 🔐 登录状态 → 使用 bool
  • 👤 用户信息 → 使用 String
  • 🎯 计数器 → 使用 int
  • 💰 金额 → 使用 double
  • 🏷️ 标签列表 → 使用 List<String>

🎨 7. 最佳实践建议

✨ 键名管理

1. 使用常量类管理所有键名:

dart 复制代码
/// 📝 SharedPreferences 键名常量
class PrefsKeys {
  PrefsKeys._();  // 私有构造函数,防止实例化
  
  // 用户相关
  static const String username = 'pref_username';
  static const String userId = 'pref_user_id';
  static const String isLoggedIn = 'pref_is_logged_in';
  
  // 设置相关
  static const String themeMode = 'pref_theme_mode';
  static const String language = 'pref_language';
  static const String fontSize = 'pref_font_size';
  
  // 缓存相关
  static const String lastSyncTime = 'pref_last_sync_time';
  static const String cacheVersion = 'pref_cache_version';
}

🔒 数据安全

1. 敏感数据不要明文存储:

dart 复制代码
// ❌ 错误:明文存储密码
await prefs.setString('password', '123456');

// ✅ 正确:敏感数据使用 flutter_secure_storage
// 或至少进行加密处理
import 'dart:convert';
import 'package:crypto/crypto.dart';

String hashPassword(String password) {
  final bytes = utf8.encode(password);
  return sha256.convert(bytes).toString();
}

await prefs.setString('passwordHash', hashPassword('123456'));

⚡ 性能优化

1. 避免频繁读写:

dart 复制代码
// ❌ 错误:每次都读取
Widget build(BuildContext context) {
  final username = prefs.getString('username');  // 每次 build 都读取
  return Text(username ?? '');
}

// ✅ 正确:缓存到变量
String? _username;

@override
void initState() {
  super.initState();
  _loadUsername();
}

Future<void> _loadUsername() async {
  final username = await _prefsService.getString('username');
  setState(() => _username = username);
}

Widget build(BuildContext context) {
  return Text(_username ?? '');  // 使用缓存的值
}

2. 批量数据使用 JSON:

dart 复制代码
// 存储复杂对象
final user = {'name': '张三', 'age': 25, 'tags': ['Flutter', '鸿蒙']};
await prefs.setString('user', jsonEncode(user));

// 读取复杂对象
final userJson = prefs.getString('user');
if (userJson != null) {
  final user = jsonDecode(userJson) as Map<String, dynamic>;
}

🎉 结语

通过以上步骤,我们完成了鸿蒙化Flutter三方库 shared_preferences 在鸿蒙系统上的完整集成!🚀

📚 完整存储流程:
📝 String
🔢 int
📊 double
✅ bool
📋 List


📚 开始
📦 引入shared_preferences依赖
🛠️ 创建SharedPreferencesService单例
🔧 初始化getInstance
📱 UI层调用服务方法
🎯 选择数据类型
setString/getString
setInt/getInt
setDouble/getDouble
setBool/getBool
setStringList/getStringList
💾 数据持久化存储
✅ 操作完成
🔄 需要其他操作?
🎉 完成

🌟 核心API总结:

API 用途 返回值 说明
getInstance() 获取实例 Future 异步获取单例实例
setXxx() 保存数据 Future 返回操作是否成功
getXxx() 读取数据 T? 返回值或 null
remove() 删除键 Future 删除指定键值对
clear() 清空数据 Future 清空所有数据
containsKey() 检查键 bool 检查键是否存在
getKeys() 获取所有键 Set 返回所有键名集合

快去运行你的鸿蒙应用试试吧!Happy Coding! 💻⚡️

相关推荐
zhuweisky3 小时前
ArkTS实现鸿蒙手机视频聊天、屏幕分享(HarmonyOS)
音视频·harmonyos·鸿蒙开发
无熵~3 小时前
Flutter入门
flutter
hudawei9964 小时前
要控制动画的widget为什么要with SingleTickerProviderStateMixin
flutter·mixin·with·ticker·动画控制
大雷神4 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地--第32篇:应用测试、优化与调试
华为·harmonyos
前端不太难4 小时前
HarmonyOS 游戏中,被“允许”的异常
游戏·状态模式·harmonyos
木斯佳5 小时前
HarmonyOS 6实战(源码教学篇)— MindSpore Lite Kit 【从证件照工具到端侧图像分割技术全解析】
华为·harmonyos
三声三视5 小时前
HarmonyOS 路由框架 HMRouter 全解析:从原理到实践
华为·harmonyos
jian110585 小时前
flutter dio 依赖,dependencies 和 dev_dependencies的区别
flutter
王码码20355 小时前
Flutter for OpenHarmony 实战之基础组件:第十七篇 滚动进阶 ScrollController 与 Scrollbar
flutter·harmonyos