Skip to content

Commit

Permalink
support realm refresh() (#1046)
Browse files Browse the repository at this point in the history
* support realm refresh()

* fix up changelog

* support refresh callback

* fix tests, add not called test

* make operation a no op if the callback was not registered successfully

don't invoke the callback if the onRefresh registration fails
fix test for outside transaction registration

* fix expectation

* fix refresh callback implementation.

* add refreshAsync to changelog

* use proper future timeout

* fix test

* Update realm in another Isolate

* Disable auto refresh realm and refresh manually

* Return result from refreshAsync

* Fix API doc

* Fix API doc

* Repair a test

* Code review changes

* Code review changes

* Code review changes

* Apply suggestions from code review

Co-authored-by: blagoev <lubo@blagoev.com>

* Code review changes

---------

Co-authored-by: Desislava Stefanova <dst.stefanova@gmail.com>
Co-authored-by: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 1, 2023
1 parent 0c697ae commit 667a6ec
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
### Enhancements
* Support setting `maxNumberOfActiveVersions` when creating a `Configuration`. ([#1036](https://github.com/realm/realm-dart/pull/1036))
* Add List.move extension method that moves an element from one index to another. Delegates to ManagedRealmList.move for managed lists. This allows notifications to correctly report moves, as opposed to reporting moves as deletes + inserts. ([#1037](https://github.com/realm/realm-dart/issues/1037))
* Add `Realm.refresh()` and `Realm.refreshAsync()` support. ([#1046](https://github.com/realm/realm-dart/pull/1046))
* Support setting `shouldDeleteIfMigrationNeeded` when creating a `Configuration.local`. ([#1049](https://github.com/realm/realm-dart/issues/1049))
* Add `unknown` error code to all SyncErrors: `SyncSessionErrorCode.unknown`, `SyncConnectionErrorCode.unknown`, `SyncClientErrorCode.unknown`, `GeneralSyncErrorCode.unknown`. Use `unknown` error code instead of throwing a RealmError. ([#1052](https://github.com/realm/realm-dart/pull/1052))
* Add support for `RealmValue` data type. This new type can represent any valid Realm data type, including objects. Lists of `RealmValue` are also supported, but `RealmValue` itself cannot contain collections. Please note that a property of type `RealmValue` cannot be nullable, but can contain null, represented by the value `RealmValue.nullValue()`. ([#1051](https://github.com/realm/realm-dart/pull/1051))
Expand Down
17 changes: 17 additions & 0 deletions lib/src/native/realm_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7786,6 +7786,23 @@ class RealmLibrary {
ffi.Pointer<realm_key_path_array_t>,
realm_on_collection_change_func_t)>();

void realm_set_auto_refresh(
ffi.Pointer<realm_t> realm,
bool enable,
) {
return _realm_set_auto_refresh(
realm,
enable,
);
}

late final _realm_set_auto_refreshPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Pointer<realm_t>, ffi.Bool)>>('realm_set_auto_refresh');
late final _realm_set_auto_refresh = _realm_set_auto_refreshPtr
.asFunction<void Function(ffi.Pointer<realm_t>, bool)>();

/// Clear a set of values.
///
/// @return True if no exception occurred.
Expand Down
27 changes: 27 additions & 0 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,10 @@ class _RealmCore {
});
}

void realmSetAutoRefresh(Realm realm, bool enable) {
_realmLib.realm_set_auto_refresh(realm.handle._pointer, enable);
}

SchedulerHandle createScheduler(int isolateId, int sendPort) {
final schedulerPtr = _realmLib.realm_dart_create_scheduler(isolateId, sendPort);
return SchedulerHandle._(schedulerPtr);
Expand Down Expand Up @@ -773,6 +777,29 @@ class _RealmCore {
});
}

Future<bool> realmRefreshAsync(Realm realm) async {
final completer = Completer<bool>();
final callback = Pointer.fromFunction<Void Function(Pointer<Void>)>(_realmRefreshAsyncCallback);
Pointer<Void> completerPtr = _realmLib.realm_dart_object_to_persistent_handle(completer);
Pointer<realm_refresh_callback_token> result = _realmLib.realm_add_realm_refresh_callback(
realm.handle._pointer, callback.cast(), completerPtr, _realmLib.addresses.realm_dart_delete_persistent_handle);

if (result == nullptr) {
return Future<bool>.value(false);
}

return completer.future;
}

static void _realmRefreshAsyncCallback(Pointer<Void> userdata) {
if (userdata == nullptr) {
return;
}

final completer = _realmLib.realm_dart_persistent_handle_to_object(userdata) as Completer<bool>;
completer.complete(true);
}

RealmObjectMetadata getObjectMetadata(Realm realm, SchemaObject schema) {
return using((Arena arena) {
final found = arena<Bool>();
Expand Down
24 changes: 24 additions & 0 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,24 @@ class Realm implements Finalizable {

realmCore.writeCopy(this, config);
}

/// Update the `Realm` instance and outstanding objects to point to the most recent persisted version.
///
/// If another process or [Isolate] has made changes to the realm file, this causes
/// those changes to become visible in this realm instance.
/// Typically you don't need to call this method since Realm has auto-refresh built-in.
/// Note that this may return `true` even if no data has actually changed.
bool refresh() {
return realmCore.realmRefresh(this);
}

/// Returns a [Future] that will complete when the `Realm` is refreshed to the version which is the
/// latest version at the time when this method is called.
///
/// Note that this may return `true` even if no data has actually changed.
Future<bool> refreshAsync() async {
return realmCore.realmRefreshAsync(this);
}
}

/// Provides a scope to safely write data to a [Realm]. Can be created using [Realm.beginWrite] or
Expand Down Expand Up @@ -726,6 +744,12 @@ extension RealmInternal on Realm {
addUnmanagedRealmObjectFromValue(value.value, update);
}
}

// Internal method that prevents the realm from being automatically refreshed.
// This method is used for testing purposes only.
void disableAutoRefreshForTesting() {
realmCore.realmSetAutoRefresh(this, false);
}
}

/// @nodoc
Expand Down
4 changes: 4 additions & 0 deletions src/realm_dart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@ RLM_API void realm_dettach_finalizer(void* finalizableHandle, Dart_Handle handle
Dart_FinalizableHandle finalHandle = reinterpret_cast<Dart_FinalizableHandle>(finalizableHandle);
return Dart_DeleteFinalizableHandle_DL(finalHandle, handle);
}

RLM_API void realm_set_auto_refresh(realm_t* realm, bool enable){
(*realm)->set_auto_refresh(enable);
}
3 changes: 1 addition & 2 deletions src/realm_dart.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,5 @@ RLM_API const char* realm_dart_library_version();

RLM_API void* realm_attach_finalizer(Dart_Handle handle, void* realmPtr, int size);
RLM_API void realm_dettach_finalizer(void* finalizableHandle, Dart_Handle handle);


RLM_API void realm_set_auto_refresh(realm_t* realm, bool enable);
#endif // REALM_DART_H
1 change: 0 additions & 1 deletion src/realm_dart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#include <realm/util/functional.hpp>

struct realm_dart_userdata_async {
public:
realm_dart_userdata_async(Dart_Handle handle, void* callback, realm_scheduler_t* scheduler)
: handle(Dart_NewPersistentHandle_DL(handle))
, dart_callback(callback)
Expand Down
101 changes: 94 additions & 7 deletions test/realm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// ignore_for_file: unused_local_variable, avoid_relative_lib_imports

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
Expand All @@ -27,6 +28,7 @@ import 'package:timezone/data/latest.dart' as tz;
import 'package:path/path.dart' as p;
import 'package:cancellation_token/cancellation_token.dart';
import '../lib/realm.dart';
import '../lib/src/realm_class.dart' as realmInternal;
import 'test.dart';

Future<void> main([List<String>? args]) async {
Expand Down Expand Up @@ -1582,22 +1584,19 @@ Future<void> main([List<String>? args]) async {
final pathCopy = originalConfig.path.replaceFirst(p.basenameWithoutExtension(originalConfig.path), generateRandomString(10));
final configCopy = Configuration.local([Car.schema], path: pathCopy);
originalRealm.write(() {
expect(() => originalRealm.writeCopy(configCopy),
throws<RealmError>("Copying a Realm is not allowed within a write transaction or during migration."));
expect(() => originalRealm.writeCopy(configCopy), throws<RealmError>("Copying a Realm is not allowed within a write transaction or during migration."));
});
originalRealm.close();
});

test('Realm writeCopy Local->Local during migration is mot allowed', () {
test('Realm writeCopy Local->Local during migration is not allowed', () {
getRealm(Configuration.local([Car.schema], schemaVersion: 1)).close();

final configWithMigrationCallback = Configuration.local([Car.schema], schemaVersion: 2, migrationCallback: (migration, oldVersion) {

final pathCopy = migration.newRealm.config.path.replaceFirst(p.basenameWithoutExtension(migration.newRealm.config.path), generateRandomString(10));
final configCopy = Configuration.local([Car.schema], path: pathCopy);
expect(() => migration.newRealm.writeCopy(configCopy),
throws<RealmError>("Copying a Realm is not allowed within a write transaction or during migration."));

expect(
() => migration.newRealm.writeCopy(configCopy), throws<RealmError>("Copying a Realm is not allowed within a write transaction or during migration."));
});
getRealm(configWithMigrationCallback);
});
Expand Down Expand Up @@ -1756,6 +1755,94 @@ Future<void> main([List<String>? args]) async {
});
}
}
test('Realm.refresh no changes', () async {
final realm = getRealm(Configuration.local([Person.schema]));
final result = realm.refresh();
expect(result, false);
});

test('Realm.refreshAsync() sync transaction', () async {
final realm = getRealm(Configuration.local([Person.schema]));
var called = false;
bool isRefreshed = false;
final transaction = realm.beginWrite();
realm.refreshAsync().then((refreshed) {
called = true;
isRefreshed = refreshed;
});
realm.add(Person("name"));
transaction.commit();

await Future<void>.delayed(Duration(milliseconds: 1));
expect(isRefreshed, false);
expect(called, true);
expect(realm.all<Person>().length, 1);
});

test('Realm.refreshAsync from within a write block', () async {
final realm = getRealm(Configuration.local([Person.schema]));
var called = false;
bool isRefreshed = false;
realm.write(() {
realm.refreshAsync().then((refreshed) {
called = true;
isRefreshed = refreshed;
});
realm.add(Person("name"));
});

await Future<void>.delayed(Duration(milliseconds: 1));
expect(isRefreshed, false);
expect(called, true);
expect(realm.all<Person>().length, 1);
});

test('Realm.refreshAsync from within an async transaction', () async {
final realm = getRealm(Configuration.local([Person.schema]));
bool called = false;
bool isRefreshed = false;
final transaction = await realm.beginWriteAsync();
realm.refreshAsync().then((refreshed) {
called = true;
isRefreshed = refreshed;
});
realm.add(Person("name"));
await transaction.commitAsync();
expect(isRefreshed, false);
expect(called, true);
expect(realm.all<Person>().length, 1);
});

test('Realm.refresh on frozen realm should be no-op', () async {
var realm = getRealm(Configuration.local([Person.schema]));
bool called = false;
realm = realm.freeze();
expect(realm.refresh(), false);
});

test('Realm.refresh', () async {
final realm = getRealm(Configuration.local([Person.schema]));
String personName = generateRandomString(5);
final path = realm.config.path;
final results = realm.query<Person>(r"name == $0", [personName]);

expect(realm.refresh(), false);
realm.disableAutoRefreshForTesting();

ReceivePort receivePort = ReceivePort();
Isolate.spawn((SendPort sendPort) async {
final externalRealm = Realm(Configuration.local([Person.schema], path: path));
externalRealm.write(() => externalRealm.add(Person(personName)));
externalRealm.close();
sendPort.send(true);
}, receivePort.sendPort);

await receivePort.first;
expect(results.length, 0);
expect(realm.refresh(), true);
expect(results.length, 1);
receivePort.close();
});
}

List<int> generateEncryptionKey() {
Expand Down

0 comments on commit 667a6ec

Please sign in to comment.