-
Notifications
You must be signed in to change notification settings - Fork 95
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
Changes from 2 commits
15cd753
1cc9772
17de27e
ce435e0
82e75bb
01cd0e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we don't hide this under our own name There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?>>{}; | ||
|
@@ -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); | ||
} | ||
|
@@ -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 { | ||
|
@@ -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); | ||
} | ||
|
||
|
@@ -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); | ||
} | ||
|
||
|
@@ -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>() { | ||
|
@@ -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 | ||
|
@@ -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]; | ||
} |
There was a problem hiding this comment.
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.