mirror of https://github.com/AMT-Cheif/drift.git
Add backup/restore functionality to example app
This commit is contained in:
parent
fe95c89720
commit
d30d2a1212
|
@ -60,7 +60,7 @@ LazyDatabase _openConnection() {
|
|||
await file.writeAsBytes(buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes));
|
||||
}
|
||||
|
||||
return NativeDatabase.createInBackground(file);;
|
||||
return NativeDatabase.createInBackground(file);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
|
|
@ -5,17 +5,16 @@ import 'package:drift/native.dart';
|
|||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Future<File> get databaseFile async {
|
||||
// We use `path_provider` to find a suitable path to store our data in.
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final dbPath = p.join(appDir.path, 'todos.db');
|
||||
return File(dbPath);
|
||||
}
|
||||
|
||||
/// Obtains a database connection for running drift in a Dart VM.
|
||||
DatabaseConnection connect() {
|
||||
return DatabaseConnection.delayed(Future(() async {
|
||||
// Background isolates can't use platform channels, so let's use
|
||||
// `path_provider` in the main isolate and just send the result containing
|
||||
// the path over to the background isolate.
|
||||
|
||||
// We use `path_provider` to find a suitable path to store our data in.
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final dbPath = p.join(appDir.path, 'todos.db');
|
||||
|
||||
return NativeDatabase.createBackgroundConnection(File(dbPath));
|
||||
return NativeDatabase.createBackgroundConnection(await databaseFile);
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class AppDatabase extends _$AppDatabase {
|
|||
});
|
||||
}
|
||||
|
||||
static Provider<AppDatabase> provider = Provider((ref) {
|
||||
static final StateProvider<AppDatabase> provider = StateProvider((ref) {
|
||||
final database = AppDatabase();
|
||||
ref.onDispose(database.close);
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export 'unsupported.dart' if (dart.library.ffi) 'supported.dart';
|
|
@ -0,0 +1,94 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:app/database/connection/native.dart';
|
||||
import 'package:app/database/database.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
class BackupIcon extends StatelessWidget {
|
||||
const BackupIcon({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () =>
|
||||
showDialog(context: context, builder: (_) => const BackupDialog()),
|
||||
icon: const Icon(Icons.save),
|
||||
tooltip: 'Backup',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BackupDialog extends ConsumerWidget {
|
||||
const BackupDialog({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return AlertDialog(
|
||||
title: const Text('Database backup'),
|
||||
content: const Text(
|
||||
'Here, you can save the database to a file or restore a created '
|
||||
'backup.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
createDatabaseBackup(ref.read(AppDatabase.provider));
|
||||
},
|
||||
child: const Text('Save'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final db = ref.read(AppDatabase.provider);
|
||||
await db.close();
|
||||
|
||||
// Open the selected database file
|
||||
final backupFile = await FilePicker.platform.pickFiles();
|
||||
if (backupFile == null) return;
|
||||
final backupDb = sqlite3.open(backupFile.files.single.path!);
|
||||
|
||||
// Vacuum it into a temporary location first to make sure it's working.
|
||||
final tempPath = await getTemporaryDirectory();
|
||||
final tempDb = p.join(tempPath.path, 'import.db');
|
||||
backupDb
|
||||
..execute('VACUUM INTO ?', [tempDb])
|
||||
..dispose();
|
||||
|
||||
// Then replace the existing database file with it.
|
||||
final tempDbFile = File(tempDb);
|
||||
await tempDbFile.copy((await databaseFile).path);
|
||||
await tempDbFile.delete();
|
||||
|
||||
// And now, re-open the database!
|
||||
ref.read(AppDatabase.provider.notifier).state = AppDatabase();
|
||||
},
|
||||
child: const Text('Restore'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createDatabaseBackup(DatabaseConnectionUser database) async {
|
||||
final choosenDirectory = await FilePicker.platform.getDirectoryPath();
|
||||
if (choosenDirectory == null) return;
|
||||
|
||||
final parent = Directory(choosenDirectory);
|
||||
final file = File(p.join(choosenDirectory, 'drift_example_backup.db'));
|
||||
|
||||
// Make sure the directory of the file exists
|
||||
if (!await parent.exists()) {
|
||||
await parent.create(recursive: true);
|
||||
}
|
||||
// However, the file itself must not exist
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
|
||||
await database.customStatement('VACUUM INTO ?', [file.absolute.path]);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class BackupIcon extends StatelessWidget {
|
||||
const BackupIcon({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../database/database.dart';
|
||||
import 'backup/backup.dart';
|
||||
import 'home/card.dart';
|
||||
import 'home/drawer.dart';
|
||||
import 'home/state.dart';
|
||||
|
@ -48,6 +49,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||
appBar: AppBar(
|
||||
title: const Text('Drift Todo list'),
|
||||
actions: [
|
||||
const BackupIcon(),
|
||||
IconButton(
|
||||
onPressed: () => context.go('/search'),
|
||||
icon: const Icon(Icons.search),
|
||||
|
|
|
@ -11,6 +11,7 @@ dependencies:
|
|||
flutter:
|
||||
sdk: flutter
|
||||
drift:
|
||||
file_picker: ^5.2.5
|
||||
flutter_colorpicker: ^1.0.3
|
||||
flutter_riverpod: ^1.0.3
|
||||
go_router: ^3.0.6
|
||||
|
|
Loading…
Reference in New Issue