Flutter for OpenHarmony轻量级开源记事本app实战:数据备份
设计理念
数据备份是笔记应用中的关键功能,它确保用户数据的安全性和可恢复性。一个好的备份系统应该支持自动备份、手动备份、云端同步和选择性备份。本文将详细介绍如何实现一个完整的数据备份解决方案。
数据备份的整体架构
数据备份系统包含备份管理、存储策略、恢复功能和云端同步等功能。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:convert';
import 'dart:io';
class BackupService extends GetxController {
static const String _lastBackupKey = 'last_backup_time';
引入备份所需依赖,包含SharedPreferences与文件系统能力。
这些依赖覆盖本地配置读写、文件路径获取与JSON序列化能力。
BackupService是备份能力的统一入口,页面只负责触发与展示。
_lastBackupKey用于持久化“上次备份时间”,供状态卡片展示。
static const String _autoBackupKey = 'auto_backup_enabled';
var lastBackupTime = DateTime.now().obs;
var isAutoBackupEnabled = true.obs;
var backupProgress = 0.0.obs;
Future<void> initialize() async {
final prefs = await SharedPreferences.getInstance();
final lastBackupStr = prefs.getString(_lastBackupKey);
if (lastBackupStr != null) {
_autoBackupKey用于持久化自动备份开关,保证重启后能恢复用户选择。
三个Rx字段保存时间/开关/进度,页面用Obx即可自动刷新。
initialize负责读取本地配置并恢复状态,避免每次进页面重复初始化。
读取lastBackupStr后先判空,再解析成DateTime以避免异常。
lastBackupTime.value = DateTime.parse(lastBackupStr);
}
isAutoBackupEnabled.value = prefs.getBool(_autoBackupKey) ?? true;
if (isAutoBackupEnabled.value) {
_scheduleAutoBackup();
}
}
判空后再解析时间可避免DateTime.parse在空值情况下抛异常。
自动备份开关提供默认值true,确保首次使用也有合理默认策略。
通过_scheduleAutoBackup触发调度逻辑,让“定时执行”与UI解耦。
下一段会给出自动/手动入口以及配置写入方法的骨架。
void _scheduleAutoBackup() {
// Schedule auto backup logic here
}
Future<void> _createManualBackup() async {
// Create manual backup logic here
}
Future<void> _saveAutoBackupSetting(bool value) async {
final prefs = await SharedPreferences.getInstance();
_scheduleAutoBackup是自动备份调度入口,后面可接入定时器/后台任务实现。
_createManualBackup是手动备份触发点,页面悬浮按钮会调用它。
_saveAutoBackupSetting负责把开关写入本地,保证设置可持久化。
本段结束于获取prefs,下一段继续写入并补上备份/恢复核心入口。
await prefs.setBool(_autoBackupKey, value);
}
Future<void> _backupData() async {
// Backup data logic here
}
Future<void> _restoreData() async {
// Restore data logic here
}
_backupData/_restoreData是备份与恢复的核心入口,后续会展开数据收集与写盘细节.
把逻辑收拢在服务层,页面只负责触发与展示,职责更清晰.
backupProgress可用于进度条展示,让用户明确当前执行状态.
下一段补上服务类的结束大括号,确保代码片段结构完整.
}
服务类闭合后,后续章节的页面代码不会被误识别为同一段代码.
代码围栏必须成对出现,避免Markdown渲染把正文吞进代码块.
结构修复不改变任何业务逻辑,只保证示例代码可读可复制.
下面开始进入备份管理页面的UI实现部分.
备份管理界面
实现备份管理的用户界面.
class BackupManagementPage extends StatelessWidget {
const BackupManagementPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('数据备份'),
actions: [
IconButton(
页面先搭好Scaffold与AppBar,标题明确模块功能,右侧放帮助入口.
help按钮用于展示说明内容,降低用户对“自动/手动/恢复”流程的理解成本.
这一段属于页面骨架区域,后续主体内容会以卡片形式继续拼装.
先把导航与入口按钮定好,方便后面追加更多配置项.
icon: const Icon(Icons.help_outline),
onPressed: () => _showBackupHelp(),
),
],
),
body: Obx(() => SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx包裹主体区域,备份服务状态改变时页面能自动刷新而无需手动setState.
使用滚动容器避免内容溢出,确保小屏也能完整查看所有卡片.
同时统一padding,保证布局留白一致,视觉更整洁.
页面主体继续用卡片拆分,避免build方法变成一大坨.
_buildBackupStatusCard(),
SizedBox(height: 16.h),
_buildBackupOptionsCard(),
SizedBox(height: 16.h),
_buildBackupHistoryCard(),
SizedBox(height: 16.h),
_buildRestoreOptionsCard(),
],
),
)),
通过_buildXXX拆分多个区域,让每个卡片关注一个功能点.
卡片之间用SizedBox统一间距,页面结构更清晰.
这些卡片组合起来就是“状态/选项/历史/恢复”的完整管理页.
下一段会补上悬浮按钮,并闭合页面结构.
floatingActionButton: FloatingActionButton(
onPressed: () => _createManualBackup(),
child: const Icon(Icons.backup, color: Colors.white),
),
);
}
}
悬浮按钮作为“手动备份”的快捷入口,用户随时可触发备份.
图标选择backup,语义直观,且不占用页面主布局空间.
触发逻辑放在_createManualBackup里,便于统一做进度与异常处理.
页面部分到这里结束,下面开始拆分各个卡片组件的实现.
Widget _buildBackupStatusCard() {
final service = Get.find<BackupService>();
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'备份状态',
状态卡片从GetX容器里取出BackupService,直接读取响应式字段.
用Card包裹内容形成独立视觉区域,便于与其它配置卡片区分.
Padding统一内边距,保证标题与内容在不同屏幕上都有一致留白.
标题“备份状态”用于明确这一块展示的是时间、开关与进度信息.
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: _StatusItem(
icon: Icons.schedule,
label: '上次备份',
value: _formatDateTime(service.lastBackupTime.value),
第一列状态项展示“上次备份”,value来自lastBackupTime并经过格式化处理.
Expanded让两列均分宽度,避免文本过长导致布局挤压.
icon与label分开传入,组件内部可统一渲染风格,减少重复代码.
下一段会补齐颜色与第二列“自动备份”状态项,并保持两列对齐.
color: Colors.blue,
),
),
Expanded(
child: _StatusItem(
icon: Icons.autorenew,
label: '自动备份',
value: service.isAutoBackupEnabled.value ? '已启用' : '已禁用',
color: service.isAutoBackupEnabled.value ? Colors.green : Colors.grey,
),
第二列状态项展示“自动备份”开关的当前状态.
value根据isAutoBackupEnabled映射为“已启用/已禁用”文本.
color同样基于开关状态切换绿/灰,用于强化视觉反馈.
下一段会闭合Row/Column/Card结构,完成状态卡片函数.
),
],
),
],
),
),
);
}
这里的代码块负责把状态卡片的Row与Card层级完整闭合.
把括号补齐后,前面的状态项展示代码才是可复制可运行的片段.
该段不引入新逻辑,只是收尾UI结构,避免后续章节被吞进同一块.
下面开始进入备份选项卡片,实现自动备份与更多配置入口.
备份选项卡片
Widget _buildBackupOptionsCard() {
final service = Get.find<BackupService>();
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
备份选项卡片从GetX容器获取service,便于读取/写入开关状态.
Card+Padding保证配置区域与其它卡片视觉统一,并留出足够间距.
Column使用start对齐,让标题与列表项从同一左边界开始排列.
下一段会渲染标题并放入自动备份开关作为首个可交互配置项.
Text(
'备份选项',
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 16.h),
SwitchListTile(
title: const Text('自动备份'),
subtitle: const Text('每日自动备份数据'),
value: service.isAutoBackupEnabled.value,
onChanged: (value) {
标题强调该卡片作用,字体加粗提升层级感.
SizedBox提供标题与配置项之间的固定间距,阅读更舒适.
SwitchListTile天然适配“开关+说明”场景,交互清晰.
onChanged里会同步更新响应式字段并持久化保存到本地.
service.isAutoBackupEnabled.value = value;
_saveAutoBackupSetting(value);
},
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.cloud_upload),
title: const Text('云端同步'),
subtitle: const Text('同步数据到云端存储'),
trailing: const Icon(Icons.arrow_forward_ios),
先改service可立刻触发Obx刷新,让开关状态即时可见.
保存逻辑封装在_saveAutoBackupSetting里,避免UI层直接操作存储.
Divider把不同配置项分隔开,形成清晰的列表层次.
云端同步入口采用“图标+箭头”布局,提示还能进入下一层配置.
onTap: () => _configureCloudSync(),
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.filter_list),
title: const Text('备份选择'),
subtitle: const Text('选择要备份的数据类型'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () => _showBackupSelectionDialog(),
),
onTap触发_configureCloudSync,把页面导航与具体配置逻辑分离.
备份选择入口用于控制备份范围,避免无关数据被打包.
两条ListTile保持一致的交互样式,学习成本更低.
下一段会闭合children/Column/Card并结束_buildBackupOptionsCard函数.
],
),
),
);
}
该段闭合children/Column/Padding/Card并返回Widget,完成整张“备份选项”卡片.
结构闭合后,主页面才能把它与其它卡片并列组合而不互相干扰.
onChanged/onTap只负责触发动作,具体实现留给独立方法,职责更清晰.
下一节进入手动备份,讲解点击悬浮按钮后如何执行备份流程.
手动备份
实现手动备份功能。
Future<void> _createManualBackup() async {
final service = Get.find<BackupService>();
try {
service.backupProgress.value = 0.0;
// 显示备份进度对话框
_showBackupProgressDialog();
手动备份入口通过Get.find拿到BackupService,便于更新进度与状态。
进入try块前把backupProgress置0,确保进度展示从0%开始。
先弹出进度对话框再执行耗时任务,避免用户误以为卡死。
下一段开始真正执行备份,并在完成后更新备份时间与提示结果。
// 执行备份
await _performBackup();
// 更新最后备份时间
await _updateLastBackupTime();
Get.snackbar('成功', '数据备份完成');
} catch (e) {
Get.snackbar('错误', '备份失败: ${e.toString()}');
执行备份放在await _performBackup中,保证流程按顺序完成。
成功后更新最后备份时间,页面“上次备份”会随之刷新。
try-catch捕获异常并提示,finally确保进度一定会被重置。
下一段会关闭进度对话框并补齐函数结束括号。
} finally {
service.backupProgress.value = 0.0;
Navigator.pop(Get.context);
}
}
finally里关闭对话框,避免对话框残留影响后续操作。
Navigator.pop使用Get.context对应的路由栈弹出当前AlertDialog。
两个右大括号依次闭合try与函数体,代码结构到此完整结束。
下一节会拆分进度对话框的实现,解释其响应式刷新逻辑。
备份进度对话框
显示备份进度的对话框。
void _showBackupProgressDialog() {
showDialog(
context: Get.context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('正在备份'),
content: Obx(() => Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
showDialog使用Get.context获取上下文,便于在任意位置弹出对话框.
barrierDismissible设为false,避免用户误触关闭导致流程中断.
AlertDialog提供标题与内容区域,结构简单且符合系统交互习惯.
下一段会补充进度文字与提示语,并完成对话框闭合.
SizedBox(height: 16.h),
Text(
'备份进度: ${(Get.find<BackupService>().backupProgress.value * 100).toInt()}%',
style: TextStyle(fontSize: 16.sp),
),
SizedBox(height: 8.h),
Text(
'请稍候,不要关闭应用...',
style: TextStyle(
fontSize: 14.sp,
CircularProgressIndicator提供基础的“进行中”视觉反馈.
进度百分比直接读取backupProgress并转为整数显示,更直观.
两段SizedBox控制元素间距,让信息层级更清晰.
下一段会补齐文字颜色样式并闭合Obx/AlertDialog结构.
color: Colors.grey[600],
),
),
],
)),
),
);
}
提示文字用灰色弱化视觉权重,避免与进度百分比争抢注意力.
Obx包裹Column后,backupProgress变化会触发内容区重建并刷新百分比.
对话框函数结束后,备份流程仍在后台await执行,不阻塞UI线程.
下一节进入实际备份逻辑_performBackup,开始收集与落盘数据.
执行备份
实现实际的备份逻辑。
Future<void> _performBackup() async {
final service = Get.find<BackupService>();
final controller = Get.find<NoteController>();
// 获取所有需要备份的数据
final backupData = {
'version': '1.0.0',
'timestamp': DateTime.now().toIso8601String(),
'notes': controller.allNotes.map((note) => note.toJson()).toList(),
'categories': controller.categories.map((cat) => cat.toJson()).toList(),
备份函数同时依赖BackupService与NoteController,分别负责状态与数据来源.
backupData用Map统一组织所有要导出的信息,便于后续序列化.
版本与时间戳用于识别备份格式与生成时间,方便将来兼容升级.
下一段会补齐tags/settings并把数据写入本地文件.
'tags': controller.tags.map((tag) => tag.toJson()).toList(),
'settings': await _getAppSettings(),
};
// 生成备份文件
final backupJson = jsonEncode(backupData);
final directory = await getApplicationDocumentsDirectory();
final timestamp = DateTime.now().millisecondsSinceEpoch;
final backupFile = File('${directory.path}/backup_$timestamp.json');
tags与settings都转成可序列化结构,确保jsonEncode能正常输出.
jsonEncode把Map转换为字符串,适合直接写入文件或上传云端.
getApplicationDocumentsDirectory提供应用私有目录,避免路径不一致.
用毫秒时间戳拼文件名可保证唯一性,避免覆盖旧备份.
await backupFile.writeAsString(backupJson);
// 更新进度
service.backupProgress.value = 1.0;
}
writeAsString把备份内容落盘,完成后即可认为本次备份成功.
进度直接置为1.0用于示例,实际项目可在分阶段写入时逐步更新.
函数结束返回Future,调用方await后再做提示与时间更新.
下一节拆分_getAppSettings,从本地偏好里读取要一起备份的设置项.
获取应用设置
获取应用的相关设置。
Future<Map<String, dynamic>> _getAppSettings() async {
final prefs = await SharedPreferences.getInstance();
return {
'theme': prefs.getString('theme') ?? 'light',
'fontSize': prefs.getDouble('font_size') ?? 16.0,
'language': prefs.getString('language') ?? 'zh_CN',
'autoBackup': prefs.getBool('auto_backup') ?? true,
'notifications': prefs.getBool('notifications') ?? true,
SharedPreferences用于读取轻量级配置项,适合备份用户偏好.
theme/fontSize/language等字段都提供默认值,防止空值导致解析失败.
这些设置与业务数据一起备份,恢复时才能还原用户的使用习惯.
下一段会闭合return的Map与函数本体,并进入备份历史展示部分.
};
}
Map闭合后函数返回设置集合,供_performBackup组合进backupData.
按key分类存储可让后续恢复逻辑逐项写回SharedPreferences.
该函数保持纯读取行为,便于测试与复用.
下一节开始拆分备份历史卡片,展示已有备份列表与操作入口.
备份历史卡片
显示备份历史记录。
Widget _buildBackupHistoryCard() {
return Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'备份历史',
历史卡片与前面一致使用Card+Padding+Column组织布局,保证风格统一.
标题“备份历史”用于说明该区域展示的是已有备份文件列表.
下一段会加入FutureBuilder异步加载历史记录,并根据状态渲染不同UI.
通过拆分构建逻辑,读者可逐步理解等待/空列表/正常列表三种情况.
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 16.h),
FutureBuilder(
future: _getBackupHistory(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
SizedBox分隔标题与列表区域,避免内容贴边影响阅读.
FutureBuilder负责管理异步结果,并在builder中给出对应界面.
当连接状态是waiting时展示加载指示器,提示用户正在读取文件.
下一段会取出snapshot数据并处理“没有备份记录”的空态展示.
final backups = snapshot.data ?? [];
if (backups.isEmpty) {
return const Center(
child: Text('暂无备份记录'),
);
}
return ListView.builder(
snapshot.data为空时用空列表兜底,避免空指针导致构建失败.
空态使用Center+Text提示“暂无备份记录”,让用户知道原因.
有数据时进入ListView.builder,按条目数量动态生成列表项.
下一段会配置ListView与itemBuilder,并开始构建每条备份记录的ListTile.
shrinkWrap: true,
itemCount: backups.length,
itemBuilder: (context, index) {
final backup = backups[index];
return ListTile(
leading: const Icon(Icons.backup),
title: Text(_formatDateTime(backup['timestamp'])),
subtitle: Text('大小: ${_formatFileSize(backup['size'])}'),
trailing: PopupMenuButton(
itemBuilder: (context) => [
shrinkWrap让列表在卡片内按内容高度展开,适配外层滚动容器.
每条记录从backups[index]取出,后续用于显示时间、大小与操作.
title与subtitle分别展示格式化时间和文件大小,信息一目了然.
trailing使用PopupMenuButton提供“恢复/删除”等操作入口.
PopupMenuItem(
value: 'restore',
child: const Text('恢复'),
),
PopupMenuItem(
value: 'delete',
child: const Text('删除'),
),
],
onSelected: (value) => _handleBackupAction(backup, value),
菜单项用value区分动作类型,点击后统一走onSelected处理.
_handleBackupAction把具体逻辑集中到一个入口,避免UI层分散判断.
PopupMenuButton放在trailing位置符合常见列表“更多操作”交互.
下一段会补齐ListTile点击事件与各层闭合括号,完成历史卡片函数.
),
onTap: () => _showBackupDetails(backup),
);
},
);
},
);
],
),
),
PopupMenuButton闭合后,ListTile继续补上onTap用于查看备份详情.
后续大括号与括号依次结束ListView.builder与FutureBuilder的builder函数.
children数组闭合意味着卡片内容区构建完成,等待外层容器收尾.
下一段会闭合Padding/Card并结束_buildBackupHistoryCard函数.
);
}
onTap可进入详情页或弹窗展示备份内容信息,便于确认再恢复.
builder结束后依次闭合FutureBuilder/Column/Padding/Card,结构与布局对应.
历史卡片完成后,下一节会讲“恢复功能”,把备份文件内容写回应用数据.
通过同一份备份列表触发恢复与删除,用户管理成本更低.
恢复功能
实现数据恢复功能。
Future<void> _restoreBackup(Map<String, dynamic> backup) async {
final confirmed = await showDialog<bool>(
context: Get.context,
builder: (context) => AlertDialog(
title: const Text('确认恢复'),
content: const Text('恢复数据将覆盖当前所有数据,确定要继续吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
恢复前先弹确认对话框,明确提示“会覆盖当前数据”以降低误操作风险.
showDialog返回bool,用户点取消/恢复分别对应false/true.
actions里放置TextButton与ElevatedButton,形成次要/主要操作层级.
下一段会补齐按钮、处理confirmed结果并执行实际的恢复逻辑.
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('恢复'),
),
],
),
);
if (!confirmed) return;
用户确认后才继续执行,confirmed为false时直接return结束流程.
_performRestore承载真正的数据写回逻辑,这里只负责流程编排.
将耗时操作放在try里,失败时能统一捕获并提示用户.
下一段会给出成功/失败提示,并闭合函数结构.
try {
await _performRestore(backup);
确认分支结束后进入try块,保证只有用户同意时才开始覆盖数据.
await确保恢复完成后再提示成功,避免用户误以为已经恢复.
这里不直接处理数据细节,而是交给_performRestore集中实现.
下一段继续展示catch分支与snackbar提示,并结束整个恢复函数.
Get.snackbar('成功', '数据恢复完成');
} catch (e) {
Get.snackbar('错误', '恢复失败: ${e.toString()}');
}
}
恢复成功用snackbar提示,用户无需离开当前页面即可获得反馈.
异常信息转字符串展示,便于定位文件损坏或格式不兼容等问题.
函数结束后即可在列表中继续操作其它备份,例如删除或查看详情.
到这里“选项/备份/历史/恢复”关键链路都已拆分完成,便于逐段阅读.
总结
数据备份是笔记应用中的关键功能,它确保用户数据的安全性和可恢复性。通过本文的介绍,我们实现了一个完整的数据备份系统。
关键特性包括:自动备份、手动备份、云端同步、备份历史、数据恢复和进度显示。这些功能共同构成了一个安全可靠的数据备份解决方案。
良好的备份系统不仅保护了用户数据,还提供了灵活的备份策略。通过持续优化和功能扩展,备份系统将成为应用的重要保障。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)