Add backup/restore functionality to example app

This commit is contained in:
Simon Binder 2023-02-08 00:35:55 +01:00
parent fe95c89720
commit d30d2a1212
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 118 additions and 11 deletions

View File

@ -60,7 +60,7 @@ LazyDatabase _openConnection() {
await file.writeAsBytes(buffer.asUint8List(blob.offsetInBytes, blob.lengthInBytes));
}
return NativeDatabase.createInBackground(file);;
return NativeDatabase.createInBackground(file);
});
}
```

View 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);
}));
}

View File

@ -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);

View File

@ -0,0 +1 @@
export 'unsupported.dart' if (dart.library.ffi) 'supported.dart';

View File

@ -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]);
}

View File

@ -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();
}
}

View File

@ -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),

View File

@ -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