Flutter跨平台三方库shared_preferences在鸿蒙中的使用指南
本文档详细介绍了在HarmonyOS平台上使用Flutter框架集成shared_preferences插件实现数据持久化的完整流程。项目通过构建一个符合Material Design 3规范的设置管理应用,展示了布尔值、整数、浮点数、字符串及字符串列表等多种数据类型的存储与读取操作。文档包含从项目初始化、依赖配置到核心功能实现的完整代码示例,并特别说明必须使用OpenHarmony TPC提供的
📋 项目概述
本文档详细介绍如何在 HarmonyOS 平台上使用 Flutter 框架集成 shared_preferences 插件,实现应用数据的持久化存储。通过实际开发案例,展示从插件配置到功能实现的完整流程,并记录开发过程中遇到的问题及解决方案。本项目构建了一个现代化的设置管理应用,采用 Material Design 3 设计规范,提供了流畅的用户体验和丰富的交互功能,全面展示了 shared_preferences 支持的各种数据类型(布尔值、整数、浮点数、字符串、字符串列表)的存储和读取。

运行截图说明:本文档中的代码已在 HarmonyOS 设备上实际运行测试,功能正常运行。建议读者在阅读时结合实际操作,以获得更好的学习效果。
🎯 项目目标
- ✅ 在 HarmonyOS 平台上集成
shared_preferences插件 - ✅ 实现布尔值(Bool)的存储和读取
- ✅ 实现整数(Int)的存储和读取
- ✅ 实现浮点数(Double)的存储和读取
- ✅ 实现字符串(String)的存储和读取
- ✅ 实现字符串列表(StringList)的存储和读取
- ✅ 构建美观的 Material Design 3 风格UI
- ✅ 实现实时保存和读取功能
- ✅ 实现清除所有设置功能
🛠️ 技术栈
- 开发框架: Flutter 3.6.2+
- 三方库: shared_preferences (OpenHarmony TPC 适配版本)
- UI 框架: Material Design 3
- 目标平台: HarmonyOS (OpenHarmony)
- 开发工具: DevEco Studio / VS Code
📦 一、项目初始化
1.1 创建 Flutter 项目
flutter create --platforms=ohos shared_preferences_demo
cd shared_preferences_demo
1.2 配置依赖
在 pubspec.yaml 中添加 shared_preferences 依赖:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
shared_preferences:
git:
url: https://atomgit.com/openharmony-tpc/flutter_packages.git
path: packages/shared_preferences/shared_preferences
ref: br_shared_preferences-v2.5.3_ohos
重要说明:
- 必须使用 OpenHarmony TPC 提供的适配版本,pub.dev 上的官方版本不支持 HarmonyOS 平台
- 需要指定正确的分支引用
br_shared_preferences-v2.5.3_ohos shared_preferences主包会自动处理平台实现包的依赖,无需手动添加shared_preferences_ohos
1.3 安装依赖
flutter pub get
🔐 二、权限配置
shared_preferences 插件在 HarmonyOS 平台上不需要任何特殊权限。它使用系统提供的键值对存储机制,无需网络权限、文件访问权限或其他敏感权限。
💻 三、核心功能实现
3.1 导入必要的包
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
3.2 初始化 SharedPreferences
在使用 shared_preferences 之前,需要先获取 SharedPreferences 实例:
SharedPreferences prefs = await SharedPreferences.getInstance();
重要提示:getInstance() 是一个异步方法,必须使用 await 等待其完成。建议在应用启动时或页面初始化时获取实例,并在整个应用生命周期中复用。
3.3 存储和读取布尔值(Bool)
布尔值通常用于存储开关状态、功能启用/禁用等设置。
存储布尔值
await prefs.setBool('notifications_enabled', true);
await prefs.setBool('dark_mode_enabled', false);
读取布尔值
bool notificationsEnabled = prefs.getBool('notifications_enabled') ?? false;
bool darkModeEnabled = prefs.getBool('dark_mode_enabled') ?? false;
注意事项:
getBool()方法可能返回null(如果键不存在),因此需要使用??运算符提供默认值- 推荐在读取时始终提供默认值,避免空指针异常
完整示例
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
late SharedPreferences _prefs;
bool _notificationsEnabled = false;
void initState() {
super.initState();
_loadSettings();
}
Future<void> _loadSettings() async {
_prefs = await SharedPreferences.getInstance();
setState(() {
_notificationsEnabled = _prefs.getBool('notifications_enabled') ?? false;
});
}
Future<void> _toggleNotifications(bool value) async {
setState(() {
_notificationsEnabled = value;
});
await _prefs.setBool('notifications_enabled', value);
// 显示保存反馈
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('设置已保存')),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('设置')),
body: SwitchListTile(
title: const Text('通知'),
value: _notificationsEnabled,
onChanged: _toggleNotifications,
),
);
}
}
3.4 存储和读取整数(Int)
整数通常用于存储字体大小、计数、索引等数值。
存储整数
await prefs.setInt('font_size', 16);
await prefs.setInt('counter', 100);
读取整数
int fontSize = prefs.getInt('font_size') ?? 16;
int counter = prefs.getInt('counter') ?? 0;
完整示例
class FontSizeSetting extends StatefulWidget {
const FontSizeSetting({super.key});
State<FontSizeSetting> createState() => _FontSizeSettingState();
}
class _FontSizeSettingState extends State<FontSizeSetting> {
late SharedPreferences _prefs;
int _fontSize = 16;
void initState() {
super.initState();
_loadFontSize();
}
Future<void> _loadFontSize() async {
_prefs = await SharedPreferences.getInstance();
setState(() {
_fontSize = _prefs.getInt('font_size') ?? 16;
});
}
Future<void> _updateFontSize(int value) async {
setState(() {
_fontSize = value;
});
await _prefs.setInt('font_size', value);
}
Widget build(BuildContext context) {
return Column(
children: [
Text('字体大小: $_fontSize'),
Slider(
value: _fontSize.toDouble(),
min: 12,
max: 24,
divisions: 12,
onChanged: (value) => _updateFontSize(value.round()),
),
],
);
}
}
3.5 存储和读取浮点数(Double)
浮点数通常用于存储音量、进度、百分比等需要小数精度的数值。
存储浮点数
await prefs.setDouble('volume_level', 0.75);
await prefs.setDouble('progress', 0.5);
读取浮点数
double volumeLevel = prefs.getDouble('volume_level') ?? 0.5;
double progress = prefs.getDouble('progress') ?? 0.0;
完整示例
class VolumeSetting extends StatefulWidget {
const VolumeSetting({super.key});
State<VolumeSetting> createState() => _VolumeSettingState();
}
class _VolumeSettingState extends State<VolumeSetting> {
late SharedPreferences _prefs;
double _volumeLevel = 0.5;
void initState() {
super.initState();
_loadVolume();
}
Future<void> _loadVolume() async {
_prefs = await SharedPreferences.getInstance();
setState(() {
_volumeLevel = _prefs.getDouble('volume_level') ?? 0.5;
});
}
Future<void> _updateVolume(double value) async {
setState(() {
_volumeLevel = value;
});
await _prefs.setDouble('volume_level', value);
}
Widget build(BuildContext context) {
return Column(
children: [
Text('音量: ${(_volumeLevel * 100).round()}%'),
Slider(
value: _volumeLevel,
min: 0.0,
max: 1.0,
divisions: 10,
onChanged: _updateVolume,
),
],
);
}
}
3.6 存储和读取字符串(String)
字符串通常用于存储用户名、主题名称、配置项等文本数据。
存储字符串
await prefs.setString('username', 'John Doe');
await prefs.setString('selected_theme', 'dark');
读取字符串
String username = prefs.getString('username') ?? '';
String selectedTheme = prefs.getString('selected_theme') ?? 'light';
完整示例
class UsernameSetting extends StatefulWidget {
const UsernameSetting({super.key});
State<UsernameSetting> createState() => _UsernameSettingState();
}
class _UsernameSettingState extends State<UsernameSetting> {
late SharedPreferences _prefs;
final TextEditingController _controller = TextEditingController();
void initState() {
super.initState();
_loadUsername();
}
Future<void> _loadUsername() async {
_prefs = await SharedPreferences.getInstance();
final username = _prefs.getString('username') ?? '';
_controller.text = username;
}
Future<void> _saveUsername(String value) async {
await _prefs.setString('username', value);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('用户名已保存')),
);
}
Widget build(BuildContext context) {
return TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
),
onChanged: _saveUsername,
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
3.7 存储和读取字符串列表(StringList)
字符串列表通常用于存储标签、收藏列表、历史记录等多项数据。
存储字符串列表
List<String> tags = ['工作', '学习', '娱乐'];
await prefs.setStringList('selected_tags', tags);
读取字符串列表
List<String> tags = prefs.getStringList('selected_tags') ?? [];
完整示例
class TagsSelector extends StatefulWidget {
const TagsSelector({super.key});
State<TagsSelector> createState() => _TagsSelectorState();
}
class _TagsSelectorState extends State<TagsSelector> {
late SharedPreferences _prefs;
List<String> _selectedTags = [];
final List<String> _availableTags = ['工作', '学习', '娱乐', '运动', '旅行', '美食'];
void initState() {
super.initState();
_loadTags();
}
Future<void> _loadTags() async {
_prefs = await SharedPreferences.getInstance();
setState(() {
_selectedTags = _prefs.getStringList('selected_tags') ?? [];
});
}
Future<void> _toggleTag(String tag) async {
setState(() {
if (_selectedTags.contains(tag)) {
_selectedTags.remove(tag);
} else {
_selectedTags.add(tag);
}
});
await _prefs.setStringList('selected_tags', _selectedTags);
}
Widget build(BuildContext context) {
return Wrap(
spacing: 8,
children: _availableTags.map((tag) {
final isSelected = _selectedTags.contains(tag);
return FilterChip(
label: Text(tag),
selected: isSelected,
onSelected: (_) => _toggleTag(tag),
);
}).toList(),
);
}
}
3.8 清除数据
清除单个键值对
await prefs.remove('username');
清除所有数据
await prefs.clear();
注意事项:
remove()方法只删除指定的键值对clear()方法会删除所有存储的数据,请谨慎使用- 建议在清除所有数据前显示确认对话框
完整示例
Future<void> _clearAllSettings() 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) {
await _prefs.clear();
// 重新加载设置
_loadSettings();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('所有设置已清除')),
);
}
}
3.9 检查键是否存在
bool exists = prefs.containsKey('username');
3.10 获取所有键
Set<String> keys = prefs.getKeys();
🎨 四、UI设计实现
4.1 动态渐变背景
使用 AnimationController 创建持续变化的渐变背景:
class _SettingsPageState extends State<SettingsPage>
with TickerProviderStateMixin {
late AnimationController _backgroundController;
void initState() {
super.initState();
_backgroundController = AnimationController(
vsync: this,
duration: const Duration(seconds: 20),
)..repeat();
}
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedBuilder(
animation: _backgroundController,
builder: (context, child) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color.lerp(
Colors.deepPurple.shade300,
Colors.pink.shade300,
(_backgroundController.value * 2) % 1,
)!,
Color.lerp(
Colors.blue.shade300,
Colors.purple.shade300,
(_backgroundController.value * 2 + 0.5) % 1,
)!,
],
),
),
child: child,
);
},
child: SafeArea(
child: Column(
children: [
// 设置项内容
],
),
),
),
);
}
void dispose() {
_backgroundController.dispose();
super.dispose();
}
}
4.2 设置卡片设计
创建美观的设置卡片组件:
class _SettingCard extends StatelessWidget {
final Widget child;
final Color color;
const _SettingCard({
required this.child,
required this.color,
});
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: child,
);
}
}
4.3 开关设置项
Widget _buildSwitchCard({
required String title,
required String subtitle,
required IconData icon,
required bool value,
required Color color,
required ValueChanged<bool> onChanged,
}) {
return _SettingCard(
color: color,
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Switch(
value: value,
onChanged: onChanged,
activeColor: color,
),
],
),
);
}
4.4 滑块设置项
Widget _buildSliderCard({
required String title,
required String subtitle,
required IconData icon,
required double value,
required Color color,
required double min,
required double max,
required int divisions,
required ValueChanged<double> onChanged,
String Function(double)? formatValue,
}) {
return _SettingCard(
color: color,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(
formatValue != null
? formatValue(value)
: '${(value * 100).round()}%',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: color,
),
),
),
],
),
const SizedBox(height: 16),
Slider(
value: value,
min: min,
max: max,
divisions: divisions,
activeColor: color,
inactiveColor: color.withOpacity(0.3),
onChanged: onChanged,
),
],
),
);
}
⚠️ 五、常见问题与解决方案
5.1 数据未保存
问题:调用 setBool()、setString() 等方法后,数据没有保存。
解决方案:
-
确保使用
await:所有set*方法都是异步的,必须使用await等待完成// 错误 prefs.setBool('key', true); // 正确 await prefs.setBool('key', true); -
确保方法标记为
async:调用set*方法的方法必须标记为async// 错误 void _saveSetting() { prefs.setBool('key', true); } // 正确 Future<void> _saveSetting() async { await prefs.setBool('key', true); }
5.2 读取数据返回 null
问题:调用 getBool()、getString() 等方法返回 null。
解决方案:
使用 ?? 运算符提供默认值:
// 错误:可能返回 null
bool value = prefs.getBool('key');
// 正确:提供默认值
bool value = prefs.getBool('key') ?? false;
String username = prefs.getString('username') ?? '';
int fontSize = prefs.getInt('font_size') ?? 16;
double volume = prefs.getDouble('volume_level') ?? 0.5;
List<String> tags = prefs.getStringList('tags') ?? [];
5.3 数据丢失
问题:应用重启后,之前保存的数据丢失。
解决方案:
-
检查键名是否正确:确保存储和读取使用相同的键名
// 存储 await prefs.setString('username', 'John'); // 读取(键名必须一致) String username = prefs.getString('username') ?? ''; -
检查应用是否被卸载:卸载应用会清除所有 SharedPreferences 数据
-
检查是否调用了
clear():确保没有意外调用clear()方法
5.4 性能问题
问题:频繁读写导致性能问题。
解决方案:
-
复用 SharedPreferences 实例:在应用启动时获取实例,并在整个生命周期中复用
class _SettingsPageState extends State<SettingsPage> { late SharedPreferences _prefs; void initState() { super.initState(); _loadPreferences(); } Future<void> _loadPreferences() async { _prefs = await SharedPreferences.getInstance(); } // 后续操作都使用 _prefs } -
批量操作:如果需要保存多个值,可以连续调用多个
set*方法,它们会批量提交await Future.wait([ prefs.setBool('notifications', true), prefs.setString('username', 'John'), prefs.setInt('font_size', 16), ]);
5.5 MissingPluginException 错误
问题:运行时出现 MissingPluginException: No implementation found for method getAll on channel plugins.flutter.io/shared_preferences。
解决方案:
这是 HarmonyOS 平台上最常见的问题,原因是插件没有正确注册到原生平台。需要完成以下步骤:
-
确保依赖正确配置:在
pubspec.yaml中正确配置了shared_preferences依赖 -
重新构建项目:
flutter clean flutter pub get cd ohos/entry && ohpm install -
检查插件注册:检查
ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets文件是否包含shared_preferences_ohos的注册 -
验证配置:检查
.flutter-plugins文件是否包含shared_preferences_ohos:cat .flutter-plugins | grep shared_preferences
重要提示:
GeneratedPluginRegistrant.ets文件虽然标记为自动生成,但在 HarmonyOS 平台上,如果插件没有自动注册,可能需要手动添加注册代码- 如果使用相对路径导入插件,需要确保路径正确
📝 六、最佳实践总结
6.1 数据存储规范
-
使用有意义的键名:键名应该清晰、有意义,便于维护
// 推荐 'notifications_enabled' 'user_name' 'font_size' // 不推荐 'flag1' 'data' 'value' -
提供默认值:读取数据时始终提供默认值
bool value = prefs.getBool('key') ?? false; -
类型一致性:确保存储和读取使用相同的数据类型
// 存储为字符串 await prefs.setString('count', '10'); // 读取时也要使用字符串 String count = prefs.getString('count') ?? '0';
6.2 错误处理
-
处理异步操作:所有
set*和getInstance()方法都是异步的,需要正确处理try { await prefs.setBool('key', true); } catch (e) { // 处理错误 print('保存失败: $e'); } -
处理空值:读取数据时始终处理可能的
null值
6.3 性能优化
-
复用实例:在应用启动时获取
SharedPreferences实例,并在整个生命周期中复用 -
避免频繁读写:不要在循环中频繁读写数据
-
批量操作:如果需要保存多个值,可以连续调用多个
set*方法
6.4 用户体验优化
-
显示保存反馈:保存数据后显示提示信息
await prefs.setBool('key', true); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('设置已保存')), ); -
加载状态:在加载数据时显示加载指示器
bool _isLoading = true; Future<void> _loadSettings() async { setState(() => _isLoading = true); _prefs = await SharedPreferences.getInstance(); // 加载数据... setState(() => _isLoading = false); } -
确认清除:清除所有数据前显示确认对话框
🔗 七、相关资源
📄 八、完整代码示例
以下是一个最小化的完整示例,可以直接复制使用:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shared Preferences Demo',
theme: ThemeData(useMaterial3: true),
home: const SettingsPage(),
);
}
}
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
late SharedPreferences _prefs;
bool _notificationsEnabled = false;
String _username = '';
void initState() {
super.initState();
_loadSettings();
}
Future<void> _loadSettings() async {
_prefs = await SharedPreferences.getInstance();
setState(() {
_notificationsEnabled = _prefs.getBool('notifications_enabled') ?? false;
_username = _prefs.getString('username') ?? '';
});
}
Future<void> _saveBool(String key, bool value) async {
await _prefs.setBool(key, value);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('设置已保存')),
);
}
Future<void> _saveString(String key, String value) async {
await _prefs.setString(key, value);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('设置已保存')),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('设置')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: const Text('通知'),
value: _notificationsEnabled,
onChanged: (value) {
setState(() {
_notificationsEnabled = value;
});
_saveBool('notifications_enabled', value);
},
),
TextField(
decoration: const InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
),
onChanged: (value) {
setState(() {
_username = value;
});
_saveString('username', value);
},
controller: TextEditingController(text: _username)
..selection = TextSelection.fromPosition(
TextPosition(offset: _username.length),
),
),
],
),
);
}
}
使用说明:
- 将上述代码保存到
lib/main.dart文件中 - 确保
pubspec.yaml中已添加shared_preferences依赖 - 运行
flutter pub get安装依赖 - 运行
flutter run启动应用
🌐 社区支持
欢迎加入开源鸿蒙跨平台社区,与其他开发者交流学习,共同推进鸿蒙跨平台生态建设:
开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
在这里你可以:
- 📚 获取最新的跨平台开发技术文档
- 💬 与其他开发者交流开发经验
- 🐛 反馈问题和建议
- 🎯 参与开源项目贡献
- 📖 学习更多跨平台开发最佳实践
享受你的设置管理开发之旅! 🚀✨
更多推荐



所有评论(0)