Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support realm refresh() #1046

Merged
merged 26 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
be353da
support realm refresh()
blagoev Nov 29, 2022
a68e61d
fix up changelog
blagoev Nov 29, 2022
964d0a1
support refresh callback
blagoev Nov 30, 2022
bcabb3a
Merge branch 'master' into blagoev/support-refresh
blagoev Nov 30, 2022
76bcbd1
fix tests, add not called test
blagoev Nov 30, 2022
9f6de65
make operation a no op if the callback was not registered successfully
blagoev Nov 30, 2022
47631b4
fix expectation
blagoev Nov 30, 2022
87fe8c6
fix refresh callback implementation.
blagoev Nov 30, 2022
68d116d
add refreshAsync to changelog
blagoev Dec 2, 2022
b94a141
use proper future timeout
blagoev Dec 2, 2022
ef3fe59
fix test
blagoev Dec 2, 2022
456ebf5
Merge branch 'main' into blagoev/support-refresh
desistefanova Jan 13, 2023
978378d
Merge branch 'main' into blagoev/support-refresh
desistefanova Jan 26, 2023
069fcaa
Update realm in another Isolate
desistefanova Jan 27, 2023
00a4005
Disable auto refresh realm and refresh manually
desistefanova Jan 29, 2023
80099d3
Merge branch 'main' into blagoev/support-refresh
desistefanova Jan 29, 2023
fb5d158
Return result from refreshAsync
desistefanova Jan 29, 2023
24c2d76
Fix API doc
desistefanova Jan 29, 2023
a19c354
Fix API doc
desistefanova Jan 29, 2023
2a2d879
Repair a test
desistefanova Jan 29, 2023
53dd35d
Code review changes
desistefanova Jan 30, 2023
e1bcd6e
Code review changes
desistefanova Jan 30, 2023
56a97c2
Code review changes
desistefanova Jan 30, 2023
00051e0
Apply suggestions from code review
desistefanova Jan 30, 2023
f7a1944
Merge branch 'main' into blagoev/support-refresh
desistefanova Jan 30, 2023
ecd453d
Code review changes
desistefanova Jan 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
_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));
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
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);
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
expect(realm.all<Person>().length, 1);
});

test('Realm.refresh on frozen realm should be no-op', () async {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
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();
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
Isolate.spawn((SendPort sendPort) async {
desistefanova marked this conversation as resolved.
Show resolved Hide resolved
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