From 3ed735f294b30d0f158d33b74aa4ed85f8bb466c Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Wed, 25 Dec 2019 20:43:03 +0100 Subject: [PATCH 01/20] callbacks from C work --- lib/objectbox.dart | 1 + lib/src/bindings/bindings.dart | 11 +++++ lib/src/bindings/signatures.dart | 8 +++ test/observer_test.dart | 84 ++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 test/observer_test.dart diff --git a/lib/objectbox.dart b/lib/objectbox.dart index 7285a5013..10e8cf6a2 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -7,3 +7,4 @@ export "src/store.dart"; export "src/box.dart"; export "src/modelinfo/index.dart"; export "src/query/query.dart"; +export "src/bindings/bindings.dart"; diff --git a/lib/src/bindings/bindings.dart b/lib/src/bindings/bindings.dart index 46f032b92..fb0120b7e 100644 --- a/lib/src/bindings/bindings.dart +++ b/lib/src/bindings/bindings.dart @@ -128,6 +128,12 @@ class _ObjectBoxBindings { obx_query_visit_dart_t obx_query_visit; + // Observers + + obx_observe_t obx_observe; + obx_observe_single_type_t obx_observe_single_type; + obx_observer_close_t obx_observer_close; + // Utilities obx_bytes_array_t obx_bytes_array; obx_bytes_array_set_t obx_bytes_array_set; @@ -291,6 +297,11 @@ class _ObjectBoxBindings { obx_query_visit = _fn("obx_query_visit").asFunction(); + // observers + obx_observe = _fn("obx_observe").asFunction(); + obx_observe_single_type = _fn>("obx_observe_single_type").asFunction(); + obx_observer_close = _fn>("obx_observer_close").asFunction(); + // Utilities obx_bytes_array = _fn>("obx_bytes_array").asFunction(); obx_bytes_array_set = _fn>("obx_bytes_array_set").asFunction(); diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index a6ca625f6..ac18ec429 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -152,6 +152,14 @@ typedef obx_query_visit_native_t = Int32 Function( typedef obx_query_visit_dart_t = int Function( Pointer query, Pointer visitor, Pointer user_data, int offset, int limit); +// observers + +typedef obx_observer_t = U Function(Pointer user_data, Pointer entity_id, T type_ids_count); +typedef obx_observer_single_type_t = U Function(Pointer user_data); +typedef obx_observe_t = Pointer Function(Pointer store, Pointer>> callback, Pointer user_data); +typedef obx_observe_single_type_t = Pointer Function(Pointer store, T entity_id, Pointer>> callback, Pointer user_data); +typedef obx_observer_close_t = U Function(Pointer observer); + // Utilities typedef obx_bytes_array_t = Pointer Function(SizeT count); diff --git a/test/observer_test.dart b/test/observer_test.dart new file mode 100644 index 000000000..589d5a194 --- /dev/null +++ b/test/observer_test.dart @@ -0,0 +1,84 @@ +import "package:test/test.dart"; +import "package:objectbox/objectbox.dart"; +import "entity.dart"; +import 'test_env.dart'; +import 'objectbox.g.dart'; +import "dart:ffi"; +import "dart:io"; + +typedef obx_observer_t = U Function(Pointer user_data, Pointer entity_id, T type_ids_count); +typedef obx_observer_single_type_t = U Function(Pointer user_data); + +void writeToFile(String input) { + final file = File("observers-debug.txt"); + final sink = file.openWrite(mode:FileMode.APPEND); + sink.write("${new DateTime.now()} $input\n"); + + // Close the IOSink to free system resources. + sink.close(); +} + +void callbackSingleType(Pointer user_data) { + expect(user_data.address, 0); + writeToFile("callbackSingleType"); +} + +void callbackAnyType(Pointer user_data, Pointer entity_id, int type_ids_count) { + expect(user_data.address, 0); + expect(type_ids_count, 1); + writeToFile("callbackAnyType"); +} + +class Meh { + static void memberCallbackAnyType(Pointer user_data, Pointer entity_id, int type_ids_count) { + expect(user_data.address, 0); + expect(type_ids_count, 1); + writeToFile("memberCallbackAnyType"); + } +} + +void main() { + TestEnv env; + Box box; + Store store; + + final testEntityId = getObjectBoxModel().model.findEntityByName("TestEntity").id.id; + + final List simpleItems = + ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => + TestEntity(tString: s)).toList(); + + setUp(() { + env = TestEnv("observers"); + box = env.box; + store = env.store; + }); + + /// Non static function can't be used for ffi + // void callbackAnyTypeNonStatic(Pointer user_data, Pointer entity_id, int type_ids_count) { + // expect(user_data.address, 0); + // expect(type_ids_count, 1); + // } + + test("Trigger observer of any entity with class member callback", () { + final observer = bindings.obx_observe(store.ptr, Pointer.fromFunction>(Meh.memberCallbackAnyType), Pointer.fromAddress(0)); + box.putMany(simpleItems); + bindings.obx_observer_close(observer); + }); + + test("Trigger observer of any entity with static callback", () { + final observer = bindings.obx_observe(store.ptr, Pointer.fromFunction>(callbackAnyType), Pointer.fromAddress(0)); + box.putMany(simpleItems); + bindings.obx_observer_close(observer); + }); + + test("Trigger single entity observer", () { + final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, Pointer.fromFunction>(callbackSingleType), Pointer.fromAddress(0)); + box.putMany(simpleItems); + bindings.obx_observer_close(observer); + }); + + tearDown(() { + env.close(); + }); +} \ No newline at end of file From 8a6bc25f589281a7818a5d3d9930bfde7018dc0b Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Wed, 25 Dec 2019 22:22:56 +0100 Subject: [PATCH 02/20] clean up --- lib/objectbox.dart | 3 ++- test/observer_test.dart | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/objectbox.dart b/lib/objectbox.dart index 10e8cf6a2..c689d6909 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -7,4 +7,5 @@ export "src/store.dart"; export "src/box.dart"; export "src/modelinfo/index.dart"; export "src/query/query.dart"; -export "src/bindings/bindings.dart"; +export "src/bindings/bindings.dart"; // TODO remove after experiment +export "src/bindings/signatures.dart"; // TODO remove after experiment diff --git a/test/observer_test.dart b/test/observer_test.dart index 589d5a194..dde235df8 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -1,13 +1,10 @@ import "package:test/test.dart"; -import "package:objectbox/objectbox.dart"; import "entity.dart"; import 'test_env.dart'; import 'objectbox.g.dart'; import "dart:ffi"; import "dart:io"; - -typedef obx_observer_t = U Function(Pointer user_data, Pointer entity_id, T type_ids_count); -typedef obx_observer_single_type_t = U Function(Pointer user_data); +import "dart:async"; // TODO start to experiment with StreamController / yield / sink / Future void writeToFile(String input) { final file = File("observers-debug.txt"); @@ -60,21 +57,27 @@ void main() { // expect(type_ids_count, 1); // } - test("Trigger observer of any entity with class member callback", () { - final observer = bindings.obx_observe(store.ptr, Pointer.fromFunction>(Meh.memberCallbackAnyType), Pointer.fromAddress(0)); + test("Observe any entity with class member callback", () { + final callback = Pointer.fromFunction>(Meh.memberCallbackAnyType); + final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(0)); + simpleItems.forEach((i) => box.put(i)); box.putMany(simpleItems); bindings.obx_observer_close(observer); }); - test("Trigger observer of any entity with static callback", () { - final observer = bindings.obx_observe(store.ptr, Pointer.fromFunction>(callbackAnyType), Pointer.fromAddress(0)); + test("Observe any entity with static callback", () { + final callback = Pointer.fromFunction>(callbackAnyType); + final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(0)); + simpleItems.forEach((i) => box.put(i)); box.putMany(simpleItems); bindings.obx_observer_close(observer); }); - test("Trigger single entity observer", () { - final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, Pointer.fromFunction>(callbackSingleType), Pointer.fromAddress(0)); + test("Observe single entity", () { + final callback = Pointer.fromFunction>(callbackSingleType); + final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, callback, Pointer.fromAddress(0)); box.putMany(simpleItems); + simpleItems.forEach((i) => box.put(i)); bindings.obx_observer_close(observer); }); From a5d177e0fbec9384947833527f14179f5cd7a6e0 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Sun, 17 May 2020 03:09:51 +0200 Subject: [PATCH 03/20] fixed tests, now using futures --- test/observer_test.dart | 134 ++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 32 deletions(-) diff --git a/test/observer_test.dart b/test/observer_test.dart index dde235df8..1781eb10b 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -6,43 +6,89 @@ import "dart:ffi"; import "dart:io"; import "dart:async"; // TODO start to experiment with StreamController / yield / sink / Future -void writeToFile(String input) { - final file = File("observers-debug.txt"); - final sink = file.openWrite(mode:FileMode.APPEND); - sink.write("${new DateTime.now()} $input\n"); - - // Close the IOSink to free system resources. - sink.close(); -} +Pointer randomPtr = Pointer.fromAddress(1337); +Completer globalSingleCompleter = Completer(); +Completer globalAnyCompleter = Completer(); void callbackSingleType(Pointer user_data) { - expect(user_data.address, 0); - writeToFile("callbackSingleType"); + expect(user_data.address, randomPtr.address); + globalSingleCompleter.complete; } -void callbackAnyType(Pointer user_data, Pointer entity_id, int type_ids_count) { - expect(user_data.address, 0); - expect(type_ids_count, 1); - writeToFile("callbackAnyType"); +void callbackAnyType(Pointer user_data, Pointer mutated_idss, int mutated_count) { + expect(user_data.address, randomPtr.address); +// expect(mutated_ids.address, 0); // TODO +// expect(mutated_count, 1); // TODO size of the array at mutated_idss + globalAnyCompleter.complete; } -class Meh { - static void memberCallbackAnyType(Pointer user_data, Pointer entity_id, int type_ids_count) { - expect(user_data.address, 0); - expect(type_ids_count, 1); - writeToFile("memberCallbackAnyType"); +typedef Single = void Function(Pointer); +typedef Any = void Function(Pointer, Pointer, int); + +/** + * Initial idea, to support of whatever flavor of + * reactive dart library (rxdart / stream) + * user_data can be used to tag a callback function object + */ +class Observable /* extension Observable on Store... */ { + static Completer completer, singleCompleter; + + static Pointer singleObserver, anyObserver; + + static Single single; + static Any any; + + Store store; + + Observable.fromStore(this.store); + + static void _anyCallback(Pointer user_data, Pointer mutated_ids, int mutated_count) { + any(user_data, mutated_ids, mutated_count); + completer.complete; + } + + static void _singleCallback(Pointer user_data) { + single(user_data); + singleCompleter.complete; + } + + // TODO plugin rx/stream framework + // TODO allow multiple callbacks? + void observeSingleType(int entityId, Single fn, Pointer identifier) { + singleCompleter = Completer(); + single = fn; + final callback = Pointer.fromFunction>(_singleCallback); + singleObserver = bindings.obx_observe_single_type(store.ptr, entityId, callback, identifier); + } + + // TODO plugin rx/stream framework + // TODO allow >1 callbacks? + void observe(Any fn, Pointer identifier) { + completer = Completer(); + any = fn; + final callback = Pointer.fromFunction>(_anyCallback); + anyObserver = bindings.obx_observe(store.ptr, callback, identifier); + } + + Future singleComplete() async { + final willDispose = await singleCompleter.isCompleted; + bindings.obx_observer_close(singleObserver); + } + + Future anyComplete() async { + await completer.isCompleted; + bindings.obx_observer_close(anyObserver); } } -void main() { +void main() async { TestEnv env; Box box; Store store; final testEntityId = getObjectBoxModel().model.findEntityByName("TestEntity").id.id; - final List simpleItems = - ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => + final List simpleItems = ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => TestEntity(tString: s)).toList(); setUp(() { @@ -52,32 +98,56 @@ void main() { }); /// Non static function can't be used for ffi - // void callbackAnyTypeNonStatic(Pointer user_data, Pointer entity_id, int type_ids_count) { + // void callbackAnyTypeNonStatic(Pointer user_data, Pointer mutated_ids, int mutated_count) { // expect(user_data.address, 0); - // expect(type_ids_count, 1); + // expect(mutated_count, 1); // } - test("Observe any entity with class member callback", () { - final callback = Pointer.fromFunction>(Meh.memberCallbackAnyType); - final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(0)); + test("Observe any entity with class member callback", () async { + final o = Observable.fromStore(store); + var putCount = 0; + o.observe((Pointer user_data, Pointer mutated_ids, int mutated_count) { + expect(user_data.address, randomPtr.address); + print("test 1: $mutated_ids, $mutated_count"); + putCount++; +// expect(mutated_ids, TODO); +// expect(mutated_count, TODO); + }, randomPtr); simpleItems.forEach((i) => box.put(i)); box.putMany(simpleItems); - bindings.obx_observer_close(observer); + await o.anyComplete(); + expect(putCount, 7); + }); + + test("Observe a single entity with class member callback", () async { + final o = Observable.fromStore(store); + var putCount = 0; + o.observeSingleType(testEntityId, (Pointer user_data) { + print("test 2"); +// expect(user_data.address, equals(randomPtr.address)); // never fails + putCount++; + }, randomPtr); + simpleItems.forEach((i) => box.put(i)); + box.putMany(simpleItems); + await o.singleComplete(); + expect(putCount, 7); }); - test("Observe any entity with static callback", () { + test("Observe any entity with static callback", () async { final callback = Pointer.fromFunction>(callbackAnyType); - final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(0)); + final observer = bindings.obx_observe(store.ptr, callback, randomPtr); simpleItems.forEach((i) => box.put(i)); box.putMany(simpleItems); + await globalAnyCompleter.isCompleted; bindings.obx_observer_close(observer); }); - test("Observe single entity", () { + test("Observe single entity", () async { final callback = Pointer.fromFunction>(callbackSingleType); - final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, callback, Pointer.fromAddress(0)); + final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, callback, randomPtr); box.putMany(simpleItems); simpleItems.forEach((i) => box.put(i)); + await globalSingleCompleter.isCompleted; bindings.obx_observer_close(observer); }); From 372de28c593270d5d72ab41df5dcfeddf661602b Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Fri, 22 May 2020 14:36:41 +0200 Subject: [PATCH 04/20] Callbacks are pretty useless if we don't know which entity objects have changed, how did java/swift/go do this? --- lib/objectbox.dart | 4 +- test/observer_test.dart | 86 +++++++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/lib/objectbox.dart b/lib/objectbox.dart index c689d6909..6d1e222dc 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -6,6 +6,4 @@ export "src/model.dart"; export "src/store.dart"; export "src/box.dart"; export "src/modelinfo/index.dart"; -export "src/query/query.dart"; -export "src/bindings/bindings.dart"; // TODO remove after experiment -export "src/bindings/signatures.dart"; // TODO remove after experiment +export "src/query/query.dart"; \ No newline at end of file diff --git a/test/observer_test.dart b/test/observer_test.dart index 1781eb10b..6183df8ea 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -1,11 +1,15 @@ import "package:test/test.dart"; +import "package:objectbox/src/bindings/bindings.dart"; +import "package:objectbox/src/bindings/signatures.dart"; import "entity.dart"; +import "entity2.dart"; import 'test_env.dart'; import 'objectbox.g.dart'; import "dart:ffi"; import "dart:io"; -import "dart:async"; // TODO start to experiment with StreamController / yield / sink / Future +import "dart:async"; +// Pointer.fromAddress(0) does not fire at all Pointer randomPtr = Pointer.fromAddress(1337); Completer globalSingleCompleter = Completer(); Completer globalAnyCompleter = Completer(); @@ -15,19 +19,20 @@ void callbackSingleType(Pointer user_data) { globalSingleCompleter.complete; } -void callbackAnyType(Pointer user_data, Pointer mutated_idss, int mutated_count) { +void callbackAnyType(Pointer user_data, Pointer mutated_ids, int mutated_count) { expect(user_data.address, randomPtr.address); -// expect(mutated_ids.address, 0); // TODO -// expect(mutated_count, 1); // TODO size of the array at mutated_idss + for (var i=0; i); typedef Any = void Function(Pointer, Pointer, int); /** - * Initial idea, to support of whatever flavor of - * reactive dart library (rxdart / stream) + * Initial idea, to support streams * user_data can be used to tag a callback function object */ class Observable /* extension Observable on Store... */ { @@ -52,8 +57,6 @@ class Observable /* extension Observable on Store... */ { singleCompleter.complete; } - // TODO plugin rx/stream framework - // TODO allow multiple callbacks? void observeSingleType(int entityId, Single fn, Pointer identifier) { singleCompleter = Completer(); single = fn; @@ -61,8 +64,6 @@ class Observable /* extension Observable on Store... */ { singleObserver = bindings.obx_observe_single_type(store.ptr, entityId, callback, identifier); } - // TODO plugin rx/stream framework - // TODO allow >1 callbacks? void observe(Any fn, Pointer identifier) { completer = Completer(); any = fn; @@ -88,16 +89,20 @@ void main() async { final testEntityId = getObjectBoxModel().model.findEntityByName("TestEntity").id.id; - final List simpleItems = ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => + final List simpleStringItems = ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => TestEntity(tString: s)).toList(); + final List simpleNumberItems = [1,2,3,4,5,6].map((s) => + TestEntity(tInt: s)).toList(); + setUp(() { env = TestEnv("observers"); box = env.box; store = env.store; }); - /// Non static function can't be used for ffi + /// Non static function can't be used for ffi, but you can call a dynamic function + /// aka closure inside a static function // void callbackAnyTypeNonStatic(Pointer user_data, Pointer mutated_ids, int mutated_count) { // expect(user_data.address, 0); // expect(mutated_count, 1); @@ -108,15 +113,18 @@ void main() async { var putCount = 0; o.observe((Pointer user_data, Pointer mutated_ids, int mutated_count) { expect(user_data.address, randomPtr.address); - print("test 1: $mutated_ids, $mutated_count"); - putCount++; -// expect(mutated_ids, TODO); -// expect(mutated_count, TODO); + for (var i=0; i box.put(i)); - box.putMany(simpleItems); - await o.anyComplete(); - expect(putCount, 7); + + box.putMany(simpleStringItems); + simpleStringItems.forEach((i) => box.put(i)); + simpleNumberItems.forEach((i) => box.put(i)); + + await o.anyComplete(); // block, otherwise no results + expect(putCount, 13); }); test("Observe a single entity with class member callback", () async { @@ -124,20 +132,37 @@ void main() async { var putCount = 0; o.observeSingleType(testEntityId, (Pointer user_data) { print("test 2"); -// expect(user_data.address, equals(randomPtr.address)); // never fails putCount++; }, randomPtr); - simpleItems.forEach((i) => box.put(i)); - box.putMany(simpleItems); + + box.putMany(simpleStringItems); + simpleStringItems.forEach((i) => box.put(i)); + simpleNumberItems.forEach((i) => box.put(i)); + await o.singleComplete(); - expect(putCount, 7); + expect(putCount, 13); }); test("Observe any entity with static callback", () async { final callback = Pointer.fromFunction>(callbackAnyType); - final observer = bindings.obx_observe(store.ptr, callback, randomPtr); - simpleItems.forEach((i) => box.put(i)); - box.putMany(simpleItems); + final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(1337)); + + box.putMany(simpleStringItems); + + print('count: ${box.count()}'); + box.remove(1); + print('count: ${box.count()}'); + + // update value + final entity2 = box.get(2); + entity2.tString = "Dva"; + box.put(entity2); + + final box2 = Box(store); + box2.put(TestEntity2()); + box2.remove(1); + box2.put(TestEntity2()); + await globalAnyCompleter.isCompleted; bindings.obx_observer_close(observer); }); @@ -145,8 +170,11 @@ void main() async { test("Observe single entity", () async { final callback = Pointer.fromFunction>(callbackSingleType); final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, callback, randomPtr); - box.putMany(simpleItems); - simpleItems.forEach((i) => box.put(i)); + + box.putMany(simpleStringItems); + simpleStringItems.forEach((i) => box.put(i)); + simpleNumberItems.forEach((i) => box.put(i)); + await globalSingleCompleter.isCompleted; bindings.obx_observer_close(observer); }); From bd6f3ee4f4d47dea70d4d82c73c4581ff3963d98 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Fri, 17 Jul 2020 03:04:34 +0200 Subject: [PATCH 05/20] Some Stream tests won't work without an 'await', ffi blows up All the callbacks and streams work as expected tho. --- lib/src/observable.dart | 72 ++++++++++++++++++++++++++++++++++++++ lib/src/query/builder.dart | 2 +- lib/src/query/query.dart | 7 ++-- test/observer_test.dart | 55 +++++++---------------------- test/stream_test.dart | 67 +++++++++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 46 deletions(-) create mode 100644 lib/src/observable.dart create mode 100644 test/stream_test.dart diff --git a/lib/src/observable.dart b/lib/src/observable.dart new file mode 100644 index 000000000..afc1bd3e8 --- /dev/null +++ b/lib/src/observable.dart @@ -0,0 +1,72 @@ +import 'dart:async'; +import "dart:ffi"; +import "bindings/bindings.dart"; +import "bindings/signatures.dart"; + +import "store.dart"; +import "query/query.dart"; + +// ignore_for_file: non_constant_identifier_names + +// dart callback signature +typedef Any = void Function(Pointer, Pointer, int); + +class Observable { + + static final anyObserver = >{}; + static final any = {}; // radix? > tree? + + // sync:true -> ObjectBoxException: 10001 TX is not active anymore: #101 + static final controller = StreamController.broadcast(); + + static void _anyCallback(Pointer user_data, Pointer mutated_ids, int mutated_count) { + for(var i=0; i>(_anyCallback); + anyObserver[store.ptr.address] = bindings.obx_observe(store.ptr, callback, store.ptr); + } + + // #53 ffi:Pointer finalizer + static unsubscribe(Store store) { + bindings.obx_observer_close(anyObserver[store.ptr.address]); + } +} + +extension ObservableStore on Store { + subscribe () { Observable.subscribe(this); } + unsubscribe () { Observable.unsubscribe(this); } +} + +extension Streamable on Query { + _setup() { + if (!Observable.anyObserver.containsKey(this.store.ptr)) { + this.store.subscribe(); + } + + // Assume consensus on entityId over all available Stores + Observable.any[this.entityId] ??= (u, _, __) { + // dummy value to trigger an event + Observable.controller.add(u.address); + }; + } + + Stream> findStream({int offset = 0, int limit = 0}) { + _setup(); + return Observable.controller.stream + .map((_) => this.find(offset:offset, limit:limit)); + } + + /// Use this for Query Property + Stream> get stream { + _setup(); + return Observable.controller.stream + .map((_) => this); + } +} \ No newline at end of file diff --git a/lib/src/query/builder.dart b/lib/src/query/builder.dart index 4cad2532e..d3b1b6720 100644 --- a/lib/src/query/builder.dart +++ b/lib/src/query/builder.dart @@ -27,7 +27,7 @@ class QueryBuilder { } try { - return Query._(_store, _fbManager, _cBuilder); + return Query._(_store, _fbManager, _cBuilder, _entityId); } finally { checkObx(bindings.obx_qb_close(_cBuilder)); } diff --git a/lib/src/query/query.dart b/lib/src/query/query.dart index 34459f555..c92ab429a 100644 --- a/lib/src/query/query.dart +++ b/lib/src/query/query.dart @@ -533,11 +533,12 @@ class ConditionGroupAll extends ConditionGroup { class Query { Pointer _cQuery; - Store _store; + Store store; OBXFlatbuffersManager _fbManager; + int entityId; // package private ctor - Query._(this._store, this._fbManager, Pointer cBuilder) { + Query._(this.store, this._fbManager, Pointer cBuilder, this.entityId) { _cQuery = checkObxPtr(bindings.obx_query_create(cBuilder), "create query"); } @@ -572,7 +573,7 @@ class Query { } List find({int offset = 0, int limit = 0}) { - return _store.runInTransaction(TxMode.Read, () { + return store.runInTransaction(TxMode.Read, () { if (bindings.obx_supports_bytes_array() == 1) { final bytesArray = checkObxPtr(bindings.obx_query_find(_cQuery, offset, limit), "find"); try { diff --git a/test/observer_test.dart b/test/observer_test.dart index 6183df8ea..7aec66ae2 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -6,38 +6,29 @@ import "entity2.dart"; import 'test_env.dart'; import 'objectbox.g.dart'; import "dart:ffi"; -import "dart:io"; -import "dart:async"; -// Pointer.fromAddress(0) does not fire at all +// ignore_for_file: non_constant_identifier_names + +/// Pointer.fromAddress(0) does not fire at all Pointer randomPtr = Pointer.fromAddress(1337); -Completer globalSingleCompleter = Completer(); -Completer globalAnyCompleter = Completer(); +var callbackSingleTypeCounter = 0; void callbackSingleType(Pointer user_data) { expect(user_data.address, randomPtr.address); - globalSingleCompleter.complete; + callbackSingleTypeCounter++; } +var callbackAnyTypeCounter = 0; void callbackAnyType(Pointer user_data, Pointer mutated_ids, int mutated_count) { expect(user_data.address, randomPtr.address); - for (var i=0; i); typedef Any = void Function(Pointer, Pointer, int); -/** - * Initial idea, to support streams - * user_data can be used to tag a callback function object - */ -class Observable /* extension Observable on Store... */ { - static Completer completer, singleCompleter; - +class Observable { static Pointer singleObserver, anyObserver; static Single single; @@ -49,37 +40,23 @@ class Observable /* extension Observable on Store... */ { static void _anyCallback(Pointer user_data, Pointer mutated_ids, int mutated_count) { any(user_data, mutated_ids, mutated_count); - completer.complete; } static void _singleCallback(Pointer user_data) { single(user_data); - singleCompleter.complete; } void observeSingleType(int entityId, Single fn, Pointer identifier) { - singleCompleter = Completer(); single = fn; final callback = Pointer.fromFunction>(_singleCallback); singleObserver = bindings.obx_observe_single_type(store.ptr, entityId, callback, identifier); } void observe(Any fn, Pointer identifier) { - completer = Completer(); any = fn; final callback = Pointer.fromFunction>(_anyCallback); anyObserver = bindings.obx_observe(store.ptr, callback, identifier); } - - Future singleComplete() async { - final willDispose = await singleCompleter.isCompleted; - bindings.obx_observer_close(singleObserver); - } - - Future anyComplete() async { - await completer.isCompleted; - bindings.obx_observer_close(anyObserver); - } } void main() async { @@ -113,17 +90,14 @@ void main() async { var putCount = 0; o.observe((Pointer user_data, Pointer mutated_ids, int mutated_count) { expect(user_data.address, randomPtr.address); - for (var i=0; i box.put(i)); simpleNumberItems.forEach((i) => box.put(i)); - await o.anyComplete(); // block, otherwise no results + bindings.obx_observer_close(Observable.anyObserver); expect(putCount, 13); }); @@ -131,7 +105,6 @@ void main() async { final o = Observable.fromStore(store); var putCount = 0; o.observeSingleType(testEntityId, (Pointer user_data) { - print("test 2"); putCount++; }, randomPtr); @@ -139,7 +112,7 @@ void main() async { simpleStringItems.forEach((i) => box.put(i)); simpleNumberItems.forEach((i) => box.put(i)); - await o.singleComplete(); + bindings.obx_observer_close(Observable.singleObserver); expect(putCount, 13); }); @@ -149,9 +122,7 @@ void main() async { box.putMany(simpleStringItems); - print('count: ${box.count()}'); box.remove(1); - print('count: ${box.count()}'); // update value final entity2 = box.get(2); @@ -163,7 +134,7 @@ void main() async { box2.remove(1); box2.put(TestEntity2()); - await globalAnyCompleter.isCompleted; + expect(callbackAnyTypeCounter, 6); bindings.obx_observer_close(observer); }); @@ -175,7 +146,7 @@ void main() async { simpleStringItems.forEach((i) => box.put(i)); simpleNumberItems.forEach((i) => box.put(i)); - await globalSingleCompleter.isCompleted; + expect(callbackSingleTypeCounter, 13); bindings.obx_observer_close(observer); }); diff --git a/test/stream_test.dart b/test/stream_test.dart new file mode 100644 index 000000000..a3509f624 --- /dev/null +++ b/test/stream_test.dart @@ -0,0 +1,67 @@ +import "package:test/test.dart"; +import "dart:async"; +import "entity.dart"; +import 'test_env.dart'; +import 'objectbox.g.dart'; +import "package:objectbox/src/observable.dart"; + +// ignore_for_file: non_constant_identifier_names + +void main() { + TestEnv env; + Box box; + + setUp(() { + env = TestEnv("streams"); + box = env.box; + }); + + test("Subscribe to stream of entities", () async { + + final result = []; + final text = TestEntity_.tString; + final condition = text.notNull(); + final query = box.query(condition).order(text).build(); + final queryStream = query.findStream(); + final subscription = queryStream.listen((list) { + final str = list.map((t) => t.tString).toList().join(', '); + result.add(str); + }); + + box.put(TestEntity(tString: "Hello world")); + box.putMany([ TestEntity(tString: "Goodbye"), + TestEntity(tString: "for now") ] as List); + + await Future.delayed(Duration(seconds: 0)); // ffi explodes without + expect(result, + ["for now, Goodbye, Hello world", "for now, Goodbye, Hello world"]); + + subscription.cancel(); + }); + + test("Subscribe to stream of query", () async { + + final result = []; + final text = TestEntity_.tString; + final condition = text.notNull(); + final query = box.query(condition).order(text).build(); + final queryStream = query.stream; + final subscription = queryStream.listen((query) { + result.add(query.count()); + }); + + box.put(TestEntity(tString: "Hello world")); + box.putMany([ TestEntity(tString: "Goodbye"), + TestEntity(tString: "for now") ] as List); + + await Future.delayed(Duration(seconds: 0)); // ffi explodes without + expect(result, [3, 3]); + + subscription.cancel(); + }); + + tearDown(() { + env.store.unsubscribe(); + env.close(); + }); +} \ No newline at end of file From 70a038cd2bea8408746bcab9068c8894f4d41462 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Fri, 17 Jul 2020 05:26:34 +0200 Subject: [PATCH 06/20] improve sub and unsub behavior --- lib/src/observable.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/observable.dart b/lib/src/observable.dart index afc1bd3e8..d28d8114c 100644 --- a/lib/src/observable.dart +++ b/lib/src/observable.dart @@ -35,7 +35,11 @@ class Observable { // #53 ffi:Pointer finalizer static unsubscribe(Store store) { + if (!anyObserver.containsKey(store.ptr.address)) { + return; + } bindings.obx_observer_close(anyObserver[store.ptr.address]); + anyObserver.remove(store.ptr.address); } } From b3e5fda8413fa4e41f69d0e6448a9ae98c930747 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Fri, 17 Jul 2020 05:33:06 +0200 Subject: [PATCH 07/20] update readme for streams --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index d2931d763..98ef9cd0e 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,37 @@ scoreQuery.close(); query.close(); ``` +### Streams + +Streams can be created from queries. +The streams can be extended with [rxdart](https://github.com/ReactiveX/rxdart); + +```dart + import "package:objectbox/src/observable.dart"; + + // final store = ... + final query = box.query(condition).build(); + final queryStream = query.stream; + final sub1 = queryStream.listen((query) { + print(query.count()); + }); + + // box.put ... + + sub1.cancel(); + + final stream = query.findStream(limit:5); + final sub2 = stream.listen((list) { + // ... + }); + + // clean up + sub2.cancel(); + store.unsubscribe(); + + store.close(); +``` + Help wanted ----------- ObjectBox for Dart is still in an early stage with limited feature set (compared to other languages). From 113fb5516eadf2acd56dc3bebb425631ce35f9db Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Thu, 30 Jul 2020 13:47:16 +0200 Subject: [PATCH 08/20] removed double quotes, also unnecessary "this" --- lib/src/observable.dart | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/src/observable.dart b/lib/src/observable.dart index d28d8114c..44db97a96 100644 --- a/lib/src/observable.dart +++ b/lib/src/observable.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import "dart:ffi"; -import "bindings/bindings.dart"; -import "bindings/signatures.dart"; +import 'dart:ffi'; +import 'bindings/bindings.dart'; +import 'bindings/signatures.dart'; -import "store.dart"; -import "query/query.dart"; +import 'store.dart'; +import 'query/query.dart'; // ignore_for_file: non_constant_identifier_names @@ -28,13 +28,13 @@ class Observable { } } - static subscribe(Store store) { + static void subscribe(Store store) { final callback = Pointer.fromFunction>(_anyCallback); anyObserver[store.ptr.address] = bindings.obx_observe(store.ptr, callback, store.ptr); } // #53 ffi:Pointer finalizer - static unsubscribe(Store store) { + static void unsubscribe(Store store) { if (!anyObserver.containsKey(store.ptr.address)) { return; } @@ -44,18 +44,18 @@ class Observable { } extension ObservableStore on Store { - subscribe () { Observable.subscribe(this); } - unsubscribe () { Observable.unsubscribe(this); } + void subscribe () { Observable.subscribe(this); } + void unsubscribe () { Observable.unsubscribe(this); } } extension Streamable on Query { - _setup() { - if (!Observable.anyObserver.containsKey(this.store.ptr)) { - this.store.subscribe(); + void _setup() { + if (!Observable.anyObserver.containsKey(store.ptr)) { + store.subscribe(); } // Assume consensus on entityId over all available Stores - Observable.any[this.entityId] ??= (u, _, __) { + Observable.any[entityId] ??= (u, _, __) { // dummy value to trigger an event Observable.controller.add(u.address); }; @@ -64,7 +64,7 @@ extension Streamable on Query { Stream> findStream({int offset = 0, int limit = 0}) { _setup(); return Observable.controller.stream - .map((_) => this.find(offset:offset, limit:limit)); + .map((_) => find(offset:offset, limit:limit)); } /// Use this for Query Property From 3a6184d7b103b6efb975c21c955c23de3f1e98a2 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Thu, 24 Sep 2020 18:44:31 +0200 Subject: [PATCH 09/20] removed superfluous extraneous redundant parameterized typedefs --- lib/src/bindings/signatures.dart | 4 ++-- lib/src/observable.dart | 2 +- test/observer_test.dart | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index 76277fe58..daacc01bc 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -161,9 +161,9 @@ typedef obx_query_visit_dart_t = int Function(Pointer query, // observers -typedef obx_observer_t = U Function(Pointer user_data, Pointer entity_id, T type_ids_count); +typedef obx_observer_t = Void Function(Pointer user_data, Pointer entity_id, Uint32 type_ids_count); typedef obx_observer_single_type_t = U Function(Pointer user_data); -typedef obx_observe_t = Pointer Function(Pointer store, Pointer>> callback, Pointer user_data); +typedef obx_observe_t = Pointer Function(Pointer store, Pointer> callback, Pointer user_data); typedef obx_observe_single_type_t = Pointer Function(Pointer store, T entity_id, Pointer>> callback, Pointer user_data); typedef obx_observer_close_t = U Function(Pointer observer); diff --git a/lib/src/observable.dart b/lib/src/observable.dart index 44db97a96..edf4027bf 100644 --- a/lib/src/observable.dart +++ b/lib/src/observable.dart @@ -29,7 +29,7 @@ class Observable { } static void subscribe(Store store) { - final callback = Pointer.fromFunction>(_anyCallback); + final callback = Pointer.fromFunction(_anyCallback); anyObserver[store.ptr.address] = bindings.obx_observe(store.ptr, callback, store.ptr); } diff --git a/test/observer_test.dart b/test/observer_test.dart index 7aec66ae2..b65d8fa52 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -54,7 +54,7 @@ class Observable { void observe(Any fn, Pointer identifier) { any = fn; - final callback = Pointer.fromFunction>(_anyCallback); + final callback = Pointer.fromFunction(_anyCallback); anyObserver = bindings.obx_observe(store.ptr, callback, identifier); } } @@ -117,7 +117,7 @@ void main() async { }); test("Observe any entity with static callback", () async { - final callback = Pointer.fromFunction>(callbackAnyType); + final callback = Pointer.fromFunction(callbackAnyType); final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(1337)); box.putMany(simpleStringItems); From e00bdfd63eef8cdb1329ff4c593740420e431238 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Sat, 26 Sep 2020 21:19:59 +0200 Subject: [PATCH 10/20] double quotes to single --- lib/src/bindings/bindings.dart | 8 ++++---- lib/src/bindings/signatures.dart | 3 ++- test/observer_test.dart | 28 ++++++++++++++-------------- test/stream_test.dart | 32 ++++++++++++++++---------------- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/lib/src/bindings/bindings.dart b/lib/src/bindings/bindings.dart index 0523ed3a2..c6d6d46ed 100644 --- a/lib/src/bindings/bindings.dart +++ b/lib/src/bindings/bindings.dart @@ -162,7 +162,7 @@ class _ObjectBoxBindings { // Observers obx_observe_t obx_observe; obx_observe_single_type_t obx_observe_single_type; - obx_observer_close_t obx_observer_close; + obx_observer_close_dart_t obx_observer_close; // query property obx_query_prop_t obx_query_prop; @@ -480,9 +480,9 @@ class _ObjectBoxBindings { _fn('obx_query_visit').asFunction(); // observers - obx_observe = _fn("obx_observe").asFunction(); - obx_observe_single_type = _fn>("obx_observe_single_type").asFunction(); - obx_observer_close = _fn>("obx_observer_close").asFunction(); + obx_observe = _fn('obx_observe').asFunction(); + obx_observe_single_type = _fn>('obx_observe_single_type').asFunction(); + obx_observer_close = _fn('obx_observer_close').asFunction(); // query property obx_query_prop = diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index f32040560..dbf5753ce 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -230,7 +230,8 @@ typedef obx_observer_t = Void Function(Pointer user_data, Pointer typedef obx_observer_single_type_t = U Function(Pointer user_data); typedef obx_observe_t = Pointer Function(Pointer store, Pointer> callback, Pointer user_data); typedef obx_observe_single_type_t = Pointer Function(Pointer store, T entity_id, Pointer>> callback, Pointer user_data); -typedef obx_observer_close_t = U Function(Pointer observer); +typedef obx_observer_close_native_t = Void Function(Pointer observer); +typedef obx_observer_close_dart_t = void Function(Pointer observer); // query property diff --git a/test/observer_test.dart b/test/observer_test.dart index b65d8fa52..ed35c0949 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -1,11 +1,11 @@ -import "package:test/test.dart"; -import "package:objectbox/src/bindings/bindings.dart"; -import "package:objectbox/src/bindings/signatures.dart"; -import "entity.dart"; -import "entity2.dart"; +import 'package:test/test.dart'; +import 'package:objectbox/src/bindings/bindings.dart'; +import 'package:objectbox/src/bindings/signatures.dart'; +import 'entity.dart'; +import 'entity2.dart'; import 'test_env.dart'; import 'objectbox.g.dart'; -import "dart:ffi"; +import 'dart:ffi'; // ignore_for_file: non_constant_identifier_names @@ -64,16 +64,16 @@ void main() async { Box box; Store store; - final testEntityId = getObjectBoxModel().model.findEntityByName("TestEntity").id.id; + final testEntityId = getObjectBoxModel().model.findEntityByName('TestEntity').id.id; - final List simpleStringItems = ["One", "Two", "Three", "Four", "Five", "Six"].map((s) => + final List simpleStringItems = ['One', 'Two', 'Three', 'Four', 'Five', 'Six'].map((s) => TestEntity(tString: s)).toList(); final List simpleNumberItems = [1,2,3,4,5,6].map((s) => TestEntity(tInt: s)).toList(); setUp(() { - env = TestEnv("observers"); + env = TestEnv('observers'); box = env.box; store = env.store; }); @@ -85,7 +85,7 @@ void main() async { // expect(mutated_count, 1); // } - test("Observe any entity with class member callback", () async { + test('Observe any entity with class member callback', () async { final o = Observable.fromStore(store); var putCount = 0; o.observe((Pointer user_data, Pointer mutated_ids, int mutated_count) { @@ -101,7 +101,7 @@ void main() async { expect(putCount, 13); }); - test("Observe a single entity with class member callback", () async { + test('Observe a single entity with class member callback', () async { final o = Observable.fromStore(store); var putCount = 0; o.observeSingleType(testEntityId, (Pointer user_data) { @@ -116,7 +116,7 @@ void main() async { expect(putCount, 13); }); - test("Observe any entity with static callback", () async { + test('Observe any entity with static callback', () async { final callback = Pointer.fromFunction(callbackAnyType); final observer = bindings.obx_observe(store.ptr, callback, Pointer.fromAddress(1337)); @@ -126,7 +126,7 @@ void main() async { // update value final entity2 = box.get(2); - entity2.tString = "Dva"; + entity2.tString = 'Dva'; box.put(entity2); final box2 = Box(store); @@ -138,7 +138,7 @@ void main() async { bindings.obx_observer_close(observer); }); - test("Observe single entity", () async { + test('Observe single entity', () async { final callback = Pointer.fromFunction>(callbackSingleType); final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, callback, randomPtr); diff --git a/test/stream_test.dart b/test/stream_test.dart index a3509f624..878a41177 100644 --- a/test/stream_test.dart +++ b/test/stream_test.dart @@ -1,9 +1,9 @@ -import "package:test/test.dart"; -import "dart:async"; -import "entity.dart"; +import 'package:test/test.dart'; +import 'dart:async'; +import 'entity.dart'; import 'test_env.dart'; import 'objectbox.g.dart'; -import "package:objectbox/src/observable.dart"; +import 'package:objectbox/src/observable.dart'; // ignore_for_file: non_constant_identifier_names @@ -12,11 +12,11 @@ void main() { Box box; setUp(() { - env = TestEnv("streams"); + env = TestEnv('streams'); box = env.box; }); - test("Subscribe to stream of entities", () async { + test('Subscribe to stream of entities', () async { final result = []; final text = TestEntity_.tString; @@ -28,18 +28,18 @@ void main() { result.add(str); }); - box.put(TestEntity(tString: "Hello world")); - box.putMany([ TestEntity(tString: "Goodbye"), - TestEntity(tString: "for now") ] as List); + box.put(TestEntity(tString: 'Hello world')); + box.putMany([ TestEntity(tString: 'Goodbye'), + TestEntity(tString: 'for now') ] as List); await Future.delayed(Duration(seconds: 0)); // ffi explodes without expect(result, - ["for now, Goodbye, Hello world", "for now, Goodbye, Hello world"]); + ['for now, Goodbye, Hello world', 'for now, Goodbye, Hello world']); - subscription.cancel(); + await subscription.cancel(); }); - test("Subscribe to stream of query", () async { + test('Subscribe to stream of query', () async { final result = []; final text = TestEntity_.tString; @@ -50,14 +50,14 @@ void main() { result.add(query.count()); }); - box.put(TestEntity(tString: "Hello world")); - box.putMany([ TestEntity(tString: "Goodbye"), - TestEntity(tString: "for now") ] as List); + box.put(TestEntity(tString: 'Hello world')); + box.putMany([ TestEntity(tString: 'Goodbye'), + TestEntity(tString: 'for now') ] as List); await Future.delayed(Duration(seconds: 0)); // ffi explodes without expect(result, [3, 3]); - subscription.cancel(); + await subscription.cancel(); }); tearDown(() { From 2a4c6f8f61b629ed09178bbe35076dd1bb532621 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Sat, 26 Sep 2020 21:56:35 +0200 Subject: [PATCH 11/20] specified the types properly for the compiler on obj creation, instead of casting and/or declaring types --- test/observer_test.dart | 8 ++++---- test/stream_test.dart | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/observer_test.dart b/test/observer_test.dart index ed35c0949..f0ea35948 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -66,11 +66,11 @@ void main() async { final testEntityId = getObjectBoxModel().model.findEntityByName('TestEntity').id.id; - final List simpleStringItems = ['One', 'Two', 'Three', 'Four', 'Five', 'Six'].map((s) => - TestEntity(tString: s)).toList(); + final simpleStringItems = ['One', 'Two', 'Three', 'Four', 'Five', 'Six'].map((s) => + TestEntity(tString: s)).toList().cast(); - final List simpleNumberItems = [1,2,3,4,5,6].map((s) => - TestEntity(tInt: s)).toList(); + final simpleNumberItems = [1,2,3,4,5,6].map((s) => + TestEntity(tInt: s)).toList().cast(); setUp(() { env = TestEnv('observers'); diff --git a/test/stream_test.dart b/test/stream_test.dart index 878a41177..eb050f300 100644 --- a/test/stream_test.dart +++ b/test/stream_test.dart @@ -29,8 +29,8 @@ void main() { }); box.put(TestEntity(tString: 'Hello world')); - box.putMany([ TestEntity(tString: 'Goodbye'), - TestEntity(tString: 'for now') ] as List); + box.putMany([ TestEntity(tString: 'Goodbye'), + TestEntity(tString: 'for now') ]); await Future.delayed(Duration(seconds: 0)); // ffi explodes without expect(result, @@ -51,8 +51,10 @@ void main() { }); box.put(TestEntity(tString: 'Hello world')); - box.putMany([ TestEntity(tString: 'Goodbye'), - TestEntity(tString: 'for now') ] as List); + + // idem, see above + box.putMany([ TestEntity(tString: 'Goodbye'), + TestEntity(tString: 'for now') ]); await Future.delayed(Duration(seconds: 0)); // ffi explodes without expect(result, [3, 3]); From 80b1256a0fc8861f62833884dfb34d1d5c04772a Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Sat, 26 Sep 2020 22:02:15 +0200 Subject: [PATCH 12/20] Code duplicated for the sake of shutting up a lint false positive: Pointer.asFunction complained that the typedef parameter was not proper. --- lib/src/bindings/signatures.dart | 6 ++++-- test/observer_test.dart | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/src/bindings/signatures.dart b/lib/src/bindings/signatures.dart index dbf5753ce..e827a1dfb 100644 --- a/lib/src/bindings/signatures.dart +++ b/lib/src/bindings/signatures.dart @@ -227,9 +227,11 @@ typedef obx_query_visit_dart_t = int Function( // observers typedef obx_observer_t = Void Function(Pointer user_data, Pointer entity_id, Uint32 type_ids_count); -typedef obx_observer_single_type_t = U Function(Pointer user_data); +typedef obx_observer_single_type_native_t = Void Function(Pointer user_data); +typedef obx_observer_single_type_dart_t = void Function(Pointer user_data); + typedef obx_observe_t = Pointer Function(Pointer store, Pointer> callback, Pointer user_data); -typedef obx_observe_single_type_t = Pointer Function(Pointer store, T entity_id, Pointer>> callback, Pointer user_data); +typedef obx_observe_single_type_t = Pointer Function(Pointer store, T entity_id, Pointer> callback, Pointer user_data); typedef obx_observer_close_native_t = Void Function(Pointer observer); typedef obx_observer_close_dart_t = void Function(Pointer observer); diff --git a/test/observer_test.dart b/test/observer_test.dart index f0ea35948..968a53e37 100644 --- a/test/observer_test.dart +++ b/test/observer_test.dart @@ -48,7 +48,7 @@ class Observable { void observeSingleType(int entityId, Single fn, Pointer identifier) { single = fn; - final callback = Pointer.fromFunction>(_singleCallback); + final callback = Pointer.fromFunction(_singleCallback); singleObserver = bindings.obx_observe_single_type(store.ptr, entityId, callback, identifier); } @@ -139,7 +139,7 @@ void main() async { }); test('Observe single entity', () async { - final callback = Pointer.fromFunction>(callbackSingleType); + final callback = Pointer.fromFunction(callbackSingleType); final observer = bindings.obx_observe_single_type(store.ptr, testEntityId, callback, randomPtr); box.putMany(simpleStringItems); From 6aacbf9707134ecc146fa51504269d7940545637 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Mon, 28 Sep 2020 16:23:04 +0200 Subject: [PATCH 13/20] fix in case single observer for multiple stores and there is no consensus on entity ids --- lib/src/observable.dart | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/src/observable.dart b/lib/src/observable.dart index edf4027bf..551ad4a59 100644 --- a/lib/src/observable.dart +++ b/lib/src/observable.dart @@ -14,32 +14,37 @@ typedef Any = void Function(Pointer, Pointer, int); class Observable { static final anyObserver = >{}; - static final any = {}; // radix? > tree? + static final any = >{}; // sync:true -> ObjectBoxException: 10001 TX is not active anymore: #101 static final controller = StreamController.broadcast(); + // The user_data is used to pass the store ptr address + // in case there is no consensus on the entity id between stores static void _anyCallback(Pointer user_data, Pointer mutated_ids, int mutated_count) { + final storeAddress = user_data.address; for(var i=0; i(_anyCallback); - anyObserver[store.ptr.address] = bindings.obx_observe(store.ptr, callback, store.ptr); + final storePtr = store.ptr; + anyObserver[storePtr.address] = bindings.obx_observe(storePtr, callback, storePtr); } // #53 ffi:Pointer finalizer static void unsubscribe(Store store) { - if (!anyObserver.containsKey(store.ptr.address)) { + final storeAddress = store.ptr.address; + if (!anyObserver.containsKey(storeAddress)) { return; } - bindings.obx_observer_close(anyObserver[store.ptr.address]); - anyObserver.remove(store.ptr.address); + bindings.obx_observer_close(anyObserver[storeAddress]); + anyObserver.remove(storeAddress); } } @@ -50,12 +55,17 @@ extension ObservableStore on Store { extension Streamable on Query { void _setup() { - if (!Observable.anyObserver.containsKey(store.ptr)) { + + final storePtr = store.ptr; + + if (!Observable.anyObserver.containsKey(storePtr)) { store.subscribe(); } - // Assume consensus on entityId over all available Stores - Observable.any[entityId] ??= (u, _, __) { + final storeAddress = storePtr.address; + + Observable.any[storeAddress] ??= {}; + Observable.any[storeAddress][entityId] ??= (u, _, __) { // dummy value to trigger an event Observable.controller.add(u.address); }; From 5d69ebf1281e4206a7d86a590f15b8eb3682b0e6 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Mon, 28 Sep 2020 23:58:03 +0200 Subject: [PATCH 14/20] applied stream to demo app and flattened the code a bit --- example/flutter/objectbox_demo/lib/main.dart | 115 +++++++++++-------- 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/example/flutter/objectbox_demo/lib/main.dart b/example/flutter/objectbox_demo/lib/main.dart index 8b8a7a494..23caf1648 100644 --- a/example/flutter/objectbox_demo/lib/main.dart +++ b/example/flutter/objectbox_demo/lib/main.dart @@ -3,6 +3,8 @@ import 'package:objectbox/objectbox.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'objectbox.g.dart'; +import 'package:objectbox/src/observable.dart'; +import 'dart:async'; @Entity() class Note { @@ -47,19 +49,18 @@ class _MyHomePageState extends State { final _noteInputController = TextEditingController(); Store _store; Box _box; - List _notes = []; + List _notes = []; + StreamSubscription _subscription; void _addNote() { if (_noteInputController.text.isEmpty) return; final newNote = Note.construct(_noteInputController.text); newNote.id = _box.put(newNote); - setState(() => _notes.add(newNote)); _noteInputController.text = ''; } void _removeNote(int index) { _box.remove(_notes[index].id); - setState(() => _notes.removeAt(index)); } @override @@ -69,18 +70,71 @@ class _MyHomePageState extends State { getApplicationDocumentsDirectory().then((dir) { _store = Store(getObjectBoxModel(), directory: dir.path + '/objectbox'); _box = Box(_store); - final notesFromDb = _box.getAll(); - setState(() => _notes = notesFromDb); // TODO: don't show UI before this point + final dateProp = Note_.date; + final dummyQuery = dateProp.greaterThan(0); + final query = _box.query(dummyQuery) + .order(dateProp, flags: Order.descending).build(); + final stream = query.findStream(); + _subscription = stream.listen((ns) { + setState(() => _notes = ns.cast()); + }); + + // default + setState(() => _notes = query.find()); }); } @override void dispose() { _noteInputController.dispose(); + _store.unsubscribe(); + _store.close(); super.dispose(); } + GestureDetector _itemBuilder(BuildContext context, int index) { + return GestureDetector( + onTap: () => _removeNote(index), + child: Row( + children: [ + Expanded( + child: Container( + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 18.0, horizontal: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _notes[index].text, + style: TextStyle( + fontSize: 15.0, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5.0), + child: Text( + "Added on ${DateFormat('dd.MM.yyyy hh:mm:ss') + .format(DateTime.fromMillisecondsSinceEpoch(_notes[index].date))}", + style: TextStyle( + fontSize: 12.0, + ), + ), + ), + ], + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.black12))), + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -100,7 +154,7 @@ class _MyHomePageState extends State { padding: EdgeInsets.only(right: 10.0), child: TextField( decoration: - InputDecoration(hintText: 'Enter new note'), + InputDecoration(hintText: 'Enter a new note'), controller: _noteInputController, ), ), @@ -109,7 +163,7 @@ class _MyHomePageState extends State { child: Align( alignment: Alignment.centerRight, child: Text( - 'Click a note to remove it', + 'Tap a note to remove it', style: TextStyle( fontSize: 11.0, color: Colors.grey, @@ -136,49 +190,10 @@ class _MyHomePageState extends State { shrinkWrap: true, padding: EdgeInsets.symmetric(horizontal: 20.0), itemCount: _notes.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () => _removeNote(index), - child: Row( - children: [ - Expanded( - child: Container( - child: Padding( - padding: EdgeInsets.symmetric( - vertical: 18.0, horizontal: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _notes[index].text, - style: TextStyle( - fontSize: 15.0, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5.0), - child: Text( - "Added on ${DateFormat('dd.MM.yyyy hh:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(_notes[index].date))}", - style: TextStyle( - fontSize: 12.0, - ), - ), - ), - ], - ), - ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.black12))), - ), - ), - ], - ), - ); - }, - ), - ), - ], + itemBuilder: _itemBuilder, + ) + ) + ] ), ); } From 3c9698f849c6d57ac6af39fb2839b44037526449 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 00:08:18 +0200 Subject: [PATCH 15/20] the sync tests just required more Future.delayed(... 0)) --- test/stream_test.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/stream_test.dart b/test/stream_test.dart index eb050f300..5ad96ed72 100644 --- a/test/stream_test.dart +++ b/test/stream_test.dart @@ -29,12 +29,14 @@ void main() { }); box.put(TestEntity(tString: 'Hello world')); + await Future.delayed(Duration(seconds: 0)); // ffi explodes without + box.putMany([ TestEntity(tString: 'Goodbye'), TestEntity(tString: 'for now') ]); - await Future.delayed(Duration(seconds: 0)); // ffi explodes without + expect(result, - ['for now, Goodbye, Hello world', 'for now, Goodbye, Hello world']); + ['Hello world', 'for now, Goodbye, Hello world']); await subscription.cancel(); }); @@ -51,13 +53,14 @@ void main() { }); box.put(TestEntity(tString: 'Hello world')); + await Future.delayed(Duration(seconds: 0)); // ffi explodes without // idem, see above box.putMany([ TestEntity(tString: 'Goodbye'), TestEntity(tString: 'for now') ]); - await Future.delayed(Duration(seconds: 0)); // ffi explodes without - expect(result, [3, 3]); + + expect(result, [1, 3]); await subscription.cancel(); }); From b3483edf74a1c0490874b5a3960b732b6d1fc38d Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 00:33:47 +0200 Subject: [PATCH 16/20] forgot to close the query --- example/flutter/objectbox_demo/lib/main.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/example/flutter/objectbox_demo/lib/main.dart b/example/flutter/objectbox_demo/lib/main.dart index 23caf1648..60f02cd5a 100644 --- a/example/flutter/objectbox_demo/lib/main.dart +++ b/example/flutter/objectbox_demo/lib/main.dart @@ -49,6 +49,7 @@ class _MyHomePageState extends State { final _noteInputController = TextEditingController(); Store _store; Box _box; + Query _query; List _notes = []; StreamSubscription _subscription; @@ -73,21 +74,22 @@ class _MyHomePageState extends State { // TODO: don't show UI before this point final dateProp = Note_.date; final dummyQuery = dateProp.greaterThan(0); - final query = _box.query(dummyQuery) + _query = _box.query(dummyQuery) .order(dateProp, flags: Order.descending).build(); - final stream = query.findStream(); + final stream = _query.findStream(); _subscription = stream.listen((ns) { setState(() => _notes = ns.cast()); }); // default - setState(() => _notes = query.find()); + setState(() => _notes = _query.find()); }); } @override void dispose() { _noteInputController.dispose(); + _query.close(); _store.unsubscribe(); _store.close(); super.dispose(); From 63600bbc15b5edaa4dd7a2b2a9fdeaea2817f948 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 11:09:42 +0200 Subject: [PATCH 17/20] package private fields --- lib/src/observable.dart | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/observable.dart b/lib/src/observable.dart index 551ad4a59..9c36fe48d 100644 --- a/lib/src/observable.dart +++ b/lib/src/observable.dart @@ -13,8 +13,8 @@ typedef Any = void Function(Pointer, Pointer, int); class Observable { - static final anyObserver = >{}; - static final any = >{}; + static final _anyObserver = >{}; + static final _any = >{}; // sync:true -> ObjectBoxException: 10001 TX is not active anymore: #101 static final controller = StreamController.broadcast(); @@ -25,8 +25,8 @@ class Observable { final storeAddress = user_data.address; for(var i=0; i(_anyCallback); final storePtr = store.ptr; - anyObserver[storePtr.address] = bindings.obx_observe(storePtr, callback, storePtr); + _anyObserver[storePtr.address] = bindings.obx_observe(storePtr, callback, storePtr); } // #53 ffi:Pointer finalizer static void unsubscribe(Store store) { final storeAddress = store.ptr.address; - if (!anyObserver.containsKey(storeAddress)) { + if (!_anyObserver.containsKey(storeAddress)) { return; } - bindings.obx_observer_close(anyObserver[storeAddress]); - anyObserver.remove(storeAddress); + bindings.obx_observer_close(_anyObserver[storeAddress]); + _anyObserver.remove(storeAddress); } } @@ -58,14 +58,14 @@ extension Streamable on Query { final storePtr = store.ptr; - if (!Observable.anyObserver.containsKey(storePtr)) { + if (!Observable._anyObserver.containsKey(storePtr)) { store.subscribe(); } final storeAddress = storePtr.address; - Observable.any[storeAddress] ??= {}; - Observable.any[storeAddress][entityId] ??= (u, _, __) { + Observable._any[storeAddress] ??= {}; + Observable._any[storeAddress][entityId] ??= (u, _, __) { // dummy value to trigger an event Observable.controller.add(u.address); }; From e62ad2abfe62d9a9254cacd1a4dec57ac361e67c Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 12:59:47 +0200 Subject: [PATCH 18/20] replaced listener with StreamBuilder, extracted database plumbing to its own ViewModel/BLoC --- example/flutter/objectbox_demo/lib/main.dart | 235 ++++++++++--------- 1 file changed, 125 insertions(+), 110 deletions(-) diff --git a/example/flutter/objectbox_demo/lib/main.dart b/example/flutter/objectbox_demo/lib/main.dart index 60f02cd5a..415f1a14f 100644 --- a/example/flutter/objectbox_demo/lib/main.dart +++ b/example/flutter/objectbox_demo/lib/main.dart @@ -5,6 +5,7 @@ import 'package:path_provider/path_provider.dart'; import 'objectbox.g.dart'; import 'package:objectbox/src/observable.dart'; import 'dart:async'; +import 'dart:io'; @Entity() class Note { @@ -13,7 +14,7 @@ class Note { String text; String comment; - int date; // TODO: use DateTime class + int date; Note(); @@ -21,6 +22,9 @@ class Note { date = DateTime.now().millisecondsSinceEpoch; print('constructed date: $date'); } + + get dateFormat => DateFormat('dd.MM.yyyy hh:mm:ss') + .format(DateTime.fromMillisecondsSinceEpoch(date)); } void main() => runApp(MyApp()); @@ -45,96 +49,114 @@ class MyHomePage extends StatefulWidget { _MyHomePageState createState() => _MyHomePageState(); } -class _MyHomePageState extends State { - final _noteInputController = TextEditingController(); +class ViewModel { Store _store; Box _box; Query _query; - List _notes = []; - StreamSubscription _subscription; + + ViewModel(Directory dir) { + _store = Store(getObjectBoxModel(), directory: dir.path + '/objectbox'); + _box = Box(_store); + + final dateProp = Note_.date; + final dummyCondition = dateProp.greaterThan(0); + + _query = _box + .query(dummyCondition) + .order(dateProp, flags: Order.descending) + .build(); + } + + void addNote(Note note) => _box.put(note); + + void removeNote(Note note) => _box.remove(note.id); + + get queryStream => _query.findStream(); + + get allNotes => _box.getAll(); + + void dispose() { + _query.close(); + _store.unsubscribe(); + _store.close(); + } +} + +class _MyHomePageState extends State { + final _noteInputController = TextEditingController(); + final _listController = StreamController>(sync:true); + Stream> _stream; + ViewModel _vm; void _addNote() { if (_noteInputController.text.isEmpty) return; - final newNote = Note.construct(_noteInputController.text); - newNote.id = _box.put(newNote); + _vm.addNote(Note.construct(_noteInputController.text)); _noteInputController.text = ''; } - void _removeNote(int index) { - _box.remove(_notes[index].id); - } - @override void initState() { super.initState(); getApplicationDocumentsDirectory().then((dir) { - _store = Store(getObjectBoxModel(), directory: dir.path + '/objectbox'); - _box = Box(_store); - // TODO: don't show UI before this point - final dateProp = Note_.date; - final dummyQuery = dateProp.greaterThan(0); - _query = _box.query(dummyQuery) - .order(dateProp, flags: Order.descending).build(); - final stream = _query.findStream(); - _subscription = stream.listen((ns) { - setState(() => _notes = ns.cast()); - }); - - // default - setState(() => _notes = _query.find()); + _vm = ViewModel(dir); + _stream = _listController.stream; + + setState(() {}); + + _listController.add(_vm.allNotes); + _listController.addStream(_vm.queryStream); }); } @override void dispose() { _noteInputController.dispose(); - _query.close(); - _store.unsubscribe(); - _store.close(); + _listController.close(); + _vm.dispose(); super.dispose(); } - GestureDetector _itemBuilder(BuildContext context, int index) { - return GestureDetector( - onTap: () => _removeNote(index), - child: Row( - children: [ - Expanded( - child: Container( - child: Padding( - padding: EdgeInsets.symmetric( - vertical: 18.0, horizontal: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _notes[index].text, - style: TextStyle( - fontSize: 15.0, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5.0), - child: Text( - "Added on ${DateFormat('dd.MM.yyyy hh:mm:ss') - .format(DateTime.fromMillisecondsSinceEpoch(_notes[index].date))}", + GestureDetector Function(BuildContext, int) _itemBuilder(List notes) { + return (BuildContext context, int index) { + return GestureDetector( + onTap: () => _vm.removeNote(notes[index]), + child: Row( + children: [ + Expanded( + child: Container( + child: Padding( + padding: + EdgeInsets.symmetric(vertical: 18.0, horizontal: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + notes[index].text, style: TextStyle( - fontSize: 12.0, + fontSize: 15.0, ), ), - ), - ], + Padding( + padding: EdgeInsets.only(top: 5.0), + child: Text( + 'Added on ${notes[index].dateFormat}', + style: TextStyle( + fontSize: 12.0, + ), + ), + ), + ], + ), ), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.black12))), ), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.black12))), ), - ), - ], - ), - ); + ], + ), + ); + }; } @override @@ -143,60 +165,53 @@ class _MyHomePageState extends State { appBar: AppBar( title: Text(widget.title), ), - body: Column( - children: [ - Padding( - padding: EdgeInsets.all(20.0), - child: Row( - children: [ - Expanded( - child: Column( - children: [ - Padding( - padding: EdgeInsets.only(right: 10.0), - child: TextField( - decoration: - InputDecoration(hintText: 'Enter a new note'), - controller: _noteInputController, - ), + body: Column(children: [ + Padding( + padding: EdgeInsets.all(20.0), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.0), + child: TextField( + decoration: + InputDecoration(hintText: 'Enter a new note'), + controller: _noteInputController, + onSubmitted: (String) => _addNote() ), - Padding( - padding: EdgeInsets.only(top: 10.0, right: 10.0), - child: Align( - alignment: Alignment.centerRight, - child: Text( - 'Tap a note to remove it', - style: TextStyle( - fontSize: 11.0, - color: Colors.grey, - ), + ), + Padding( + padding: EdgeInsets.only(top: 10.0, right: 10.0), + child: Align( + alignment: Alignment.centerRight, + child: Text( + 'Tap a note to remove it', + style: TextStyle( + fontSize: 11.0, + color: Colors.grey, ), ), ), - ], - ), - ), - Column( - children: [ - RaisedButton( - onPressed: _addNote, - child: Text('Add'), - ) + ), ], - ) - ], - ), + ), + ) + ], ), - Expanded( - child: ListView.builder( - shrinkWrap: true, - padding: EdgeInsets.symmetric(horizontal: 20.0), - itemCount: _notes.length, - itemBuilder: _itemBuilder, - ) - ) - ] - ), + ), + Expanded( + child: StreamBuilder>( + stream: _stream, + builder: (context, snapshot) { + return ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 20.0), + itemCount: snapshot.hasData ? snapshot.data.length : 0, + itemBuilder: _itemBuilder(snapshot.data)); + })) + ]), ); } } From cd4a391f473abffba8555448081c28e00097d28e Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Tue, 29 Sep 2020 13:46:41 +0200 Subject: [PATCH 19/20] oopsie --- example/flutter/objectbox_demo/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/flutter/objectbox_demo/lib/main.dart b/example/flutter/objectbox_demo/lib/main.dart index 415f1a14f..89bb54fa5 100644 --- a/example/flutter/objectbox_demo/lib/main.dart +++ b/example/flutter/objectbox_demo/lib/main.dart @@ -73,7 +73,7 @@ class ViewModel { get queryStream => _query.findStream(); - get allNotes => _box.getAll(); + get allNotes => _query.find(); void dispose() { _query.close(); From 298169e8d5bfbe8cffad39fbe03dee09a9f8dfd1 Mon Sep 17 00:00:00 2001 From: Jasm Sison Date: Mon, 5 Oct 2020 23:13:48 +0200 Subject: [PATCH 20/20] changed comments to just declaring something explodes --- test/stream_test.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/stream_test.dart b/test/stream_test.dart index 5ad96ed72..4be373ee7 100644 --- a/test/stream_test.dart +++ b/test/stream_test.dart @@ -29,11 +29,16 @@ void main() { }); box.put(TestEntity(tString: 'Hello world')); - await Future.delayed(Duration(seconds: 0)); // ffi explodes without + + // The delay is here to ensure that the + // callback execution is executed sequentially, + // otherwise the testing framework's execution + // will be prioritized (for some reason), before any callback. + await Future.delayed(Duration(seconds: 0)); box.putMany([ TestEntity(tString: 'Goodbye'), TestEntity(tString: 'for now') ]); - await Future.delayed(Duration(seconds: 0)); // ffi explodes without + await Future.delayed(Duration(seconds: 0)); expect(result, ['Hello world', 'for now, Goodbye, Hello world']); @@ -53,12 +58,12 @@ void main() { }); box.put(TestEntity(tString: 'Hello world')); - await Future.delayed(Duration(seconds: 0)); // ffi explodes without + await Future.delayed(Duration(seconds: 0)); // idem, see above box.putMany([ TestEntity(tString: 'Goodbye'), TestEntity(tString: 'for now') ]); - await Future.delayed(Duration(seconds: 0)); // ffi explodes without + await Future.delayed(Duration(seconds: 0)); expect(result, [1, 3]);