diff --git a/CHANGELOG.md b/CHANGELOG.md index b17bd24cf..463ea4740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## vNext (TBD) ### Enhancements +* 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)) +* 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)) +* 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)) * Nested collections have full support for automatic client reset. (Core 14.7.0) ### Fixed diff --git a/packages/realm_dart/lib/src/realm_class.dart b/packages/realm_dart/lib/src/realm_class.dart index 179ce3c49..79427fe08 100644 --- a/packages/realm_dart/lib/src/realm_class.dart +++ b/packages/realm_dart/lib/src/realm_class.dart @@ -750,8 +750,8 @@ extension RealmInternal on Realm { return RealmMapInternal.create(handle, this, metadata); } - List getPropertyNames(Type type, List propertyKeys) { - final metadata = _metadata.getByType(type); + List getPropertyNames(RealmObjectBase obj, List propertyKeys) { + final metadata = _metadata.tryGetByType(obj.runtimeType) ?? _metadata.getByName(obj.objectSchema.name); final result = []; for (var key in propertyKeys) { final name = metadata.getPropertyName(key); @@ -907,6 +907,8 @@ class RealmMetadata { return metadata; } + RealmObjectMetadata? tryGetByType(Type type) => _typeMap[type]; + RealmObjectMetadata getByName(String type) { var metadata = _stringMap[type]; if (metadata == null) { @@ -963,6 +965,29 @@ class DynamicRealm { final accessor = RealmCoreAccessor(metadata, _realm._isInMigration); return RealmObjectInternal.create(RealmObject, _realm, handle, accessor) as RealmObject; } + + /// Creates a managed RealmObject with the specified [className] and [primaryKey]. + RealmObject create(String className, {Object? primaryKey}) { + final metadata = _realm._metadata.getByName(className); + + RealmObjectHandle handle; + if (metadata.primaryKey != null) { + if (primaryKey == null) { + throw RealmError("The class $className has primary key defined, but you didn't pass one"); + } + + handle = realmCore.createRealmObjectWithPrimaryKey(_realm, metadata.classKey, primaryKey); + } else { + if (primaryKey != null) { + throw RealmError("The class $className doesn't have primary key defined, but you passed $primaryKey"); + } + + handle = realmCore.createRealmObject(_realm, metadata.classKey); + } + + final accessor = RealmCoreAccessor(metadata, _realm._isInMigration); + return RealmObjectInternal.create(RealmObject, _realm, handle, accessor) as RealmObject; + } } /// A class used during a migration callback. It exposes a set of dynamic API as diff --git a/packages/realm_dart/lib/src/realm_object.dart b/packages/realm_dart/lib/src/realm_object.dart index 184350291..b65dd715a 100644 --- a/packages/realm_dart/lib/src/realm_object.dart +++ b/packages/realm_dart/lib/src/realm_object.dart @@ -246,7 +246,7 @@ class RealmCoreAccessor implements RealmAccessor { value = object.realm.createObject(type, value, targetMetadata); } - if (T == RealmValue) { + if (T == RealmValue || (propertyMeta.propertyType == RealmPropertyType.mixed && _isTypeGenericObject())) { value = RealmValue.from(value); } @@ -381,7 +381,7 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab } /// @nodoc - static void set(RealmObjectBase object, String name, T? value, {bool update = false}) { + static void set(RealmObjectBase object, String name, T value, {bool update = false}) { object._accessor.set(object, name, value, update: update); } @@ -605,10 +605,16 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab } /// @nodoc -mixin RealmObject on RealmObjectBase implements RealmObjectMarker {} +mixin RealmObject on RealmObjectBase implements RealmObjectMarker { + @override + Stream> get changes => throw RealmError("Invalid usage. Use the generated inheritors of RealmObject"); +} /// @nodoc -mixin EmbeddedObject on RealmObjectBase implements EmbeddedObjectMarker {} +mixin EmbeddedObject on RealmObjectBase implements EmbeddedObjectMarker { + @override + Stream> get changes => throw RealmError("Invalid usage. Use the generated inheritors of EmbeddedObject"); +} /// Base for any object that can be persisted in a [Realm], but cannot be retrieved, /// hence cannot be modified. @@ -731,7 +737,7 @@ class RealmObjectChanges implements Finalizable { List get properties { final propertyKeys = realmCore.getObjectChangesProperties(_handle); return object.realm - .getPropertyNames(object.runtimeType, propertyKeys) + .getPropertyNames(object, propertyKeys) .map((e) => object.objectSchema.firstWhere((element) => element.mapTo == e || element.name == e).name) .toList(); } @@ -794,12 +800,18 @@ class RealmObjectNotificationsController extends Noti class _ConcreteRealmObject with RealmEntity, RealmObjectBase, RealmObject { @override SchemaObject get objectSchema => RealmObjectBase.getSchema(this)!; // _ConcreteRealmObject should only ever be created for managed objects + + @override + Stream> get changes => RealmObjectBase.getChanges(this); } /// @nodoc class _ConcreteEmbeddedObject with RealmEntity, RealmObjectBase, EmbeddedObject { @override SchemaObject get objectSchema => RealmObjectBase.getSchema(this)!; // _ConcreteEmbeddedObject should only ever be created for managed objects + + @override + Stream> get changes => RealmObjectBase.getChanges(this); } // This is necessary whenever we need to pass T? as the type. @@ -824,6 +836,13 @@ class DynamicRealmObject { return RealmObjectBase.get(_obj, name) as T; } + /// Sets a property by its name. The supplied [value] must be assignable + /// to the property type, otherwise an exception will be thrown. + void set(String name, T value) { + _validatePropertyType(name, RealmCollectionType.none, relaxedNullability: true); + RealmObjectBase.set(_obj, name, value); + } + /// Gets a list by the property name. If a generic type is specified, the property /// type will be validated against the type. Otherwise, a `List` will be /// returned. @@ -848,7 +867,7 @@ class DynamicRealmObject { return RealmObjectBase.get(_obj, name) as RealmMap; } - RealmPropertyMetadata? _validatePropertyType(String name, RealmCollectionType expectedCollectionType) { + RealmPropertyMetadata? _validatePropertyType(String name, RealmCollectionType expectedCollectionType, {bool relaxedNullability = false}) { final accessor = _obj.accessor; if (accessor is RealmCoreAccessor) { final prop = accessor.metadata._propertyKeys[name]; @@ -864,8 +883,15 @@ class DynamicRealmObject { // 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() && T != _typeOf() && prop.isNullable != null is T) { - throw RealmException( - "Property '$name' on class '${accessor.metadata.schema.name}' is ${prop.isNullable ? 'nullable' : 'required'} but the generic argument passed to get is $T."); + if (relaxedNullability && prop.isNullable) { + // We're relaxing nullability requirements when setting a property - in that case, we accept + // a non-null generic type argument, even if the property is nullable to allow users to invoke + // .set without a generic argument (i.e. have the compiler infer the generic based on the value + // argument). + } else { + throw RealmException( + "Property '$name' on class '${accessor.metadata.schema.name}' is ${prop.isNullable ? 'nullable' : 'required'} but the generic argument supplied is $T."); + } } final targetType = _getPropertyType(); diff --git a/packages/realm_dart/test/dynamic_realm_test.dart b/packages/realm_dart/test/dynamic_realm_test.dart index ba20e3713..e5c218412 100644 --- a/packages/realm_dart/test/dynamic_realm_test.dart +++ b/packages/realm_dart/test/dynamic_realm_test.dart @@ -115,7 +115,8 @@ void main() { nullableObjectIdProp: objectId, nullableUuidProp: uuid, nullableIntProp: 123, - nullableDecimalProp: Decimal128.fromDouble(4242)); + nullableDecimalProp: Decimal128.fromDouble(4242), + realmValueProp: RealmValue.from('value')); AllTypes _getEmptyAllTypes() => AllTypes('', false, DateTime(0).toUtc(), 0, objectId, uuid, 0, Decimal128.zero, Uint8List(16)); @@ -205,6 +206,9 @@ void main() { expect(actual.dynamic.get('nullableDecimalProp'), expected.nullableDecimalProp); expect(actual.dynamic.get('nullableDecimalProp'), expected.nullableDecimalProp); + expect(actual.dynamic.get('realmValueProp'), expected.realmValueProp); + expect(actual.dynamic.get('realmValueProp'), expected.realmValueProp); + dynamic actualDynamic = actual; expect(actualDynamic.stringProp, expected.stringProp); expect(actualDynamic.nullableStringProp, expected.nullableStringProp); @@ -222,6 +226,97 @@ void main() { expect(actualDynamic.nullableIntProp, expected.nullableIntProp); expect(actualDynamic.decimalProp, expected.decimalProp); expect(actualDynamic.nullableDecimalProp, expected.nullableDecimalProp); + expect(actualDynamic.realmValueProp, expected.realmValueProp); + } + + void _validateDynamicSetters(RealmObject actual, AllTypes expected) { + final oid = ObjectId(); + final uuid = Uuid.v4(); + actual.realm.write(() { + actual.dynamic.set('stringProp', 'updated abc'); + actual.dynamic.set('nullableStringProp', 'updated abc'); + + actual.dynamic.set('boolProp', false); + actual.dynamic.set('nullableBoolProp', false); + + actual.dynamic.set('dateProp', DateTime.utc(1999)); + actual.dynamic.set('nullableDateProp', DateTime.utc(1999)); + + actual.dynamic.set('doubleProp', -999.111); + actual.dynamic.set('nullableDoubleProp', 111.999); + + actual.dynamic.set('objectIdProp', oid); + actual.dynamic.set('nullableObjectIdProp', oid); + + actual.dynamic.set('uuidProp', uuid); + actual.dynamic.set('nullableUuidProp', uuid); + + actual.dynamic.set('intProp', 42); + actual.dynamic.set('nullableIntProp', -42); + + actual.dynamic.set('decimalProp', Decimal128.fromDouble(500)); + actual.dynamic.set('nullableDecimalProp', Decimal128.infinity); + + actual.dynamic.set('realmValueProp', RealmValue.from([true, 5])); + }); + + expect(actual.dynamic.get('stringProp'), 'updated abc'); + expect(actual.dynamic.get('nullableStringProp'), 'updated abc'); + expect(actual.dynamic.get('boolProp'), false); + expect(actual.dynamic.get('nullableBoolProp'), false); + expect(actual.dynamic.get('dateProp'), DateTime.utc(1999)); + expect(actual.dynamic.get('nullableDateProp'), DateTime.utc(1999)); + expect(actual.dynamic.get('doubleProp'), -999.111); + expect(actual.dynamic.get('nullableDoubleProp'), 111.999); + expect(actual.dynamic.get('objectIdProp'), oid); + expect(actual.dynamic.get('nullableObjectIdProp'), oid); + expect(actual.dynamic.get('uuidProp'), uuid); + expect(actual.dynamic.get('nullableUuidProp'), uuid); + expect(actual.dynamic.get('intProp'), 42); + expect(actual.dynamic.get('nullableIntProp'), -42); + expect(actual.dynamic.get('decimalProp'), Decimal128.fromDouble(500)); + expect(actual.dynamic.get('nullableDecimalProp'), Decimal128.infinity); + expect(actual.dynamic.get('realmValueProp').asList().map((e) => e.value), [true, 5]); + + dynamic actualDynamic = actual; + + actual.realm.write(() { + actualDynamic.stringProp = expected.stringProp; + actualDynamic.nullableStringProp = expected.nullableStringProp; + actualDynamic.boolProp = expected.boolProp; + actualDynamic.nullableBoolProp = expected.nullableBoolProp; + actualDynamic.dateProp = expected.dateProp; + actualDynamic.nullableDateProp = expected.nullableDateProp; + actualDynamic.doubleProp = expected.doubleProp; + actualDynamic.nullableDoubleProp = expected.nullableDoubleProp; + actualDynamic.objectIdProp = expected.objectIdProp; + actualDynamic.nullableObjectIdProp = expected.nullableObjectIdProp; + actualDynamic.uuidProp = expected.uuidProp; + actualDynamic.nullableUuidProp = expected.nullableUuidProp; + actualDynamic.intProp = expected.intProp; + actualDynamic.nullableIntProp = expected.nullableIntProp; + actualDynamic.decimalProp = expected.decimalProp; + actualDynamic.nullableDecimalProp = expected.nullableDecimalProp; + actualDynamic.realmValueProp = expected.realmValueProp; + }); + + expect(actualDynamic.stringProp, expected.stringProp); + expect(actualDynamic.nullableStringProp, expected.nullableStringProp); + expect(actualDynamic.boolProp, expected.boolProp); + expect(actualDynamic.nullableBoolProp, expected.nullableBoolProp); + expect(actualDynamic.dateProp, expected.dateProp); + expect(actualDynamic.nullableDateProp, expected.nullableDateProp); + expect(actualDynamic.doubleProp, expected.doubleProp); + expect(actualDynamic.nullableDoubleProp, expected.nullableDoubleProp); + expect(actualDynamic.objectIdProp, expected.objectIdProp); + expect(actualDynamic.nullableObjectIdProp, expected.nullableObjectIdProp); + expect(actualDynamic.uuidProp, expected.uuidProp); + expect(actualDynamic.nullableUuidProp, expected.nullableUuidProp); + expect(actualDynamic.intProp, expected.intProp); + expect(actualDynamic.nullableIntProp, expected.nullableIntProp); + expect(actualDynamic.decimalProp, expected.decimalProp); + expect(actualDynamic.nullableDecimalProp, expected.nullableDecimalProp); + expect(actualDynamic.realmValueProp, expected.realmValueProp); } void _validateDynamicCollections(RealmObject actual, AllCollections expected) { @@ -576,8 +671,8 @@ void main() { }); }); - group('RealmObject.dynamic.get when isDynamic=$isDynamic', () { - test('gets all property types', () { + group('RealmObject.dynamic.get/set when isDynamic=$isDynamic', () { + test('works for all property types', () { final config = Configuration.local([AllTypes.schema]); final staticRealm = getRealm(config); @@ -597,9 +692,10 @@ void main() { _validateDynamic(obj1, _getPopulatedAllTypes()); _validateDynamic(obj2, _getEmptyAllTypes()); + _validateDynamicSetters(obj1, _getPopulatedAllTypes()); }); - test('gets normal links', () { + test('works for normal links', () { final config = Configuration.local([LinksClass.schema]); final staticRealm = getRealm(config); @@ -633,6 +729,22 @@ void main() { expect(dynamicObj2.link.id, uuid1); assertSchemaMatches(dynamicObj2.link.objectSchema as SchemaObject, LinksClass.schema); + + dynamicRealm.write(() { + obj1.dynamic.set('link', obj1); + obj2.dynamic.set('link', null); + }); + + expect(obj1.dynamic.get('link'), obj1); + expect(obj2.dynamic.get('link'), isNull); + + dynamicRealm.write(() { + dynamicObj1.link = null; + dynamicObj2.link = obj1; + }); + + expect(dynamicObj1.link, isNull); + expect(dynamicObj2.link, obj1); }); test('fails with non-existent property', () { @@ -647,6 +759,11 @@ void main() { dynamic dynamicObj = obj; expect(() => obj.dynamic.get('i-dont-exist'), throws("Property 'i-dont-exist' does not exist on class 'AllTypes'")); expect(() => dynamicObj.idontexist, throws("Property idontexist does not exist on class AllTypes")); + + dynamicRealm.write(() { + expect(() => obj.dynamic.set('i-dont-exist', true), throws("Property 'i-dont-exist' does not exist on class 'AllTypes'")); + expect(() => dynamicObj.idontexist = 5, throws("Property idontexist does not exist on class AllTypes")); + }); }); test('fails with wrong type', () { @@ -670,10 +787,34 @@ void main() { "Property 'nullableStringProp' on class 'AllTypes' is not the correct type. Expected 'RealmPropertyType.int', got 'RealmPropertyType.string'.")); expect(() => obj.dynamic.get('nullableIntProp'), - throws("Property 'nullableIntProp' on class 'AllTypes' is nullable but the generic argument passed to get is int.")); + throws("Property 'nullableIntProp' on class 'AllTypes' is nullable but the generic argument supplied is int.")); expect(() => obj.dynamic.get('intProp'), - throws("Property 'intProp' on class 'AllTypes' is required but the generic argument passed to get is int?.")); + throws("Property 'intProp' on class 'AllTypes' is required but the generic argument supplied is int?.")); + + dynamic dynamicObj = obj; + dynamicRealm.write(() { + expect( + () => obj.dynamic.set('stringProp', 5), + throws( + "Property 'stringProp' on class 'AllTypes' is not the correct type. Expected 'RealmPropertyType.int', got 'RealmPropertyType.string'.")); + + expect( + () => obj.dynamic.set('nullableStringProp', 5), + throws( + "Property 'nullableStringProp' on class 'AllTypes' is not the correct type. Expected 'RealmPropertyType.int', got 'RealmPropertyType.string'.")); + + expect(() => obj.dynamic.set('intProp', null), + throws("Property 'intProp' on class 'AllTypes' is required but the generic argument supplied is int?.")); + + expect(() => dynamicObj.stringProp = true, throws("Type mismatch for property 'AllTypes.stringProp'")); + expect(() => dynamicObj.nullableStringProp = 5, throws("Type mismatch for property 'AllTypes.nullableStringProp'")); + expect(() => dynamicObj.intProp = null, throws("Invalid null value for non-nullable property 'AllTypes.intProp'")); + + // Passing a non-null value to nullable property should be fine + expect(() => obj.dynamic.set('nullableIntProp', 999), returnsNormally); + expect(() => dynamicObj.nullableIntProp = 1000, returnsNormally); + }); }); test('fails on collection properties', () { @@ -699,6 +840,22 @@ void main() { () => obj.dynamic.get('stringList'), throws( "Property 'stringList' on class 'AllCollections' is 'RealmCollectionType.list' but the method used to access it expected 'RealmCollectionType.none'.")); + + dynamic dynamicObj = obj; + dynamicRealm.write(() { + expect( + () => obj.dynamic.set('stringList', 5), + throws( + "Property 'stringList' on class 'AllCollections' is 'RealmCollectionType.list' but the method used to access it expected 'RealmCollectionType.none'.")); + + expect( + () => obj.dynamic.set('stringList', null), + throws( + "Property 'stringList' on class 'AllCollections' is 'RealmCollectionType.list' but the method used to access it expected 'RealmCollectionType.none'.")); + + expect(() => dynamicObj.stringList = 5, throws("Type mismatch for property 'AllCollections.stringList'")); + expect(() => dynamicObj.stringList = null, throws("Type mismatch for property 'AllCollections.stringList'")); + }); }); }); @@ -834,6 +991,178 @@ void main() { }); } }); + + group('.changes when isDynamic=$isDynamic', () { + test('Returns stream for objects', () async { + final config = Configuration.local( + [ObjectWithEmbedded.schema, AllTypesEmbedded.schema, RecursiveEmbedded1.schema, RecursiveEmbedded2.schema, RecursiveEmbedded3.schema]); + + final staticRealm = getRealm(config); + staticRealm.write(() { + staticRealm.add(ObjectWithEmbedded('abc', recursiveObject: RecursiveEmbedded1('child 1'))); + }); + final dynamicRealm = _getDynamicRealm(staticRealm); + + final toplevelChanges = >[]; + final embeddedChanges = >[]; + final topLevel = dynamicRealm.dynamic.all(ObjectWithEmbedded.schema.name).single; + final embedded = topLevel.dynamic.get('recursiveObject')!; + + final topLevelSubscription = topLevel.changes.listen((event) { + toplevelChanges.add(event); + }); + + final embeddedSubscription = embedded.changes.listen((event) { + embeddedChanges.add(event); + }); + + final newUuid = Uuid.v4(); + dynamicRealm.write(() { + topLevel.dynamic.set('differentiator', newUuid); + embedded.dynamic.set('value', 'updated child 1'); + }); + + await Future.delayed(Duration(milliseconds: 20)); + + expect(topLevel.dynamic.get('differentiator'), newUuid); + expect(embedded.dynamic.get('value'), 'updated child 1'); + + expect(toplevelChanges, hasLength(2)); + expect(toplevelChanges[0].properties, isEmpty); // First notification is delivered with empty properties + expect(toplevelChanges[1].object, topLevel); + expect(toplevelChanges[1].properties, hasLength(1)); + expect(toplevelChanges[1].properties[0], 'differentiator'); + + expect(embeddedChanges, hasLength(2)); + expect(embeddedChanges[0].properties, isEmpty); // First notification is delivered with empty properties + expect(embeddedChanges[1].object, embedded); + expect(embeddedChanges[1].properties, hasLength(1)); + expect(embeddedChanges[1].properties[0], 'value'); + + topLevelSubscription.cancel(); + embeddedSubscription.cancel(); + }); + + test('Returns stream for collection of primitives', () async { + final config = Configuration.local([AllCollections.schema]); + final staticRealm = getRealm(config); + staticRealm.write(() { + staticRealm.add(_getPopulatedAllCollections()); + }); + + final dynamicRealm = _getDynamicRealm(staticRealm); + final obj = dynamicRealm.dynamic.all(AllCollections.schema.name).single; + + final listChanges = >[]; + final setChanges = >[]; + final mapChanges = >[]; + + final list = obj.dynamic.getList('stringList'); + final listSub = list.changes.listen((event) { + listChanges.add(event); + }); + + final set = obj.dynamic.getSet('doubleSet'); + final setSub = set.changes.listen((event) { + setChanges.add(event); + }); + + final map = obj.dynamic.getMap('nullableUuidMap'); + final mapSub = map.changes.listen((event) { + mapChanges.add(event); + }); + + dynamicRealm.write(() { + list[0] = 'new string'; + set.clear(); + map['new map value'] = null; + }); + + await Future.delayed(Duration(milliseconds: 20)); + + expect(listChanges, hasLength(2)); + expect(listChanges[1].inserted, isEmpty); + expect(listChanges[1].deleted, isEmpty); + expect(listChanges[1].modified, [0]); + + expect(setChanges, hasLength(2)); + expect(setChanges[1].deleted, [0, 1]); + expect(setChanges[1].inserted, isEmpty); + expect(setChanges[1].modified, isEmpty); + + expect(mapChanges, hasLength(2)); + expect(mapChanges[1].inserted, ['new map value']); + expect(mapChanges[1].deleted, isEmpty); + expect(mapChanges[1].modified, isEmpty); + + listSub.cancel(); + setSub.cancel(); + mapSub.cancel(); + }); + + test('Returns stream for collection of objects', () async { + final config = Configuration.local([LinksClass.schema]); + final staticRealm = getRealm(config); + + final uuid1 = Uuid.v4(); + final uuid2 = Uuid.v4(); + + staticRealm.write(() { + final obj1 = staticRealm.add(LinksClass(uuid1)); + staticRealm.add(LinksClass(uuid2, list: [obj1, obj1], linksSet: {obj1}, map: {'a': obj1, 'b': obj1})); + }); + + final dynamicRealm = _getDynamicRealm(staticRealm); + + final obj = dynamicRealm.dynamic.find(LinksClass.schema.name, uuid2)!; + + final listChanges = >[]; + final setChanges = >[]; + final mapChanges = >[]; + + final list = obj.dynamic.getList('list'); + final listSub = list.changes.listen((event) { + listChanges.add(event); + }); + + final set = obj.dynamic.getSet('linksSet'); + final setSub = set.changes.listen((event) { + setChanges.add(event); + }); + + final map = obj.dynamic.getMap('map'); + final mapSub = map.changes.listen((event) { + mapChanges.add(event); + }); + + dynamicRealm.write(() { + list[0] = dynamicRealm.dynamic.create(LinksClass.schema.name, primaryKey: Uuid.v4()); + set.clear(); + map['new map value'] = null; + }); + + await Future.delayed(Duration(milliseconds: 20)); + + expect(listChanges, hasLength(2)); + expect(listChanges[1].inserted, isEmpty); + expect(listChanges[1].deleted, isEmpty); + expect(listChanges[1].modified, [0]); + + expect(setChanges, hasLength(2)); + expect(setChanges[1].deleted, [0]); + expect(setChanges[1].inserted, isEmpty); + expect(setChanges[1].modified, isEmpty); + + expect(mapChanges, hasLength(2)); + expect(mapChanges[1].inserted, ['new map value']); + expect(mapChanges[1].deleted, isEmpty); + expect(mapChanges[1].modified, isEmpty); + + listSub.cancel(); + setSub.cancel(); + mapSub.cancel(); + }); + }); } test('RealmObject.dynamic.get when static can get all property types', () { diff --git a/packages/realm_dart/test/test.dart b/packages/realm_dart/test/test.dart index df220a3a7..b7682133e 100644 --- a/packages/realm_dart/test/test.dart +++ b/packages/realm_dart/test/test.dart @@ -134,6 +134,8 @@ class _AllTypes { late int? nullableIntProp; late Decimal128? nullableDecimalProp; late Uint8List? nullableBinaryProp; + + late RealmValue realmValueProp; } @RealmModel() diff --git a/packages/realm_dart/test/test.realm.dart b/packages/realm_dart/test/test.realm.dart index cd61bbaf4..a4f300f2a 100644 --- a/packages/realm_dart/test/test.realm.dart +++ b/packages/realm_dart/test/test.realm.dart @@ -860,6 +860,7 @@ class AllTypes extends _AllTypes int? nullableIntProp, Decimal128? nullableDecimalProp, Uint8List? nullableBinaryProp, + RealmValue realmValueProp = const RealmValue.nullValue(), }) { RealmObjectBase.set(this, 'stringProp', stringProp); RealmObjectBase.set(this, 'boolProp', boolProp); @@ -879,6 +880,7 @@ class AllTypes extends _AllTypes RealmObjectBase.set(this, 'nullableIntProp', nullableIntProp); RealmObjectBase.set(this, 'nullableDecimalProp', nullableDecimalProp); RealmObjectBase.set(this, 'nullableBinaryProp', nullableBinaryProp); + RealmObjectBase.set(this, 'realmValueProp', realmValueProp); } AllTypes._(); @@ -1003,6 +1005,13 @@ class AllTypes extends _AllTypes set nullableBinaryProp(Uint8List? value) => RealmObjectBase.set(this, 'nullableBinaryProp', value); + @override + RealmValue get realmValueProp => + RealmObjectBase.get(this, 'realmValueProp') as RealmValue; + @override + set realmValueProp(RealmValue value) => + RealmObjectBase.set(this, 'realmValueProp', value); + @override Stream> get changes => RealmObjectBase.getChanges(this); @@ -1034,6 +1043,7 @@ class AllTypes extends _AllTypes 'nullableIntProp': nullableIntProp.toEJson(), 'nullableDecimalProp': nullableDecimalProp.toEJson(), 'nullableBinaryProp': nullableBinaryProp.toEJson(), + 'realmValueProp': realmValueProp.toEJson(), }; } @@ -1059,6 +1069,7 @@ class AllTypes extends _AllTypes 'nullableIntProp': EJsonValue nullableIntProp, 'nullableDecimalProp': EJsonValue nullableDecimalProp, 'nullableBinaryProp': EJsonValue nullableBinaryProp, + 'realmValueProp': EJsonValue realmValueProp, } => AllTypes( fromEJson(stringProp), @@ -1079,6 +1090,7 @@ class AllTypes extends _AllTypes nullableIntProp: fromEJson(nullableIntProp), nullableDecimalProp: fromEJson(nullableDecimalProp), nullableBinaryProp: fromEJson(nullableBinaryProp), + realmValueProp: fromEJson(realmValueProp), ), _ => raiseInvalidEJson(ejson), }; @@ -1114,6 +1126,7 @@ class AllTypes extends _AllTypes optional: true), SchemaProperty('nullableBinaryProp', RealmPropertyType.binary, optional: true), + SchemaProperty('realmValueProp', RealmPropertyType.mixed, optional: true), ]); }();