@@ -412,45 +412,165 @@ class Store {
412
412
return _runInTransaction (mode, (tx) => fn ());
413
413
}
414
414
415
- // Isolate entry point must be static or top-level.
415
+ /// Like [runAsync] , but executes [callback] within a read or write
416
+ /// transaction depending on [mode] .
417
+ ///
418
+ /// See the documentation on [runAsync] for important usage details.
419
+ ///
420
+ /// The following example gets the name of a User object, deletes the object
421
+ /// and returns the name within a write transaction:
422
+ /// ```dart
423
+ /// String? readNameAndRemove(Store store, int objectId) {
424
+ /// var box = store.box<User>();
425
+ /// final nameOrNull = box.get(objectId)?.name;
426
+ /// box.remove(objectId);
427
+ /// return nameOrNull;
428
+ /// }
429
+ /// await store.runInTransactionAsync(TxMode.write, readNameAndRemove, objectId);
430
+ /// ```
431
+ Future <R > runInTransactionAsync <R , P >(
432
+ TxMode mode, TxAsyncCallback <R , P > callback, P param) =>
433
+ runAsync (
434
+ (Store store, P p) =>
435
+ store.runInTransaction (mode, () => callback (store, p)),
436
+ param);
437
+
438
+ // Isolate entry point must be able to be sent via SendPort.send.
439
+ // Must guarantee only a single result event is sent.
440
+ // runAsync only handles a single event, any sent afterwards are ignored. E.g.
441
+ // in case [Error] or [Exception] are thrown after the result is sent.
416
442
static Future <void > _callFunctionWithStoreInIsolate <P , R >(
417
- _IsoPass <P , R > isoPass) async {
443
+ _RunAsyncIsolateConfig <P , R > isoPass) async {
418
444
final store = Store .attach (isoPass.model, isoPass.dbDirectoryPath,
419
445
queriesCaseSensitiveDefault: isoPass.queriesCaseSensitiveDefault);
420
- final result = await isoPass.runFn (store);
421
- store.close ();
422
- // Note: maybe replace with Isolate.exit (and remove kill call in
423
- // runIsolated) once min Dart SDK 2.15.
424
- isoPass.resultPort? .send (result);
446
+ dynamic result;
447
+ try {
448
+ final callbackResult = await isoPass.runCallback (store);
449
+ result = _RunAsyncResult (callbackResult);
450
+ } catch (error, stack) {
451
+ result = _RunAsyncError (error, stack);
452
+ } finally {
453
+ store.close ();
454
+ }
455
+
456
+ // Note: maybe replace with Isolate.exit (and remove kill() call in caller)
457
+ // once min Dart SDK 2.15.
458
+ isoPass.resultPort.send (result);
425
459
}
426
460
427
461
/// Spawns an isolate, runs [callback] in that isolate passing it [param] with
428
462
/// its own Store and returns the result of callback.
429
463
///
430
- /// Instances of [callback] must be top-level functions or static methods
431
- /// of classes, not closures or instance methods of objects.
464
+ /// This is useful for ObjectBox operations that take longer than a few
465
+ /// milliseconds, e.g. putting many objects, which would cause frame drops.
466
+ /// If all operations can execute within a single transaction, prefer to use
467
+ /// [runInTransactionAsync] .
468
+ ///
469
+ /// The following example gets the name of a User object, deletes the object
470
+ /// and returns the name:
471
+ /// ```dart
472
+ /// String? readNameAndRemove(Store store, int objectId) {
473
+ /// var box = store.box<User>();
474
+ /// final nameOrNull = box.get(objectId)?.name;
475
+ /// box.remove(objectId);
476
+ /// return nameOrNull;
477
+ /// }
478
+ /// await store.runAsync(readNameAndRemove, objectId);
479
+ /// ```
480
+ ///
481
+ /// The [callback] must be a function that can be sent to an isolate: either a
482
+ /// top-level function, static method or a closure that only captures objects
483
+ /// that can be sent to an isolate.
484
+ ///
485
+ /// Warning: Due to
486
+ /// [dart-lang/sdk#36983] (https://github.com/dart-lang/sdk/issues/36983) a
487
+ /// closure may capture more objects than expected, even if they are not
488
+ /// directly used in the closure itself.
489
+ ///
490
+ /// The types `P` (type of the parameter to be passed to the callback) and
491
+ /// `R` (type of the result returned by the callback) must be able to be sent
492
+ /// to or received from an isolate. The same applies to errors originating
493
+ /// from the callback.
494
+ ///
495
+ /// See [SendPort.send] for a discussion on which values can be sent to and
496
+ /// received from isolates.
432
497
///
433
498
/// Note: this requires Dart 2.15.0 or newer
434
499
/// (shipped with Flutter 2.8.0 or newer).
435
- Future <R > runIsolated <P , R >(
436
- TxMode mode, FutureOr <R > Function (Store , P ) callback, P param) async {
437
- final resultPort = ReceivePort ();
438
- // Await isolate spawn to avoid waiting forever if it fails to spawn.
439
- final isolate = await Isolate .spawn (
440
- _callFunctionWithStoreInIsolate,
441
- _IsoPass (_defs, directoryPath, _queriesCaseSensitiveDefault,
442
- resultPort.sendPort, callback, param));
443
- // Use Completer to return result so type is not lost.
444
- final result = Completer <R >();
445
- resultPort.listen ((dynamic message) {
446
- result.complete (message as R );
447
- });
448
- await result.future;
449
- resultPort.close ();
500
+ Future <R > runAsync <P , R >(RunAsyncCallback <P , R > callback, P param) async {
501
+ final port = RawReceivePort ();
502
+ final completer = Completer <dynamic >();
503
+
504
+ void _cleanup () {
505
+ port.close ();
506
+ }
507
+
508
+ port.handler = (dynamic message) {
509
+ _cleanup ();
510
+ completer.complete (message);
511
+ };
512
+
513
+ final Isolate isolate;
514
+ try {
515
+ // Await isolate spawn to avoid waiting forever if it fails to spawn.
516
+ isolate = await Isolate .spawn (
517
+ _callFunctionWithStoreInIsolate,
518
+ _RunAsyncIsolateConfig (_defs, directoryPath,
519
+ _queriesCaseSensitiveDefault, port.sendPort, callback, param),
520
+ errorsAreFatal: true ,
521
+ onError: port.sendPort,
522
+ onExit: port.sendPort);
523
+ } on Object {
524
+ _cleanup ();
525
+ rethrow ;
526
+ }
527
+
528
+ final dynamic response = await completer.future;
529
+ // Replace with Isolate.exit in _callFunctionWithStoreInIsolate
530
+ // once min SDK 2.15.
450
531
isolate.kill ();
451
- return result.future;
532
+
533
+ if (response == null ) {
534
+ throw RemoteError ('Isolate exited without result or error.' , '' );
535
+ }
536
+
537
+ if (response is _RunAsyncResult ) {
538
+ // Success, return result.
539
+ return response.result as R ;
540
+ } else if (response is List <dynamic >) {
541
+ // See isolate.addErrorListener docs for message structure.
542
+ assert (response.length == 2 );
543
+ await Future <Never >.error (RemoteError (
544
+ response[0 ] as String ,
545
+ response[1 ] as String ,
546
+ ));
547
+ } else {
548
+ // Error thrown by callback.
549
+ assert (response is _RunAsyncError );
550
+ response as _RunAsyncError ;
551
+
552
+ await Future <Never >.error (
553
+ response.error,
554
+ response.stack,
555
+ );
556
+ }
452
557
}
453
558
559
+ /// Deprecated. Use [runAsync] instead. Will be removed in a future release.
560
+ ///
561
+ /// Spawns an isolate, runs [callback] in that isolate passing it [param] with
562
+ /// its own Store and returns the result of callback.
563
+ ///
564
+ /// Instances of [callback] must be top-level functions or static methods
565
+ /// of classes, not closures or instance methods of objects.
566
+ ///
567
+ /// Note: this requires Dart 2.15.0 or newer
568
+ /// (shipped with Flutter 2.8.0 or newer).
569
+ @Deprecated ('Use `runAsync` instead. Will be removed in a future release.' )
570
+ Future <R > runIsolated <P , R >(TxMode mode,
571
+ FutureOr <R > Function (Store , P ) callback, P param) async =>
572
+ runAsync (callback, param);
573
+
454
574
/// Internal only - bypasses the main checks for async functions, you may
455
575
/// only pass synchronous callbacks!
456
576
R _runInTransaction <R >(TxMode mode, R Function (Transaction ) fn) {
@@ -571,10 +691,16 @@ final _openStoreDirectories = HashSet<String>();
571
691
final _nullSafetyEnabled = _nullReturningFn is ! Future Function ();
572
692
final _nullReturningFn = () => null ;
573
693
694
+ // Define type so IDE generates named parameters.
695
+ /// Signature for the callback passed to [Store.runAsync] .
696
+ ///
697
+ /// Instances must be functions that can be sent to an isolate.
698
+ typedef RunAsyncCallback <P , R > = FutureOr <R > Function (Store store, P parameter);
699
+
574
700
/// Captures everything required to create a "copy" of a store in an isolate
575
701
/// and run user code.
576
702
@immutable
577
- class _IsoPass <P , R > {
703
+ class _RunAsyncIsolateConfig <P , R > {
578
704
final ModelDefinition model;
579
705
580
706
/// Used to attach to store in separate isolate
@@ -584,15 +710,15 @@ class _IsoPass<P, R> {
584
710
final bool queriesCaseSensitiveDefault;
585
711
586
712
/// Non-void functions can use this port to receive the result.
587
- final SendPort ? resultPort;
713
+ final SendPort resultPort;
588
714
589
715
/// Parameter passed to [callback] .
590
716
final P param;
591
717
592
718
/// To be called in isolate.
593
- final FutureOr < R > Function ( Store , P ) callback;
719
+ final RunAsyncCallback < P , R > callback;
594
720
595
- const _IsoPass (
721
+ const _RunAsyncIsolateConfig (
596
722
this .model,
597
723
this .dbDirectoryPath,
598
724
// ignore: avoid_positional_boolean_parameters
@@ -603,5 +729,26 @@ class _IsoPass<P, R> {
603
729
604
730
/// Calls [callback] inside this class so types are not lost
605
731
/// (if called in isolate types would be dynamic instead of P and R).
606
- FutureOr <R > runFn (Store store) => callback (store, param);
732
+ FutureOr <R > runCallback (Store store) => callback (store, param);
733
+ }
734
+
735
+ @immutable
736
+ class _RunAsyncResult <R > {
737
+ final R result;
738
+
739
+ const _RunAsyncResult (this .result);
740
+ }
741
+
742
+ @immutable
743
+ class _RunAsyncError {
744
+ final Object error;
745
+ final StackTrace stack;
746
+
747
+ const _RunAsyncError (this .error, this .stack);
607
748
}
749
+
750
+ // Specify so IDE generates named parameters.
751
+ /// Signature for callback passed to [Store.runInTransactionAsync] .
752
+ ///
753
+ /// Instances must be functions that can be sent to an isolate.
754
+ typedef TxAsyncCallback <R , P > = R Function (Store store, P parameter);
0 commit comments