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

Add support for string based/dynamic API on RealmObject #853

Merged
merged 6 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
10 changes: 5 additions & 5 deletions lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import 'results.dart';
/// added to or deleted from the collection or from the Realm.
///
/// {@category Realm}
abstract class RealmList<T extends Object> with RealmEntity implements List<T>, Finalizable {
abstract class RealmList<T extends Object?> with RealmEntity implements List<T>, Finalizable {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This liberalizes the type args on RealmList to support nullable objects. It doesn't actually add support for lists of nullables - that is done in #708, but is necessary to simplify a lot of the generic constraints elsewhere.

late final RealmObjectMetadata? _metadata;

/// Gets a value indicating whether this collection is still valid to use.
Expand All @@ -44,7 +44,7 @@ abstract class RealmList<T extends Object> with RealmEntity implements List<T>,
factory RealmList(Iterable<T> items) => UnmanagedRealmList(items);
}

class ManagedRealmList<T extends Object> extends collection.ListBase<T> with RealmEntity implements RealmList<T> {
class ManagedRealmList<T extends Object?> extends collection.ListBase<T> with RealmEntity implements RealmList<T> {
final RealmListHandle _handle;

@override
Expand Down Expand Up @@ -97,7 +97,7 @@ class ManagedRealmList<T extends Object> extends collection.ListBase<T> with Rea
bool get isValid => realmCore.listIsValid(this);
}

class UnmanagedRealmList<T extends Object> extends collection.ListBase<T> with RealmEntity implements RealmList<T> {
class UnmanagedRealmList<T extends Object?> extends collection.ListBase<T> with RealmEntity implements RealmList<T> {
final _unmanaged = <T?>[]; // use T? for length=

UnmanagedRealmList([Iterable<T>? items]) {
Expand Down Expand Up @@ -156,7 +156,7 @@ extension RealmListOfObject<T extends RealmObject> on RealmList<T> {
}

/// @nodoc
extension RealmListInternal<T extends Object> on RealmList<T> {
extension RealmListInternal<T extends Object?> on RealmList<T> {
@pragma('vm:never-inline')
void keepAlive() {
final self = this;
Expand All @@ -170,7 +170,7 @@ extension RealmListInternal<T extends Object> on RealmList<T> {

RealmListHandle get handle => asManaged()._handle;

static RealmList<T> create<T extends Object>(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => RealmList<T>._(handle, realm, metadata);
static RealmList<T> create<T extends Object?>(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => RealmList<T>._(handle, realm, metadata);

static void setValue(RealmListHandle handle, Realm realm, int index, Object? value, {bool update = false}) {
if (index < 0) {
Expand Down
6 changes: 4 additions & 2 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,9 @@ class _RealmCore {
final property = propertiesPtr.elementAt(i);
final propertyName = property.ref.name.cast<Utf8>().toRealmDartString()!;
final objectType = property.ref.link_target.cast<Utf8>().toRealmDartString(treatEmptyAsNull: true);
final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, RealmCollectionType.values.elementAt(property.ref.collection_type));
final isNullable = property.ref.flags & realm_property_flags.RLM_PROPERTY_NULLABLE != 0;
final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, RealmPropertyType.values.elementAt(property.ref.type), isNullable,
RealmCollectionType.values.elementAt(property.ref.collection_type));
result[propertyName] = propertyMeta;
}
return result;
Expand Down Expand Up @@ -2053,7 +2055,7 @@ extension on Pointer<realm_value_t> {
case realm_value_type.RLM_TYPE_INT:
return ref.values.integer;
case realm_value_type.RLM_TYPE_BOOL:
return ref.values.boolean != 0;
return ref.values.boolean;
Comment on lines -2056 to +2058
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be a change with the new ffi and seems like it was a bug - not sure how/why our tests didn't catch it, but likely warrants an investigation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a test and fixed it in #854. This can be left here as well

case realm_value_type.RLM_TYPE_STRING:
return ref.values.string.data.cast<Utf8>().toRealmDartString(length: ref.values.string.size)!;
case realm_value_type.RLM_TYPE_FLOAT:
Expand Down
4 changes: 2 additions & 2 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ extension RealmInternal on Realm {
return RealmObjectInternal.create(type, this, handle, accessor);
}

RealmList<T> createList<T extends Object>(RealmListHandle handle, RealmObjectMetadata? metadata) {
return RealmListInternal.create(handle, this, metadata);
RealmList<T> createList<T extends Object?>(RealmListHandle handle, RealmObjectMetadata? metadata) {
return RealmListInternal.create<T>(handle, this, metadata);
}

List<String> getPropertyNames(Type type, List<int> propertyKeys) {
Expand Down
120 changes: 111 additions & 9 deletions lib/src/realm_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@

import 'dart:async';
import 'dart:ffi';
import 'dart:mirrors';
nirinchev marked this conversation as resolved.
Show resolved Hide resolved

import 'list.dart';
import 'native/realm_core.dart';
import 'realm_class.dart';

typedef DartDynamic = dynamic;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we don't hide this under our own name

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a typedef - it's not going to affect users. The reason why I need to typedef it is to avoid a collision with the dynamic property on RealmObject.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't users be hit by the same?


abstract class RealmAccessor {
Object? get<T extends Object>(RealmObject object, String name);
Object? get<T extends Object?>(RealmObject object, String name);
void set(RealmObject object, String name, Object? value, {bool isDefault = false, bool update = false});

static final Map<Type, Map<String, Object?>> _defaultValues = <Type, Map<String, Object?>>{};
Expand Down Expand Up @@ -60,7 +63,7 @@ class RealmValuesAccessor implements RealmAccessor {
final Map<String, Object?> _values = <String, Object?>{};

@override
Object? get<T extends Object>(RealmObject object, String name) {
Object? get<T extends Object?>(RealmObject object, String name) {
if (!_values.containsKey(name)) {
return RealmAccessor.getDefaultValue(object.runtimeType, name);
}
Expand Down Expand Up @@ -119,8 +122,10 @@ class RealmObjectMetadata {
class RealmPropertyMetadata {
final int key;
final RealmCollectionType collectionType;
final RealmPropertyType propertyType;
final bool isNullable;
final String? objectType;
const RealmPropertyMetadata(this.key, this.objectType, [this.collectionType = RealmCollectionType.none]);
const RealmPropertyMetadata(this.key, this.objectType, this.propertyType, this.isNullable, [this.collectionType = RealmCollectionType.none]);
}

class RealmCoreAccessor implements RealmAccessor {
Expand All @@ -129,19 +134,27 @@ class RealmCoreAccessor implements RealmAccessor {
RealmCoreAccessor(this.metadata);

@override
Object? get<T extends Object>(RealmObject object, String name) {
Object? get<T extends Object?>(RealmObject object, String name) {
try {
final propertyMeta = metadata[name];
if (propertyMeta.collectionType == RealmCollectionType.list) {
final handle = realmCore.getListProperty(object, propertyMeta.key);
final listMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!);
if (listMetadata != null && _isTypeGenericObject<T>()) {
return object.realm.createList<RealmObject>(handle, listMetadata);
}

return object.realm.createList<T>(handle, listMetadata);
}

Object? value = realmCore.getProperty(object, propertyMeta.key);

if (value is RealmObjectHandle) {
final targetMetadata = propertyMeta.objectType != null ? object.realm.metadata.getByName(propertyMeta.objectType!) : object.realm.metadata.getByType(T);
if (_isTypeGenericObject<T>()) {
return object.realm.createObject(RealmObject, value, targetMetadata);
}

return object.realm.createObject(T, value, targetMetadata);
}

Expand Down Expand Up @@ -199,11 +212,12 @@ mixin RealmObject on RealmEntity implements Finalizable {
RealmObjectHandle? _handle;
RealmAccessor _accessor = RealmValuesAccessor();
static final Map<Type, RealmObject Function()> _factories = <Type, RealmObject Function()>{
RealmObject: () => DynamicRealmObject._(),
RealmObject: () => ConcreteRealmObject._(),
_typeOf<RealmObject?>(): () => ConcreteRealmObject._(),
};

/// @nodoc
static Object? get<T extends Object>(RealmObject object, String name) {
static Object? get<T extends Object?>(RealmObject object, String name) {
return object._accessor.get<T>(object, name);
}

Expand All @@ -213,7 +227,10 @@ mixin RealmObject on RealmEntity implements Finalizable {
}

/// @nodoc
static void registerFactory<T extends RealmObject>(T Function() factory) => _factories.putIfAbsent(T, () => factory);
static void registerFactory<T extends RealmObject>(T Function() factory) {
_factories.putIfAbsent(T, () => factory);
_factories.putIfAbsent(_typeOf<T?>(), () => factory);
}

/// @nodoc
static T create<T extends RealmObject>() {
Expand Down Expand Up @@ -261,6 +278,18 @@ mixin RealmObject on RealmEntity implements Finalizable {
final controller = RealmObjectNotificationsController<T>(object);
return controller.createStream();
}

@override
DartDynamic noSuchMethod(Invocation invocation) {
if (invocation.isGetter) {
final name = MirrorSystem.getName(invocation.memberName);
return get(this, name);
}

return super.noSuchMethod(invocation);
}

late final DynamicRealmObject dynamic = DynamicRealmObject._(this);
}

/// @nodoc
Expand Down Expand Up @@ -379,6 +408,79 @@ class RealmObjectNotificationsController<T extends RealmObject> extends Notifica
}

/// @nodoc
class DynamicRealmObject with RealmEntity, RealmObject {
DynamicRealmObject._();
class ConcreteRealmObject with RealmEntity, RealmObject {
ConcreteRealmObject._();
}

Type _typeOf<T>() => T;

bool _isTypeGenericObject<T>() => T == Object || T == _typeOf<Object?>();

class DynamicRealmObject {
final RealmObject _obj;

DynamicRealmObject._(this._obj);

T get<T extends Object?>(String name) {
_validatePropertyType<T>(name, RealmCollectionType.none);
return RealmObject.get<T>(_obj, name) as T;
}

List<T> getList<T extends Object?>(String name) {
_validatePropertyType<T>(name, RealmCollectionType.list);
return RealmObject.get<T>(_obj, name) as List<T>;
}

RealmPropertyMetadata? _validatePropertyType<T extends Object?>(String name, RealmCollectionType expectedCollectionType) {
final accessor = _obj.accessor;
if (accessor is RealmCoreAccessor) {
final prop = accessor.metadata._propertyKeys[name];
if (prop == null) {
throw RealmException("Property '$name' does not exist on class '${accessor.metadata.name}'");
}

if (prop.collectionType != expectedCollectionType) {
throw RealmException(
"Property '$name' on class '${accessor.metadata.name}' is '${prop.collectionType}' but the method used to access it expected '$expectedCollectionType'.");
}

// If the user passed in a type argument, we should validate its nullability; if they invoked
// the method without a type arg, we don't
if (T != _typeOf<Object?>() && prop.isNullable != null is T) {
throw RealmException(
"Property '$name' on class '${accessor.metadata.name}' is ${prop.isNullable ? 'nullable' : 'required'} but the generic argument passed to get<T> is $T.");
}

final targetType = _getPropertyType<T>();
if (targetType != null && targetType != prop.propertyType) {
throw RealmException(
"Property '$name' on class '${accessor.metadata.name}' is not the correct type. Expected '$targetType', got '${prop.propertyType}'.");
}

return prop;
}

return null;
}

static final _propertyTypeMap = <Type, RealmPropertyType>{
int: RealmPropertyType.int,
_typeOf<int?>(): RealmPropertyType.int,
double: RealmPropertyType.double,
_typeOf<double?>(): RealmPropertyType.double,
String: RealmPropertyType.string,
_typeOf<String?>(): RealmPropertyType.string,
bool: RealmPropertyType.bool,
_typeOf<bool?>(): RealmPropertyType.bool,
DateTime: RealmPropertyType.timestamp,
_typeOf<DateTime?>(): RealmPropertyType.timestamp,
ObjectId: RealmPropertyType.objectid,
_typeOf<ObjectId?>(): RealmPropertyType.objectid,
Uuid: RealmPropertyType.uuid,
_typeOf<Uuid?>(): RealmPropertyType.uuid,
RealmObject: RealmPropertyType.object,
_typeOf<RealmObject?>(): RealmPropertyType.object,
};

RealmPropertyType? _getPropertyType<T extends Object?>() => _propertyTypeMap[T];
}
Loading