【Flutter for OpenHarmony 跨平台征文】Flutter Hive 数据持久化实战:血压记录本地存储完全指南
Flutter Hive 数据持久化实战:血压记录本地存储指南 本文介绍了使用Hive实现Flutter血压记录应用的数据持久化方案。作为NoSQL数据库,Hive相比SQLite具有性能优异(快10倍以上)、零配置、类型安全和跨平台等优势。文章详细展示了如何配置Hive依赖、创建自定义适配器存储血压记录对象,并实现完整的存储服务类,包含初始化Hive、保存记录(单条/批量)、按日期范围查询等功能
【Flutter for OpenHarmony 跨平台征文】Flutter Hive 数据持久化实战:血压记录本地存储完全指南
🎯 写在前面
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
👋 自我介绍
哈喽,大家好!我是 ,上海某高校大一计算机专业的学生 🚀。上篇文章讲了血压数据模型的设计,这次来聊聊数据持久化的问题。
之前我们写的血压记录功能,数据都是存在内存里的。一旦 App 重启,所有数据就消失了 😭。这怎么行?
所以今天我来实现 Hive 数据持久化,让血压记录能够永久保存!
一、为什么选择 Hive?
1.1 常见数据持久化方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SharedPreferences | 轻量、简单 | 只支持基本类型 | 简单配置存储 |
| SQLite | 功能强大、SQL查询 | 配置复杂 | 复杂关系数据 |
| Hive | 速度快、无类型限制 | 不支持SQL | NoSQL式存储 |
| Firebase | 实时同步、云端 | 需要网络 | 跨设备同步 |
对于血压记录这种 NoSQL 场景,Hive 是最佳选择!
1.2 Hive 的优势
| 优势 | 说明 |
|---|---|
| 性能优异 | 比 SQLite 快 10 倍以上 |
| 零配置 | 不需要复杂的数据库配置 |
| 类型安全 | 支持 Dart 对象的直接存储 |
| 轻量级 | 包体积小,适合移动端 |
| 跨平台 | 支持 iOS/Android/鸿蒙 |
二、安装与配置
2.1 添加依赖
# pubspec.yaml
dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.12
2.2 安装依赖
flutter pub get
三、血压数据存储服务实现
3.1 创建 Hive 适配器
为了让 Hive 能够存储自定义的 BloodPressureRecord 对象,我们需要创建适配器:
// lib/services/blood_pressure_storage_service.dart
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import '../models/health/health_model.dart';
// Hive 适配器 ID(需要唯一)
const int bloodPressureRecordTypeId = 100;
/// 血压记录 Hive 适配器
/// 用于将 BloodPressureRecord 对象转换为二进制数据
class BloodPressureRecordAdapter extends TypeAdapter<BloodPressureRecord> {
final int typeId = bloodPressureRecordTypeId;
BloodPressureRecord read(BinaryReader reader) {
// 读取字段数量
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
// 构建对象
return BloodPressureRecord(
id: fields[0] as String,
date: DateTime.fromMillisecondsSinceEpoch(fields[1] as int),
systolic: fields[2] as int,
diastolic: fields[3] as int,
pulse: fields[4] as int?,
note: fields[5] as String?,
);
}
void write(BinaryWriter writer, BloodPressureRecord obj) {
// 写入字段数量
writer.writeByte(6);
// 写入每个字段
writer.writeByte(0); writer.write(obj.id);
writer.writeByte(1); writer.write(obj.date.millisecondsSinceEpoch);
writer.writeByte(2); writer.write(obj.systolic);
writer.writeByte(3); writer.write(obj.diastolic);
writer.writeByte(4); writer.write(obj.pulse);
writer.writeByte(5); writer.write(obj.note);
}
}
3.2 存储服务类
/// 血压数据存储服务
class BloodPressureStorageService {
// Box 名称
static const String _boxName = 'bloodPressureBox';
static late Box<BloodPressureRecord> _box;
// 单例模式
static final BloodPressureStorageService _instance =
BloodPressureStorageService._internal();
factory BloodPressureStorageService() => _instance;
BloodPressureStorageService._internal();
/// 初始化 Hive
static Future<void> init() async {
try {
// 1. 获取应用文档目录
final directory = await getApplicationDocumentsDirectory();
// 2. 初始化 Hive
Hive.init(directory.path);
// 3. 注册适配器
if (!Hive.isAdapterRegistered(bloodPressureRecordTypeId)) {
Hive.registerAdapter(BloodPressureRecordAdapter());
}
// 4. 打开 Box(类似数据库表)
_box = await Hive.openBox<BloodPressureRecord>(_boxName);
print('BloodPressureStorageService 初始化成功');
} catch (e) {
print('初始化错误: $e');
rethrow;
}
}
/// 保存单条血压记录
Future<void> saveRecord(BloodPressureRecord record) async {
try {
// 使用记录 ID 作为键
await _box.put(record.id, record);
print('已保存记录: ${record.id}');
} catch (e) {
print('保存错误: $e');
rethrow;
}
}
/// 批量保存血压记录
Future<void> saveRecords(List<BloodPressureRecord> records) async {
try {
// 将记录列表转换为 Map
final map = {for (var r in records) r.id: r};
await _box.putAll(map);
print('已保存 ${records.length} 条记录');
} catch (e) {
print('批量保存错误: $e');
rethrow;
}
}
/// 获取所有血压记录(按日期降序)
List<BloodPressureRecord> getAllRecords() {
try {
final records = _box.values.toList();
// 按日期降序排列(最新的在前)
records.sort((a, b) => b.date.compareTo(a.date));
return records;
} catch (e) {
print('获取记录错误: $e');
return [];
}
}
/// 获取指定日期范围的记录
List<BloodPressureRecord> getRecordsByDateRange(DateTime start, DateTime end) {
try {
return _box.values.where((record) {
// 检查日期是否在范围内
return record.date.isAfter(start.subtract(const Duration(days: 1))) &&
record.date.isBefore(end.add(const Duration(days: 1)));
}).toList()
..sort((a, b) => b.date.compareTo(a.date));
} catch (e) {
print('日期范围查询错误: $e');
return [];
}
}
/// 获取指定日期的记录
List<BloodPressureRecord> getRecordsByDate(DateTime date) {
final startOfDay = DateTime(date.year, date.month, date.day);
final endOfDay = startOfDay.add(const Duration(days: 1));
return getRecordsByDateRange(startOfDay, endOfDay);
}
/// 获取某月的所有记录
List<BloodPressureRecord> getRecordsByMonth(int year, int month) {
final startOfMonth = DateTime(year, month, 1);
final endOfMonth = DateTime(year, month + 1, 0, 23, 59, 59);
return getRecordsByDateRange(startOfMonth, endOfMonth);
}
/// 获取最近 N 天的记录
List<BloodPressureRecord> getRecentRecords(int days) {
final now = DateTime.now();
final start = now.subtract(Duration(days: days));
return getRecordsByDateRange(start, now);
}
/// 删除单条记录
Future<void> deleteRecord(String id) async {
try {
await _box.delete(id);
print('已删除记录: $id');
} catch (e) {
print('删除错误: $e');
rethrow;
}
}
/// 清空所有记录
Future<void> clearAll() async {
try {
await _box.clear();
print('已清空所有记录');
} catch (e) {
print('清空错误: $e');
rethrow;
}
}
/// 获取记录总数
int get totalRecords => _box.length;
/// 计算平均血压(最近 N 天)
({int systolic, int diastolic, int pulse}) calculateAverage(int days) {
final records = getRecentRecords(days);
if (records.isEmpty) {
return (systolic: 0, diastolic: 0, pulse: 0);
}
int totalSys = 0;
int totalDia = 0;
int totalPulse = 0;
int pulseCount = 0;
for (final record in records) {
totalSys += record.systolic;
totalDia += record.diastolic;
if (record.pulse != null) {
totalPulse += record.pulse!;
pulseCount++;
}
}
return (
systolic: (totalSys / records.length).round(),
diastolic: (totalDia / records.length).round(),
pulse: pulseCount > 0 ? (totalPulse / pulseCount).round() : 0,
);
}
/// 获取有记录的所有日期(用于日历打点)
Set<DateTime> getRecordedDates() {
return _box.values
.map((record) => DateTime(
record.date.year, record.date.month, record.date.day))
.toSet();
}
}
四、血压统计数据类
/// 血压统计数据类
class BloodPressureStats {
final int recordCount; // 记录数
final int avgSystolic; // 平均收缩压
final int avgDiastolic; // 平均舒张压
final int avgPulse; // 平均脉搏
final int normalCount; // 正常记录数
final int elevatedCount; // 偏高记录数
final int highCount; // 高血压记录数
BloodPressureStats({
this.recordCount = 0,
this.avgSystolic = 0,
this.avgDiastolic = 0,
this.avgPulse = 0,
this.normalCount = 0,
this.elevatedCount = 0,
this.highCount = 0,
});
factory BloodPressureStats.empty() => BloodPressureStats();
bool get isEmpty => recordCount == 0;
// 计算各分类占比
double get normalPercentage =>
recordCount > 0 ? (normalCount / recordCount * 100) : 0;
double get elevatedPercentage =>
recordCount > 0 ? (elevatedCount / recordCount * 100) : 0;
double get highPercentage =>
recordCount > 0 ? (highCount / recordCount * 100) : 0;
}
五、在应用启动时初始化
// main.dart
import 'package:hive_flutter/hive_flutter.dart';
import 'services/blood_pressure_storage_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化 Hive
await Hive.initFlutter();
// 初始化血压存储服务
await BloodPressureStorageService.init();
runApp(const MyApp());
}
六、集成到页面中使用
class BloodPressurePage extends StatefulWidget {
State<BloodPressurePage> createState() => _BloodPressurePageState();
}
class _BloodPressurePageState extends State<BloodPressurePage> {
// 存储服务实例
final BloodPressureStorageService _storage = BloodPressureStorageService();
// 记录列表
List<BloodPressureRecord> _records = [];
void initState() {
super.initState();
// 加载记录
_loadRecords();
}
void _loadRecords() {
setState(() {
_records = _storage.getAllRecords();
});
}
// 保存记录
Future<void> _saveRecord(BloodPressureRecord record) async {
await _storage.saveRecord(record);
_loadRecords(); // 刷新列表
}
// 删除记录
Future<void> _deleteRecord(String id) async {
await _storage.deleteRecord(id);
_loadRecords(); // 刷新列表
}
}
七、开发踩坑与解决方案
7.1 踩坑一:适配器 ID 冲突 😱
问题描述:
运行时报错:Duplicate registration of type ID
原因:
多个模型使用了相同的适配器 ID。
解决方案:
为每个模型分配唯一的 ID:
// 不同模型使用不同的 ID
const int bloodPressureRecordTypeId = 100;
const int heartRateRecordTypeId = 101;
const int dietRecordTypeId = 102;
7.2 踩坑二:DateTime 序列化问题 🤔
问题描述:
存储的日期时间读取后不一致。
解决方案:
使用毫秒时间戳存储:
// 写入
writer.writeByte(1);
writer.write(obj.date.millisecondsSinceEpoch);
// 读取
date: DateTime.fromMillisecondsSinceEpoch(fields[1] as int),
7.3 踩坑三:Box 未打开 😅
问题描述:
报错 Box not opened
原因:
在 Hive.openBox 完成前就访问了数据。
解决方案:
确保在 main() 中等待初始化完成:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await BloodPressureStorageService.init(); // 等待完成
runApp(const MyApp());
}
八、最终实现效果

8.1 功能验证
| 功能 | 验证结果 |
|---|---|
| 数据保存 | ✅ 重启后数据保留 |
| 数据查询 | ✅ 按日期、月份查询正常 |
| 数据删除 | ✅ 删除后列表更新 |
| 统计分析 | ✅ 平均值计算正确 |
8.2 性能测试
| 操作 | 耗时 |
|---|---|
| 保存 100 条记录 | ~50ms |
| 查询 1000 条记录 | ~5ms |
| 删除单条记录 | ~1ms |
九、个人总结
9.1 学习心得
Hive 的使用真的很简单!相比 SQLite,Hive 的学习成本低了很多,而且性能也很不错 👍。
最大的收获是理解了 数据持久化的重要性:
- 内存数据:App 重启即丢失
- 持久化数据:永久保存
9.2 核心要点
- 适配器是关键:每个自定义类型都需要适配器
- ID 要唯一:避免适配器 ID 冲突
- 初始化顺序:先初始化 Hive,再使用服务
- 异常处理:存储操作要做好异常捕获
9.3 后续计划
数据持久化搞定了,接下来可以实现:
- 📅 table_calendar 月历打卡视图
- 📊 fl_chart 趋势图表
- 🔔 本地通知提醒
敬请期待!
创作日期:2026 年 4 月
版权所有,转载须注明出处
更多推荐

所有评论(0)