Skip to content

Commit

Permalink
Results of primitives (#893)
Browse files Browse the repository at this point in the history
* Allow results of primitives.

Moves results of objects specific functionality to extension method
to avoid misuse.

* Update CHANGELOG

* Address PR feedback

* Support notifications on List of primitives

* Support notifications on results of primitives

* Update CHANGELOG

* Test List.asResults().query

* Test on type instead of _metadata
  • Loading branch information
nielsenko authored Oct 26, 2022
1 parent a6ffabf commit eba2e31
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 54 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dart.lineLength": 160,
"cSpell.words": [
"apikeys",
"BEGINSWITH",
"bson",
"deallocated",
"deleter",
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Enhancements
* Added `MutableSubscriptionSet.removeByType` for removing subscriptions by their realm object type. (Issue [#317](https://github.com/realm/realm-dart/issues/317))
* Support results of primitives, ie. `RealmResult<int>`. (Issue [#162](https://github.com/realm/realm-dart/issues/162))
* Support notifications on all managed realm lists, including list of primitives, ie. `RealmList<int>.changes` is supported. ([#893](https://github.com/realm/realm-dart/pull/893))

### Fixed
* Fixed a wrong mapping for `AuthProviderType` returned by `User.provider` for google, facebook and apple credentials.
Expand Down
42 changes: 27 additions & 15 deletions lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ abstract class RealmList<T extends Object?> with RealmEntity implements List<T>,
/// and it's parent object hasn't been deleted.
bool get isValid;

/// Converts this [List] to a [RealmResults].
RealmResults<T> asResults();

factory RealmList._(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmList._(handle, realm, metadata);
factory RealmList(Iterable<T> items) => UnmanagedRealmList(items);

/// Creates a frozen snapshot of this `RealmList`.
RealmList<T> freeze();

/// Allows listening for changes when the contents of this collection changes.
Stream<RealmListChanges<T>> get changes;
}

class ManagedRealmList<T extends Object?> with RealmEntity, ListMixin<T> implements RealmList<T> {
Expand Down Expand Up @@ -165,6 +171,18 @@ class ManagedRealmList<T extends Object?> with RealmEntity, ListMixin<T> impleme
final frozenRealm = realm.freeze();
return frozenRealm.resolveList(this)!;
}

@override
RealmResults<T> asResults() => RealmResultsInternal.create<T>(realmCore.resultsFromList(this), realm, metadata);

@override
Stream<RealmListChanges<T>> get changes {
if (isFrozen) {
throw RealmStateError('List is frozen and cannot emit changes');
}
final controller = ListNotificationsController<T>(asManaged());
return controller.createStream();
}
}

class UnmanagedRealmList<T extends Object?> extends collection.DelegatingList<T> with RealmEntity implements RealmList<T> {
Expand All @@ -181,6 +199,12 @@ class UnmanagedRealmList<T extends Object?> extends collection.DelegatingList<T>

@override
RealmList<T> freeze() => throw RealmStateError("Unmanaged lists can't be frozen");

@override
RealmResults<T> asResults() => throw RealmStateError("Unmanaged lists can't be converted to results");

@override
Stream<RealmListChanges<T>> get changes => throw RealmStateError("Unmanaged lists don't support changes");
}

// The query operations on lists, as well as the ability to subscribe for notifications,
Expand All @@ -194,21 +218,9 @@ extension RealmListOfObject<T extends RealmObjectBase> on RealmList<T> {
/// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/)
/// and [Predicate Programming Guide.](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789)
RealmResults<T> query(String query, [List<Object> arguments = const []]) {
final managedList = asManaged();
final handle = realmCore.queryList(managedList, query, arguments);
final handle = realmCore.queryList(asManaged(), query, arguments);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
}

/// Allows listening for changes when the contents of this collection changes.
Stream<RealmListChanges<T>> get changes {
if (isFrozen) {
throw RealmStateError('List is frozen and cannot emit changes');
}

final managedList = asManaged();
final controller = ListNotificationsController<T>(managedList);
return controller.createStream();
}
}

/// @nodoc
Expand Down Expand Up @@ -275,15 +287,15 @@ extension RealmListInternal<T extends Object?> on RealmList<T> {
}

/// Describes the changes in a Realm results collection since the last time the notification callback was invoked.
class RealmListChanges<T extends Object> extends RealmCollectionChanges {
class RealmListChanges<T extends Object?> extends RealmCollectionChanges {
/// The collection being monitored for changes.
final RealmList<T> list;

RealmListChanges._(super.handle, this.list);
}

/// @nodoc
class ListNotificationsController<T extends Object> extends NotificationsController {
class ListNotificationsController<T extends Object?> extends NotificationsController {
final ManagedRealmList<T> list;
late final StreamController<RealmListChanges<T>> streamController;

Expand Down
15 changes: 14 additions & 1 deletion lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,20 @@ class _RealmCore {
});
}

RealmObjectHandle getObjectAt(RealmResults results, int index) {
RealmResultsHandle resultsFromList(RealmList list) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_list_to_results(list.handle._pointer));
return RealmResultsHandle._(pointer, list.realm.handle);
}

Object? resultsGetElementAt(RealmResults results, int index) {
return using((Arena arena) {
final realm_value = arena<realm_value_t>();
_realmLib.invokeGetBool(() => _realmLib.realm_results_get(results.handle._pointer, index, realm_value));
return realm_value.toDartValue(results.realm);
});
}

RealmObjectHandle resultsGetObjectAt(RealmResults results, int index) {
final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_get_object(results.handle._pointer, index));
return RealmObjectHandle._(pointer, results.realm.handle);
}
Expand Down
8 changes: 5 additions & 3 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges;
export 'realm_object.dart'
show RealmEntity, RealmException, UserCallbackException, RealmObject, RealmObjectBase, EmbeddedObject, RealmObjectChanges, DynamicRealmObject;
export 'realm_property.dart';
export 'results.dart' show RealmResults, RealmResultsChanges;
export 'results.dart' show RealmResults, RealmResultsChanges, RealmResultsOfObject;
export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress, ConnectionStateChange;
export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet;
export 'user.dart' show User, UserState, UserIdentity, ApiKeyClient, ApiKey, FunctionsClient;
export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress, ConnectionStateChange;
export 'migration.dart' show Migration;
export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException;

Expand Down Expand Up @@ -628,12 +628,14 @@ extension RealmInternal on Realm {
return createList<T>(handle, list.metadata);
}

RealmResults<T> resolveResults<T extends RealmObjectBase>(RealmResults<T> results) {
RealmResults<T> resolveResults<T extends Object?>(RealmResults<T> results) {
final handle = realmCore.resolveResults(results, this);
return RealmResultsInternal.create<T>(handle, this, results.metadata);
}

static MigrationRealm getMigrationRealm(Realm realm) => MigrationRealm._(realm);

bool get isInMigration => _isInMigration;
}

/// @nodoc
Expand Down
66 changes: 38 additions & 28 deletions lib/src/results.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:collection' as collection;
import 'dart:ffi';
Expand All @@ -29,7 +28,7 @@ import 'realm_object.dart';
/// added to or deleted from the Realm that match the underlying query.
///
/// {@category Realm}
class RealmResults<T extends RealmObjectBase> extends collection.IterableBase<T> with RealmEntity implements Finalizable {
class RealmResults<T extends Object?> extends collection.IterableBase<T> with RealmEntity implements Finalizable {
final RealmObjectMetadata? _metadata;
final RealmResultsHandle _handle;

Expand All @@ -41,17 +40,13 @@ class RealmResults<T extends RealmObjectBase> extends collection.IterableBase<T>

/// Returns the element of type `T` at the specified [index].
T operator [](int index) {
final handle = realmCore.getObjectAt(this, index);
return realm.createObject(T, handle, _metadata!) as T;
}

/// Returns a new [RealmResults] filtered according to the provided query.
///
/// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/)
/// and [Predicate Programming Guide.](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789)
RealmResults<T> query(String query, [List<Object> args = const []]) {
final handle = realmCore.queryResults(this, query, args);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
if (this is RealmResults<RealmObjectBase>) {
final handle = realmCore.resultsGetObjectAt(this, index);
final accessor = RealmCoreAccessor(metadata, realm.isInMigration);
return RealmObjectInternal.create(T, realm, handle, accessor) as T;
} else {
return realmCore.resultsGetElementAt(this, index) as T;
}
}

/// `true` if the `Results` collection is empty.
Expand All @@ -73,6 +68,16 @@ class RealmResults<T extends RealmObjectBase> extends collection.IterableBase<T>
@override
int get length => realmCore.getResultsCount(this);

/// Creates a frozen snapshot of this query.
RealmResults<T> freeze() {
if (isFrozen) {
return this;
}

final frozenRealm = realm.freeze();
return frozenRealm.resolveResults(this);
}

/// Allows listening for changes when the contents of this collection changes.
Stream<RealmResultsChanges<T>> get changes {
if (isFrozen) {
Expand All @@ -82,15 +87,17 @@ class RealmResults<T extends RealmObjectBase> extends collection.IterableBase<T>
final controller = ResultsNotificationsController<T>(this);
return controller.createStream();
}
}

/// Creates a frozen snapshot of this query.
RealmResults<T> freeze() {
if (isFrozen) {
return this;
}

final frozenRealm = realm.freeze();
return frozenRealm.resolveResults(this);
// The query operations on results only work for results of objects (core restriction),
// so we add it as an extension methods to allow the compiler to prevent misuse.
extension RealmResultsOfObject<T extends RealmObjectBase> on RealmResults<T> {
/// Returns a new [RealmResults] filtered according to the provided query.
///
/// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://www.mongodb.com/docs/realm/realm-query-language/)
RealmResults<T> query(String query, [List<Object> args = const []]) {
final handle = realmCore.queryResults(this, query, args);
return RealmResultsInternal.create<T>(handle, realm, _metadata);
}
}

Expand All @@ -110,23 +117,26 @@ extension RealmResultsInternal on RealmResults {
return _handle;
}

RealmObjectMetadata? get metadata => _metadata;
RealmObjectMetadata get metadata => _metadata!;

static RealmResults<T> create<T extends RealmObjectBase>(RealmResultsHandle handle, Realm realm, RealmObjectMetadata? metadata) {
return RealmResults<T>._(handle, realm, metadata);
}
static RealmResults<T> create<T extends Object?>(
RealmResultsHandle handle,
Realm realm,
RealmObjectMetadata? metadata,
) =>
RealmResults<T>._(handle, realm, metadata);
}

/// Describes the changes in a Realm results collection since the last time the notification callback was invoked.
class RealmResultsChanges<T extends RealmObjectBase> extends RealmCollectionChanges {
class RealmResultsChanges<T extends Object?> extends RealmCollectionChanges {
/// The results collection being monitored for changes.
final RealmResults<T> results;

RealmResultsChanges._(super.handle, this.results);
}

/// @nodoc
class ResultsNotificationsController<T extends RealmObjectBase> extends NotificationsController {
class ResultsNotificationsController<T extends Object?> extends NotificationsController {
final RealmResults<T> results;
late final StreamController<RealmResultsChanges<T>> streamController;

Expand Down Expand Up @@ -158,7 +168,7 @@ class ResultsNotificationsController<T extends RealmObjectBase> extends Notifica
}
}

class _RealmResultsIterator<T extends RealmObjectBase> implements Iterator<T> {
class _RealmResultsIterator<T extends Object?> implements Iterator<T> {
final RealmResults<T> _results;
int _index;
T? _current;
Expand Down
Loading

0 comments on commit eba2e31

Please sign in to comment.