From e0fc60f9eb8bd93eb4f7e1ae857a45004d9199a4 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Fri, 17 Apr 2020 17:25:25 +0200 Subject: [PATCH] Run Moor within a background isolate This basically follows the guidelines at https://moor.simonbinder.eu/docs/advanced-features/isolates/. In the future we will be able to have multiple simultaneous database connections. --- build.yaml | 6 +++++ lib/backend.dart | 65 ++++++++++++++++++++++++++++++++++++++++++++++- lib/database.dart | 13 ++-------- 3 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 build.yaml diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..11e0038 --- /dev/null +++ b/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + moor_generator: + options: + generate_connect_constructor: true \ No newline at end of file diff --git a/lib/backend.dart b/lib/backend.dart index 6906ae6..8d8141c 100644 --- a/lib/backend.dart +++ b/lib/backend.dart @@ -1,10 +1,63 @@ +import 'dart:io'; +import 'dart:isolate'; + import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:moor/isolate.dart'; +import 'package:moor/moor.dart'; +import 'package:moor_ffi/moor_ffi.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart' as pp; import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'database.dart'; +// The following code was taken from +// https://moor.simonbinder.eu/docs/advanced-features/isolates/ and just +// slightly modified. + +Future _createMoorIsolate() async { + // This method is called from the main isolate. Since we can't use + // getApplicationDocumentsDirectory on a background isolate, we calculate + // the database path in the foreground isolate and then inform the + // background isolate about the path. + final dir = await pp.getApplicationDocumentsDirectory(); + final path = p.join(dir.path, 'db.sqlite'); + final receivePort = ReceivePort(); + + await Isolate.spawn( + _startBackground, + _IsolateStartRequest(receivePort.sendPort, path), + ); + + // _startBackground will send the MoorIsolate to this ReceivePort. + return (await receivePort.first as MoorIsolate); +} + +void _startBackground(_IsolateStartRequest request) { + // This is the entrypoint from the background isolate! Let's create + // the database from the path we received. + final executor = VmDatabase(File(request.targetPath)); + // We're using MoorIsolate.inCurrent here as this method already runs on a + // background isolate. If we used MoorIsolate.spawn, a third isolate would be + // started which is not what we want! + final moorIsolate = MoorIsolate.inCurrent( + () => DatabaseConnection.fromExecutor(executor), + ); + // Inform the starting isolate about this, so that it can call .connect(). + request.sendMoorIsolate.send(moorIsolate); +} + +// Used to bundle the SendPort and the target path, since isolate entrypoint +// functions can only take one parameter. +class _IsolateStartRequest { + final SendPort sendMoorIsolate; + final String targetPath; + + _IsolateStartRequest(this.sendMoorIsolate, this.targetPath); +} + enum BackendStatus { loading, setup, @@ -36,6 +89,7 @@ class BackendState extends State { Database db; String musicLibraryUri; + MoorIsolate _moorIsolate; SharedPreferences _shPref; @override @@ -53,7 +107,10 @@ class BackendState extends State { } Future _load() async { - db = Database('musicus.sqlite'); + _moorIsolate = await _createMoorIsolate(); + final dbConnection = await _moorIsolate.connect(); + db = Database.connect(dbConnection); + _shPref = await SharedPreferences.getInstance(); musicLibraryUri = _shPref.getString('musicLibraryUri'); @@ -110,6 +167,12 @@ class BackendState extends State { } } } + + @override + void dispose() { + super.dispose(); + _moorIsolate.shutdownAll(); + } } class _InheritedBackend extends InheritedWidget { diff --git a/lib/database.dart b/lib/database.dart index a2e9976..20e0999 100644 --- a/lib/database.dart +++ b/lib/database.dart @@ -1,10 +1,6 @@ -import 'dart:io'; import 'dart:math'; import 'package:moor/moor.dart'; -import 'package:moor_ffi/moor_ffi.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart' as pp; part 'database.g.dart'; @@ -38,13 +34,8 @@ class PerformanceModel { 'database.moor', }, ) -class Database extends _$Database { - Database(String fileName) - : super(LazyDatabase(() async { - final dir = await pp.getApplicationDocumentsDirectory(); - final file = File(p.join(dir.path, fileName)); - return VmDatabase(file); - })); +class Database extends _$Database { + Database.connect(DatabaseConnection connection) : super.connect(connection); @override int get schemaVersion => 1;