Skip to content

Commit 2dd3bff

Browse files
nirinchevKasper Overgård Nielsen
andauthored
RDART-1021: Wire up some basic dynamic setting and change notifications (#1669)
* Wire up some basic dynamic setting and change notifications * Add changes for object collections * Add setter tests * Changelog, more tests * Update packages/realm_dart/lib/src/realm_object.dart * CR comments --------- Co-authored-by: Kasper Overgård Nielsen <[email protected]>
1 parent 4f55de7 commit 2dd3bff

File tree

6 files changed

+414
-16
lines changed

6 files changed

+414
-16
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
## vNext (TBD)
22

33
### Enhancements
4+
* Added support for creating and storing a RealmObject using the `Realm.dynamic` API: `realm.dynamic.create("Person", primaryKey: 123)`. (PR [#1669](https://github.com/realm/realm-dart/pull/1669))
5+
* Added support for setting properties on a RealmObject using the dynamic API: `obj.dynamic.set("name", "Peter")`. (PR [#1669](https://github.com/realm/realm-dart/pull/1669))
6+
* Listening for `.changes` on a dynamic object (obtained via the `realm.dynamic` API) no longer throws. (Issue [#1668](https://github.com/realm/realm-dart/issues/1668))
47
* Nested collections have full support for automatic client reset. (Core 14.7.0)
58

69
### Fixed

packages/realm_dart/lib/src/realm_class.dart

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -750,8 +750,8 @@ extension RealmInternal on Realm {
750750
return RealmMapInternal.create<T>(handle, this, metadata);
751751
}
752752

753-
List<String> getPropertyNames(Type type, List<int> propertyKeys) {
754-
final metadata = _metadata.getByType(type);
753+
List<String> getPropertyNames(RealmObjectBase obj, List<int> propertyKeys) {
754+
final metadata = _metadata.tryGetByType(obj.runtimeType) ?? _metadata.getByName(obj.objectSchema.name);
755755
final result = <String>[];
756756
for (var key in propertyKeys) {
757757
final name = metadata.getPropertyName(key);
@@ -907,6 +907,8 @@ class RealmMetadata {
907907
return metadata;
908908
}
909909

910+
RealmObjectMetadata? tryGetByType(Type type) => _typeMap[type];
911+
910912
RealmObjectMetadata getByName(String type) {
911913
var metadata = _stringMap[type];
912914
if (metadata == null) {
@@ -963,6 +965,29 @@ class DynamicRealm {
963965
final accessor = RealmCoreAccessor(metadata, _realm._isInMigration);
964966
return RealmObjectInternal.create(RealmObject, _realm, handle, accessor) as RealmObject;
965967
}
968+
969+
/// Creates a managed RealmObject with the specified [className] and [primaryKey].
970+
RealmObject create(String className, {Object? primaryKey}) {
971+
final metadata = _realm._metadata.getByName(className);
972+
973+
RealmObjectHandle handle;
974+
if (metadata.primaryKey != null) {
975+
if (primaryKey == null) {
976+
throw RealmError("The class $className has primary key defined, but you didn't pass one");
977+
}
978+
979+
handle = realmCore.createRealmObjectWithPrimaryKey(_realm, metadata.classKey, primaryKey);
980+
} else {
981+
if (primaryKey != null) {
982+
throw RealmError("The class $className doesn't have primary key defined, but you passed $primaryKey");
983+
}
984+
985+
handle = realmCore.createRealmObject(_realm, metadata.classKey);
986+
}
987+
988+
final accessor = RealmCoreAccessor(metadata, _realm._isInMigration);
989+
return RealmObjectInternal.create(RealmObject, _realm, handle, accessor) as RealmObject;
990+
}
966991
}
967992

968993
/// A class used during a migration callback. It exposes a set of dynamic API as

packages/realm_dart/lib/src/realm_object.dart

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ class RealmCoreAccessor implements RealmAccessor {
246246
value = object.realm.createObject(type, value, targetMetadata);
247247
}
248248

249-
if (T == RealmValue) {
249+
if (T == RealmValue || (propertyMeta.propertyType == RealmPropertyType.mixed && _isTypeGenericObject<T>())) {
250250
value = RealmValue.from(value);
251251
}
252252

@@ -381,7 +381,7 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab
381381
}
382382

383383
/// @nodoc
384-
static void set<T extends Object>(RealmObjectBase object, String name, T? value, {bool update = false}) {
384+
static void set<T>(RealmObjectBase object, String name, T value, {bool update = false}) {
385385
object._accessor.set(object, name, value, update: update);
386386
}
387387

@@ -605,10 +605,16 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab
605605
}
606606

607607
/// @nodoc
608-
mixin RealmObject on RealmObjectBase implements RealmObjectMarker {}
608+
mixin RealmObject on RealmObjectBase implements RealmObjectMarker {
609+
@override
610+
Stream<RealmObjectChanges<RealmObject>> get changes => throw RealmError("Invalid usage. Use the generated inheritors of RealmObject");
611+
}
609612

610613
/// @nodoc
611-
mixin EmbeddedObject on RealmObjectBase implements EmbeddedObjectMarker {}
614+
mixin EmbeddedObject on RealmObjectBase implements EmbeddedObjectMarker {
615+
@override
616+
Stream<RealmObjectChanges<EmbeddedObject>> get changes => throw RealmError("Invalid usage. Use the generated inheritors of EmbeddedObject");
617+
}
612618

613619
/// Base for any object that can be persisted in a [Realm], but cannot be retrieved,
614620
/// hence cannot be modified.
@@ -731,7 +737,7 @@ class RealmObjectChanges<T extends RealmObjectBase> implements Finalizable {
731737
List<String> get properties {
732738
final propertyKeys = realmCore.getObjectChangesProperties(_handle);
733739
return object.realm
734-
.getPropertyNames(object.runtimeType, propertyKeys)
740+
.getPropertyNames(object, propertyKeys)
735741
.map((e) => object.objectSchema.firstWhere((element) => element.mapTo == e || element.name == e).name)
736742
.toList();
737743
}
@@ -794,12 +800,18 @@ class RealmObjectNotificationsController<T extends RealmObjectBase> extends Noti
794800
class _ConcreteRealmObject with RealmEntity, RealmObjectBase, RealmObject {
795801
@override
796802
SchemaObject get objectSchema => RealmObjectBase.getSchema(this)!; // _ConcreteRealmObject should only ever be created for managed objects
803+
804+
@override
805+
Stream<RealmObjectChanges<RealmObject>> get changes => RealmObjectBase.getChanges<RealmObject>(this);
797806
}
798807

799808
/// @nodoc
800809
class _ConcreteEmbeddedObject with RealmEntity, RealmObjectBase, EmbeddedObject {
801810
@override
802811
SchemaObject get objectSchema => RealmObjectBase.getSchema(this)!; // _ConcreteEmbeddedObject should only ever be created for managed objects
812+
813+
@override
814+
Stream<RealmObjectChanges<EmbeddedObject>> get changes => RealmObjectBase.getChanges<EmbeddedObject>(this);
803815
}
804816

805817
// This is necessary whenever we need to pass T? as the type.
@@ -824,6 +836,13 @@ class DynamicRealmObject {
824836
return RealmObjectBase.get<T>(_obj, name) as T;
825837
}
826838

839+
/// Sets a property by its name. The supplied [value] must be assignable
840+
/// to the property type, otherwise an exception will be thrown.
841+
void set<T extends Object?>(String name, T value) {
842+
_validatePropertyType<T>(name, RealmCollectionType.none, relaxedNullability: true);
843+
RealmObjectBase.set(_obj, name, value);
844+
}
845+
827846
/// Gets a list by the property name. If a generic type is specified, the property
828847
/// type will be validated against the type. Otherwise, a `List<Object>` will be
829848
/// returned.
@@ -848,7 +867,7 @@ class DynamicRealmObject {
848867
return RealmObjectBase.get<T>(_obj, name) as RealmMap<T>;
849868
}
850869

851-
RealmPropertyMetadata? _validatePropertyType<T extends Object?>(String name, RealmCollectionType expectedCollectionType) {
870+
RealmPropertyMetadata? _validatePropertyType<T extends Object?>(String name, RealmCollectionType expectedCollectionType, {bool relaxedNullability = false}) {
852871
final accessor = _obj.accessor;
853872
if (accessor is RealmCoreAccessor) {
854873
final prop = accessor.metadata._propertyKeys[name];
@@ -864,8 +883,15 @@ class DynamicRealmObject {
864883
// If the user passed in a type argument, we should validate its nullability; if they invoked
865884
// the method without a type arg, we don't
866885
if (T != _typeOf<RealmValue>() && T != _typeOf<Object?>() && prop.isNullable != null is T) {
867-
throw RealmException(
868-
"Property '$name' on class '${accessor.metadata.schema.name}' is ${prop.isNullable ? 'nullable' : 'required'} but the generic argument passed to get<T> is $T.");
886+
if (relaxedNullability && prop.isNullable) {
887+
// We're relaxing nullability requirements when setting a property - in that case, we accept
888+
// a non-null generic type argument, even if the property is nullable to allow users to invoke
889+
// .set without a generic argument (i.e. have the compiler infer the generic based on the value
890+
// argument).
891+
} else {
892+
throw RealmException(
893+
"Property '$name' on class '${accessor.metadata.schema.name}' is ${prop.isNullable ? 'nullable' : 'required'} but the generic argument supplied is $T.");
894+
}
869895
}
870896

871897
final targetType = _getPropertyType<T>();

0 commit comments

Comments
 (0)