Flutter数据备份服务插件lit_backup_service的使用
Flutter数据备份服务插件lit_backup_service的使用
LitBackupService 是一个 Flutter 包,允许你使用 JSON 文件创建和恢复备份。此包实现了一个非常简单的本地设备持久性(键值对)存储,应该仅用作次要存储解决方案(例如用于备份其他数据库)。
它包含一个抽象模型类,提供了如何定义可备份模型类的基本结构。因此,每个模型类的 JSON 序列化应单独执行,因为这些模型在结构上有所不同。
平台支持
Android |
---|
✔️ |
该插件目前仅支持 Android 设备。
截图
示例主屏幕 | 示例主屏幕 |
---|---|
![]() |
![]() |
Android 上所需的权限
为了在应用特定目录之外读写文件,必须授予额外的权限。这些权限必须在你的应用的 <code>AndroidManifest.xml</code>
中启用,位于 <code>android/app/src/main</code>
目录下。
<!-- Android 设备前于 Android 11 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Android 11 及以上版本可选 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
启用所需权限将通过 <code>BackupStorage</code>
的 <code>requestPermissions()</code>
方法完成,该方法利用了 <code>permission_handler</code>
包。
更近版本的 <code>Android</code>
对存储权限进行管理,基于文件用途,并有一些限制。要在 Android 10 及更高版本上访问先前安装创建的现有备份文件,必须授予 <code>MANAGE_EXTERNAL_STORAGE</code>
权限。由于文件权限更改,当前安装无法直接访问它们。这必须在 <code>BackupStorage</code>
的 <code>useManageExternalStoragePermission</code>
标志中指定。建议使用 <code>BackupStorage</code>
的 <code>pickBackupFile()</code>
方法来选择特定文件,而无需额外权限。
备份位置
备份默认存储在设备的媒体目录中。但支持自定义文件路径。媒体目录包括 <code>/storage/emulated/0/Documents/</code>
下的文档文件夹(默认)或 <code>/storage/emulated/0/Download/</code>
下的下载文件夹。
我们建议尽可能使用文档文件夹作为备份位置以提高安全性。
但对于更高的兼容性,我们建议使用 <code>Download</code>
文件夹,因为它几乎在所有 Android 设备上都可用。
开始使用
该项目是一个 Dart 包,包含可以在多个 Flutter 或 Dart 项目中轻松共享的库模块。
有关 Flutter 入门的帮助,请参阅我们的在线文档,其中提供了教程、示例、移动开发指南以及完整的 API 参考。
示例应用
为了更好地理解如何实现 LitBackupService,请查看 <code>example</code>
文件夹中的示例应用。请随意尝试该应用。
依赖项
LitBackupService 使用以下 Dart 依赖项来实现某些功能:
<a href="https://pub.dev/packages/permission_handler">permission_handler</a>
-<a href="https://github.com/Baseflow/flutter-permission-handler/blob/master/LICENSE" rel="ugc">许可证</a>
(用于请求存储访问权限)<a href="https://pub.dev/packages/file_picker">file_picker</a>
-<a href="https://github.com/miguelpruivo/flutter_file_picker/blob/master/LICENSE" rel="ugc">许可证</a>
(用于允许平台特定的文件选择)
致谢
LitBackupService 得到了 Flutter 项目的帮助。
许可证
本仓库中的所有内容(包括源代码)均根据 <strong>BSD 3-Clause</strong>
许可证分发,具体在 <code>LICENSE</code>
文件中说明。
示例代码
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:lit_backup_service/lit_backup_service.dart';
class ExampleBackup implements BackupModel {
final String name;
final String quote;
final String backupDate;
const ExampleBackup({
required this.name,
required this.quote,
required this.backupDate,
});
factory ExampleBackup.fromJson(Map<String, dynamic> json) {
return ExampleBackup(
name: json['name'] as String,
quote: json['quote'] as String,
backupDate: json['backupDate'] as String,
);
}
[@override](/user/override)
Map<String, dynamic> toJson() {
return {
'quote': quote,
'name': name,
'backupDate': backupDate,
};
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'LitBackupService',
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
[@override](/user/override)
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
bool _pressedPickFile = false;
TextEditingController _nameInput = TextEditingController(text: '');
TextEditingController _quoteInput = TextEditingController(text: 'If you want to be happy, be.');
ExampleBackup get _exampleBackup {
return ExampleBackup(
name: _nameInput.text,
quote: _quoteInput.text,
backupDate: DateTime.now().toIso8601String(),
);
}
String _formatAsLocalizedDate(BuildContext context, DateTime date) {
final TimeOfDay timeOfDay = TimeOfDay.fromDateTime(date);
final String dateFormat = MaterialLocalizations.of(context).formatShortDate(date);
final String timeFormat = MaterialLocalizations.of(context).formatTimeOfDay(timeOfDay);
return "$dateFormat $timeFormat";
}
void _onPressedPick() {
setState(() {
_pressedPickFile = !_pressedPickFile;
});
}
final BackupStorage _backupStorage = BackupStorage(
organizationName: "MyOrganization",
applicationName: "LitBackupService",
fileName: "examplebackup",
installationID: DateTime.now().millisecondsSinceEpoch.toRadixString(16),
);
Future<void> _writeBackup(ExampleBackup backup) async {
setState(() {
_backupStorage.writeBackup(backup);
});
}
Future<BackupModel?> _readBackup() {
return _backupStorage.readBackup(
decode: (contents) => ExampleBackup.fromJson(jsonDecode(contents)),
);
}
Future<void> _deleteBackup() async {
setState(() {
_backupStorage.deleteBackup();
});
}
Future<BackupModel?> _readBackupFromPicker() {
return _backupStorage.pickBackupFile(
decode: (contents) => ExampleBackup.fromJson(jsonDecode(contents)),
);
}
Future<void> _requestPermissions() async {
_backupStorage.requestPermissions().then((value) => setState(() {}));
}
[@override](/user/override)
void dispose() {
_pressedPickFile = false;
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (selected) {
setState(() {
_currentIndex = selected;
_pressedPickFile = false;
});
},
items: [
BottomNavigationBarItem(
label: "Restore",
icon: Icon(Icons.restore),
),
BottomNavigationBarItem(
label: "Create",
icon: Icon(Icons.create),
),
],
),
appBar: AppBar(
centerTitle: true,
backgroundColor: Colors.black,
title: Text("LitBackupService"),
),
body: _currentIndex == 0
? Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 16.0),
child: FutureBuilder(
future: _backupStorage.hasPermissions(),
builder: (context, AsyncSnapshot<bool> hasPerSnap) {
if (hasPerSnap.connectionState == ConnectionState.done && hasPerSnap.hasData) {
return hasPerSnap.data!
? Column(
children: [
ElevatedButton(
onPressed: _onPressedPick,
child: Text(_pressedPickFile ? "CLEAR" : "PICK BACKUP FILE"),
),
_pressedPickFile
? _BackupPreviewBuilder(
backupStorage: _backupStorage,
formatAsLocalizedDate: _formatAsLocalizedDate,
readBackup: _readBackupFromPicker(),
requestPermissions: _requestPermissions,
)
: SizedBox(),
],
)
: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text("Reading backup from storage denied."),
),
ElevatedButton(
onPressed: _requestPermissions,
child: Text("Request permissions"),
),
],
);
}
return CircularProgressIndicator();
},
),
),
),
)
: Scaffold(
body: SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 16.0),
child: Column(
children: [
_InputField(
title: "Your name",
controller: _nameInput,
),
_InputField(
title: "Your quote",
controller: _quoteInput,
),
ElevatedButton(
onPressed: () => _writeBackup(_exampleBackup),
child: Text("BACKUP NOW"),
),
_BackupPreviewBuilder(
backupStorage: _backupStorage,
formatAsLocalizedDate: _formatAsLocalizedDate,
readBackup: _readBackup(),
requestPermissions: _requestPermissions,
showMediaLocation: true,
),
FutureBuilder(
future: _backupStorage.hasPermissions(),
builder: (context, AsyncSnapshot<bool> hasPerSnap) {
return hasPerSnap.hasData
? hasPerSnap.data!
? ElevatedButton(
onPressed: _deleteBackup,
child: Text("DELETE BACKUP"),
)
: SizedBox()
: SizedBox();
},
),
],
),
),
),
),
),
);
}
}
class _BackupPreviewBuilder extends StatelessWidget {
final BackupStorage backupStorage;
final String Function(BuildContext context, DateTime datetime) formatAsLocalizedDate;
final Future<BackupModel?> readBackup;
final void Function() requestPermissions;
final bool showMediaLocation;
const _BackupPreviewBuilder({
Key? key,
required this.backupStorage,
required this.formatAsLocalizedDate,
required this.readBackup,
required this.requestPermissions,
this.showMediaLocation = false,
}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return FutureBuilder(
future: readBackup,
builder: (context, AsyncSnapshot<BackupModel?> snap) {
if (snap.connectionState == ConnectionState.done) {
if (snap.hasData) {
ExampleBackup? exampleBackup = snap.data as ExampleBackup;
return snap.data != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Backup of ${exampleBackup.name}"),
Text("Quote: ${exampleBackup.quote}"),
Text("Last backup: " + formatAsLocalizedDate(context, DateTime.parse(exampleBackup.backupDate))),
Padding(
padding: const EdgeInsets.symmetric(vertical: 30.0),
child: Text("You will find your JSON file on your selected Media directory of your local device."),
),
showMediaLocation
? FutureBuilder(
future: backupStorage.currentMediaPath,
builder: (context, AsyncSnapshot<String> snap) {
return snap.data != null
? Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Text("Current media directory:\n" + snap.data!),
)
: SizedBox();
},
)
: SizedBox()
],
)
: Text("No backup found!");
}
if (snap.hasError) return Text("Error");
} else if (snap.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// Permission denied or file not found.
return FutureBuilder(
future: backupStorage.hasPermissions(),
builder: (context, AsyncSnapshot<bool> hasPerSnap) {
return hasPerSnap.hasData
? !hasPerSnap.data!
? Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text("Reading backup from storage denied."),
),
ElevatedButton(
onPressed: requestPermissions,
child: Text("Request permissions"),
),
],
)
: Text("Backup not found!")
: SizedBox();
},
);
},
);
}
}
class _InputField extends StatelessWidget {
final String title;
final TextEditingController controller;
const _InputField({
Key? key,
required this.title,
required this.controller,
}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
children: [
Text(title),
TextField(
controller: controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter your $title...',
),
),
],
),
);
}
}
更多关于Flutter数据备份服务插件lit_backup_service的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter数据备份服务插件lit_backup_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用Flutter数据备份服务插件lit_backup_service
的示例代码案例。请注意,实际使用时你可能需要根据你的应用需求进行调整,并确保插件版本与Flutter SDK版本兼容。
首先,确保你已经在pubspec.yaml
文件中添加了lit_backup_service
插件的依赖:
dependencies:
flutter:
sdk: flutter
lit_backup_service: ^x.y.z # 请替换为实际版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Flutter项目中,你可以按照以下步骤来使用lit_backup_service
插件进行数据备份服务。
1. 导入插件
在你的Dart文件中导入插件:
import 'package:lit_backup_service/lit_backup_service.dart';
2. 初始化备份服务
在应用启动时初始化备份服务。通常,你可以在main.dart
的MyApp
类中执行此操作:
void main() {
WidgetsFlutterBinding.ensureInitialized();
// 初始化备份服务
LitBackupService.initialize();
runApp(MyApp());
}
3. 配置备份策略
在适当的时机(如用户登录或应用启动时),配置备份策略。例如,你可以指定要备份的数据路径和备份频率:
void configureBackupService() async {
// 定义备份策略
final backupStrategy = BackupStrategy(
paths: [
'/data/user/0/com.example.myapp/files/important_file.txt', // 替换为你的文件路径
],
frequency: BackupFrequency.daily, // 设置备份频率,如每日备份
);
// 配置并启动备份服务
await LitBackupService.configure(strategy: backupStrategy);
await LitBackupService.start();
}
4. 触发备份
你可以在应用中的特定操作(如用户点击备份按钮)时触发立即备份:
void triggerBackup() async {
try {
await LitBackupService.triggerBackup();
print('Backup triggered successfully.');
} catch (e) {
print('Failed to trigger backup: $e');
}
}
5. 监听备份状态
你可以监听备份服务的状态变化,以便向用户显示相应的UI反馈:
void listenToBackupServiceStatus() {
LitBackupService.backupStatusStream.listen((status) {
print('Backup status: $status');
// 根据状态更新UI,例如显示进度条或通知用户备份完成
});
}
6. 完整示例
以下是一个完整的示例,展示了如何在Flutter应用中使用lit_backup_service
插件:
import 'package:flutter/material.dart';
import 'package:lit_backup_service/lit_backup_service.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
LitBackupService.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Backup Service Example'),
),
body: BackupServicePage(),
),
);
}
}
class BackupServicePage extends StatefulWidget {
@override
_BackupServicePageState createState() => _BackupServicePageState();
}
class _BackupServicePageState extends State<BackupServicePage> {
@override
void initState() {
super.initState();
configureBackupService();
listenToBackupServiceStatus();
}
void configureBackupService() async {
final backupStrategy = BackupStrategy(
paths: [
'/data/user/0/com.example.myapp/files/important_file.txt',
],
frequency: BackupFrequency.daily,
);
await LitBackupService.configure(strategy: backupStrategy);
await LitBackupService.start();
}
void listenToBackupServiceStatus() {
LitBackupService.backupStatusStream.listen((status) {
print('Backup status: $status');
// 更新UI逻辑
});
}
void triggerBackup() async {
try {
await LitBackupService.triggerBackup();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Backup triggered successfully.'),
));
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Failed to trigger backup: $e'),
));
}
}
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: triggerBackup,
child: Text('Trigger Backup'),
),
);
}
}
请注意,上述代码中的文件路径/data/user/0/com.example.myapp/files/important_file.txt
仅作为示例,你需要根据你的应用实际情况进行调整。另外,lit_backup_service
插件的具体API可能会根据版本有所不同,请参考官方文档或插件的源代码以获取最新和最准确的API信息。