diff --git a/examples/dart/pubspec.yaml b/examples/dart/pubspec.yaml index 83f2050b78..f2c6f563f7 100644 --- a/examples/dart/pubspec.yaml +++ b/examples/dart/pubspec.yaml @@ -4,7 +4,7 @@ description: A simple command-line application using Realm Dart SDK. publish_to: none environment: - sdk: "^3.0.2" + sdk: "^3.1.0" dependencies: realm_dart: ^2.0.0 diff --git a/examples/dart/test/data_types_test.dart b/examples/dart/test/data_types_test.dart index c546ecd5c6..ea8d9bb9ff 100644 --- a/examples/dart/test/data_types_test.dart +++ b/examples/dart/test/data_types_test.dart @@ -2,6 +2,7 @@ import 'package:realm_dart/realm.dart'; import 'package:test/expect.dart'; import 'package:test/scaffolding.dart'; import 'dart:typed_data'; +import 'dart:developer'; import 'utils.dart'; @@ -70,10 +71,18 @@ class _RealmValueExample { @Indexed() late RealmValue singleAnyValue; late List listOfMixedAnyValues; + late Set setOfMixedAnyValues; + late Map mapOfMixedAnyValues; } // :snippet-end: +@RealmModel() +class _RealmValueCollectionExample { + @Indexed() + late RealmValue singleAnyValue; +} + // :snippet-start: datetime-model @RealmModel() class _Vehicle { @@ -149,6 +158,7 @@ main() { final object = UuidPrimaryKey(myId); // :snippet-end: expect(myId.toString(), isA()); + expect(myId, object.id); }); test('ObjectId', () { // :snippet-start: objectid-use @@ -157,54 +167,165 @@ main() { // :snippet-end: expect(object.id.toString(), isA()); }); - test("RealmValue - RealmValue.from()", () { - // :snippet-start: realm-value-from - final realm = Realm(Configuration.local([RealmValueExample.schema])); - realm.write(() { - realm.addAll([ - RealmValueExample( - singleAnyValue: RealmValue.from(1), - listOfMixedAnyValues: [Uuid.v4(), "abc", 123].map(RealmValue.from)), - RealmValueExample( - singleAnyValue: RealmValue.nullValue(), - listOfMixedAnyValues: ["abc", 123].map(RealmValue.from)) - ]); - }); - // :snippet-end: + group('RealmValue - ', () { + test("RealmValue.from()", () { + // :snippet-start: realm-value-from + final realm = Realm(Configuration.local([RealmValueExample.schema])); - expect( - realm.query("singleAnyValue.@type == 'int'").first, - isNotNull); - expect( - realm.query("singleAnyValue.@type == 'Null'").first, - isNotNull); - cleanUpRealm(realm); - }); - test("RealmValue - RealmValue.type and RealmValue.value", () { - final realm = Realm(Configuration.local([RealmValueExample.schema])); - realm.write(() { - realm.addAll([ - RealmValueExample( + realm.write(() { + // Use 'RealmValue.from()' to set values + var anyValue = realm.add(RealmValueExample( + // Add a single `RealmValue` value singleAnyValue: RealmValue.from(1), - listOfMixedAnyValues: [Uuid.v4(), "abc", 123].map(RealmValue.from)), - RealmValueExample( + // Add a list of `RealmValue` values + listOfMixedAnyValues: [Uuid.v4(), 'abc', 123].map(RealmValue.from), + // Add a set of `RealmValue` values + setOfMixedAnyValues: { + RealmValue.from('abc'), + RealmValue.from('def') + }, + // Add a map of string keys and `RealmValue` values + mapOfMixedAnyValues: { + '1': RealmValue.from(123), + '2': RealmValue.from('abc') + })); + + // Use 'RealmValue.nullValue()' to set null values + var anyValueNull = realm.add(RealmValueExample( singleAnyValue: RealmValue.nullValue(), - listOfMixedAnyValues: ["abc", 123].map(RealmValue.from)) - ]); + listOfMixedAnyValues: [null, null].map(RealmValue.from), + setOfMixedAnyValues: {RealmValue.nullValue()}, + mapOfMixedAnyValues: {'null': RealmValue.nullValue()})); + + // :remove-start: + expect(anyValue.singleAnyValue.type, RealmValueType.int); + expect(anyValue.listOfMixedAnyValues[1].value.toString(), 'abc'); + expect(anyValue.setOfMixedAnyValues.first.value, 'abc'); + expect( + anyValue.mapOfMixedAnyValues.containsValue(RealmValue.from('abc')), + true); + expect(anyValueNull.singleAnyValue.value, null); + expect(anyValueNull.listOfMixedAnyValues[0].value, null); + expect(anyValueNull.setOfMixedAnyValues.first.value, null); + expect(anyValueNull.mapOfMixedAnyValues.containsValue(null), true); + }); + // :remove-end: + // :snippet-end: + cleanUpRealm(realm); }); - var calledCount = 0; - // :snippet-start: realm-value-type-value - final data = realm.all(); - for (var obj in data) { - if (obj.singleAnyValue.type == int) { - print(obj.singleAnyValue.value.toString()); - calledCount++; // :remove: + test("RealmValueType and RealmValue.value", () { + final realm = Realm(Configuration.local([RealmValueExample.schema])); + realm.write(() { + realm.addAll([ + RealmValueExample( + singleAnyValue: RealmValue.from(1), + listOfMixedAnyValues: + [Uuid.v4(), 'abc', 123].map(RealmValue.from), + mapOfMixedAnyValues: { + '1': RealmValue.from(123), + '2': RealmValue.from('abc') + }), + RealmValueExample( + singleAnyValue: RealmValue.nullValue(), + listOfMixedAnyValues: [null, null].map(RealmValue.from), + mapOfMixedAnyValues: {'null': RealmValue.nullValue()}) + ]); + }); + var approximateAge = 0; + // :snippet-start: realm-value-type + final data = realm.all(); + for (var obj in data) { + final anyValue = obj.singleAnyValue; + // Access the RealmValue.type property + switch (anyValue.type) { + // Work with the returned RealmValueType enums + case RealmValueType.int: + approximateAge = DateTime.now().year - anyValue.as(); + break; + case RealmValueType.dateTime: + approximateAge = + (DateTime.now().difference(anyValue.as()).inDays / + 365) + .floor(); + break; + case RealmValueType.string: + final birthday = DateTime.parse(anyValue.as()); + approximateAge = + (DateTime.now().difference(birthday).inDays / 365).floor(); + break; + // Handle other possible types ... + default: + log('Unhandled type: ${anyValue.type}'); + } } - } - // :snippet-end: - expect(calledCount, 1); - cleanUpRealm(realm); + // :snippet-end: + expect(approximateAge, 2023); + int sum = 0; + String combinedStrings = ''; + // :snippet-start: realm-value-value + for (var obj in data) { + for (var mixedValue in obj.listOfMixedAnyValues) { + // Use RealmValue.value to access the value + final value = mixedValue.value; + if (value is int) { + sum = sum + value; + expect(sum, 123); // :remove: + } else if (value is String) { + combinedStrings += value; + expect(combinedStrings, 'abc'); // :remove: + } + + // Use RealmValue.as to cast value to a specific type + try { + final intValue = mixedValue.as(); + sum = sum + intValue; + expect(sum, 246); // :remove: + } catch (e) { + log('Error casting value to int: $e'); + } + } + } + // :snippet-end: + cleanUpRealm(realm); + }); + test('Nested collections of mixed data', () { + final realm = + Realm(Configuration.local([RealmValueCollectionExample.schema])); + + // :snippet-start: realm-value-nested-collections + realm.write(() { + realm.add(RealmValueCollectionExample( + // Set the RealmValue as a map of mixed data + singleAnyValue: RealmValue.from({ + 'int': 1, + // You can nest RealmValues in collections + 'listOfInt': [2, 3, 4], + 'mapOfStrings': {'1': 'first', '2': 'second'}, + // You can also nest collections within collections + 'mapOfMaps': [ + { + 'nestedMap_1': {'1': 1, '2': 2}, + 'nestedMap_2': {'3': 3, '4': 4} + } + ], + 'listOfMaps': [ + { + 'nestedList_1': [1, 2, 3], + 'nestedList_2': [4, 5, 6] + } + ] + }))); + // :snippet-end: + final collectionsOfMixed = + realm.all().first; + final mapValue = collectionsOfMixed.singleAnyValue.asMap(); + expect(mapValue.length, 5); + expect(mapValue.containsKey('mapOfStrings'), true); + expect(mapValue['mapOfMaps']?.asList()[0].asMap().length, 2); + }); + cleanUpRealm(realm); + }); }); test('DateTime', () { final config = Configuration.local([Vehicle.schema]); @@ -264,7 +385,7 @@ main() { // Query RealmList with Realm Query Language final playersWithBodyArmor = realm.query("inventory.name == \$0", ['body armor']); - print("LEN " + playersWithBodyArmor.length.toString()); + print("LEN ${playersWithBodyArmor.length}"); // :snippet-end: expect(brave, 'brave'); expect(elvishSword.name, 'elvish sword'); @@ -441,7 +562,7 @@ main() { // Add RealmObject to realm database realm.write(() => realm.add(mapExample)); - // Qeury for all MapExample objects + // Query for all MapExample objects final realmMap = realm.all()[0]; // :remove-start: diff --git a/examples/dart/test/define_realm_model_test.dart b/examples/dart/test/define_realm_model_test.dart index 03d2c02d2a..62a0a088ef 100644 --- a/examples/dart/test/define_realm_model_test.dart +++ b/examples/dart/test/define_realm_model_test.dart @@ -36,6 +36,20 @@ class _Boat { } // :snippet-end: +// :snippet-start: unstructured-data-model +// Define class with a `RealmValue` property +@RealmModel() +class _EventLog { + @PrimaryKey() + late ObjectId id; + + late String eventType; + late DateTime timestamp; + late String userId; + late RealmValue details; +} +// :snippet-end: + main() { test('Create a new schema version', () { // :snippet-start: schema-version @@ -46,4 +60,42 @@ main() { realm.close(); Realm.deleteRealm(config.path); }); + + test('Create unstructured data', () { + final config = Configuration.local([EventLog.schema]); + final realm = Realm(config); + + // :snippet-start: create-unstructured-data-example + realm.write(() { + // Add `eventLog` property data as a map of mixed data, which + // also includes nested lists of mixed data + realm.add(EventLog(ObjectId(), 'purchase', DateTime.now(), 'user123', + details: RealmValue.from({ + 'ipAddress': '192.168.1.1', + 'items': [ + {'id': 1, 'name': 'Laptop', 'price': 1200.00}, + {'id': 2, 'name': 'Mouse', 'price': 49.99} + ], + 'total': 1249.99 + }))); + + final eventLog = realm.all().first; + final items = eventLog.details.asMap(); + print(''' + Event Type: ${eventLog.eventType} + Timestamp: ${eventLog.timestamp} + User ID: ${eventLog.userId} + Details: + Item: + '''); + for (var item in items.entries) { + print('${item.key}: ${item.value}'); + } + // :snippet-end: + expect(items['ipAddress']?.value.toString(), '192.168.1.1'); + expect(items['total']?.value, 1249.99); + }); + realm.close(); + Realm.deleteRealm(config.path); + }); } diff --git a/examples/dart/test/define_realm_model_test.realm.dart b/examples/dart/test/define_realm_model_test.realm.dart index eab62b73aa..63e4780c71 100644 --- a/examples/dart/test/define_realm_model_test.realm.dart +++ b/examples/dart/test/define_realm_model_test.realm.dart @@ -254,3 +254,104 @@ class Boat extends _Boat with RealmEntity, RealmObjectBase, RealmObject { @override SchemaObject get objectSchema => RealmObjectBase.getSchema(this) ?? schema; } + +class EventLog extends _EventLog + with RealmEntity, RealmObjectBase, RealmObject { + EventLog( + ObjectId id, + String eventType, + DateTime timestamp, + String userId, { + RealmValue details = const RealmValue.nullValue(), + }) { + RealmObjectBase.set(this, 'id', id); + RealmObjectBase.set(this, 'eventType', eventType); + RealmObjectBase.set(this, 'timestamp', timestamp); + RealmObjectBase.set(this, 'userId', userId); + RealmObjectBase.set(this, 'details', details); + } + + EventLog._(); + + @override + ObjectId get id => RealmObjectBase.get(this, 'id') as ObjectId; + @override + set id(ObjectId value) => RealmObjectBase.set(this, 'id', value); + + @override + String get eventType => + RealmObjectBase.get(this, 'eventType') as String; + @override + set eventType(String value) => RealmObjectBase.set(this, 'eventType', value); + + @override + DateTime get timestamp => + RealmObjectBase.get(this, 'timestamp') as DateTime; + @override + set timestamp(DateTime value) => + RealmObjectBase.set(this, 'timestamp', value); + + @override + String get userId => RealmObjectBase.get(this, 'userId') as String; + @override + set userId(String value) => RealmObjectBase.set(this, 'userId', value); + + @override + RealmValue get details => + RealmObjectBase.get(this, 'details') as RealmValue; + @override + set details(RealmValue value) => RealmObjectBase.set(this, 'details', value); + + @override + Stream> get changes => + RealmObjectBase.getChanges(this); + + @override + EventLog freeze() => RealmObjectBase.freezeObject(this); + + EJsonValue toEJson() { + return { + 'id': id.toEJson(), + 'eventType': eventType.toEJson(), + 'timestamp': timestamp.toEJson(), + 'userId': userId.toEJson(), + 'details': details.toEJson(), + }; + } + + static EJsonValue _toEJson(EventLog value) => value.toEJson(); + static EventLog _fromEJson(EJsonValue ejson) { + return switch (ejson) { + { + 'id': EJsonValue id, + 'eventType': EJsonValue eventType, + 'timestamp': EJsonValue timestamp, + 'userId': EJsonValue userId, + 'details': EJsonValue details, + } => + EventLog( + fromEJson(id), + fromEJson(eventType), + fromEJson(timestamp), + fromEJson(userId), + details: fromEJson(details), + ), + _ => raiseInvalidEJson(ejson), + }; + } + + static final schema = () { + RealmObjectBase.registerFactory(EventLog._); + register(_toEJson, _fromEJson); + return SchemaObject(ObjectType.realmObject, EventLog, 'EventLog', [ + SchemaProperty('id', RealmPropertyType.objectid, primaryKey: true), + SchemaProperty('eventType', RealmPropertyType.string), + SchemaProperty('timestamp', RealmPropertyType.timestamp), + SchemaProperty('userId', RealmPropertyType.string), + SchemaProperty('details', RealmPropertyType.mixed, optional: true), + ]); + }(); + + @override + SchemaObject get objectSchema => RealmObjectBase.getSchema(this) ?? schema; +} diff --git a/examples/dart/test/read_write_data_test.dart b/examples/dart/test/read_write_data_test.dart index aae537a162..aff7e94c65 100644 --- a/examples/dart/test/read_write_data_test.dart +++ b/examples/dart/test/read_write_data_test.dart @@ -2,9 +2,7 @@ // file, as the examples are the same. import 'package:test/test.dart'; import 'package:realm_dart/realm.dart'; -import '../bin/models/car.dart'; import 'utils.dart'; -import 'dart:io'; part 'read_write_data_test.realm.dart'; @@ -25,6 +23,7 @@ class _Team { late String name; late List<_Person> crew; + late RealmValue eventLog; } // :snippet-end: @@ -57,7 +56,7 @@ void main() { expect(people.length, 1); cleanUpRealm(realm); }); - test("Query List of Realm Objects", () { + test('Query List of Realm Objects', () { // :snippet-start: query-realm-list final config = Configuration.local([Person.schema, Team.schema]); final realm = Realm(config); @@ -79,7 +78,7 @@ void main() { }); cleanUpRealm(realm); }); - test("Lists asResults", () { + test('Lists asResults', () { // :snippet-start: list-as-results final config = Configuration.local([Person.schema, Team.schema]); final realm = Realm(config); @@ -116,7 +115,9 @@ void main() { }); cleanUpRealm(realm); }); - test("Filter Results", () { + }); + group('Filter Data', () { + test('Filter Results', () { final config = Configuration.local([Person.schema, Team.schema]); final realm = Realm(config); final heroes = Team(ObjectId(), 'Millennium Falcon Crew', crew: [ @@ -140,14 +141,12 @@ void main() { realm.query('name IN \$0', [listOfNames]); // :snippet-end: expect(matchingRealmObjects.length, 2); - for (var person in matchingRealmObjects) { print(person.name); } - cleanUpRealm(realm); }); - test("Sort Results", () { + test('Sort Results', () { final config = Configuration.local([Person.schema, Team.schema]); final realm = Realm(config); // :snippet-start: sort @@ -171,8 +170,74 @@ void main() { expect(alphabetizedPeople.last.name, 'Luke'); cleanUpRealm(realm); }); + test('Filter Nested Collections', () { + final config = Configuration.local([Person.schema, Team.schema]); + final realm = Realm(config); + + // :snippet-start: filter-nested-collections + realm.write(() { + realm.addAll([ + (Team(ObjectId(), 'Janitorial Staff', + eventLog: RealmValue.from({ + '1': { + 'date': DateTime.utc(5622, 8, 18, 12, 30, 0), + 'type': ['work_order', 'maintenance'], + 'summary': 'leaking pipes in control room', + 'priority': 'high', + }, + '2': { + 'date': DateTime.utc(5622, 9, 18, 12, 30, 0), + 'type': ['maintenance'], + 'summary': 'trash compactor jammed', + 'priority': 'low', + 'comment': 'this is the second time this week' + } + }))), + (Team(ObjectId(), 'IT', + eventLog: RealmValue.from({ + '1': { + 'date': DateTime.utc(5622, 9, 20, 12, 30, 0), + 'type': ['hardware', 'repair'], + 'summary': 'lightsaber damage to server room', + 'priority': 'high', + } + }))) + ]); + + final teams = realm.all(); + // Use bracket notation to query collection values at the specified path + final teamsWithHighPriorityEvents = + // Check any element at that path with [*] + teams.query("eventLog[*].priority == 'high'"); + print(teamsWithHighPriorityEvents.length); // prints `2` + + final teamsWithMaintenanceEvents = + // Check for the first element at that path with [FIRST] + teams.query("eventLog[*].type[FIRST] == 'maintenance'"); + print(teamsWithMaintenanceEvents.length); // prints `1` - test("Limit Results", () { + final teamsWithMultipleEvents = + // Check for collection at that path with matching elements + // Note that the order must match unless you use ANY or ALL + teams.query("eventLog[*].type[*] == {'maintenance', 'work_order'}"); + print( + teamsWithMultipleEvents.length); // prints `0` because order matters + + final teamsWithEventsAsLists = + // Check the collection type with @type + teams.query("eventLog[*].type.@type == 'list'"); + print(teamsWithEventsAsLists.length); // prints `2` + // :remove-start: + expect(teamsWithHighPriorityEvents.length, 2); + expect(teamsWithMaintenanceEvents.length, 1); + expect(teamsWithMultipleEvents.length, 0); + expect(teamsWithEventsAsLists.length, 2); + // :remove-end: + }); + // :snippet-end: + cleanUpRealm(realm); + }); + test('Limit Results', () { final config = Configuration.local([Person.schema, Team.schema]); final realm = Realm(config); // :snippet-start: limit @@ -196,153 +261,156 @@ void main() { cleanUpRealm(realm); }); }); + group('Write, update, and delete data', () { + test('Return from write block', () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); - test('Return from write block', () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - - // :snippet-start: return-from-write - final yoda = realm.write(() { - return realm.add(Person(ObjectId(), 'Yoda')); + // :snippet-start: return-from-write + final yoda = realm.write(() { + return realm.add(Person(ObjectId(), 'Yoda')); + }); + // :snippet-end: + expect(yoda.name, 'Yoda'); + cleanUpRealm(realm); }); - // :snippet-end: - expect(yoda.name, 'Yoda'); - cleanUpRealm(realm); - }); - test("Create an Object", () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - // :snippet-start: create-object - realm.write(() { - realm.add(Person(ObjectId(), 'Lando')); + test("Create an Object", () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + // :snippet-start: create-object + realm.write(() { + realm.add(Person(ObjectId(), 'Lando')); + }); + // :snippet-end: + expect(realm.all().first.name, 'Lando'); + cleanUpRealm(realm); }); - // :snippet-end: - expect(realm.all().first.name, 'Lando'); - cleanUpRealm(realm); - }); - test("Create Multiple Objects", () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - // :snippet-start: create-multiple-objects - realm.write(() { - realm.addAll([ - Person(ObjectId(), 'Figrin D\'an'), - Person(ObjectId(), 'Greedo'), - Person(ObjectId(), 'Toro') - ]); + test('Create Multiple Objects', () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + // :snippet-start: create-multiple-objects + realm.write(() { + realm.addAll([ + Person(ObjectId(), 'Figrin D\'an'), + Person(ObjectId(), 'Greedo'), + Person(ObjectId(), 'Toro') + ]); + }); + // :snippet-end: + expect(realm.all().length, 3); + cleanUpRealm(realm); }); - // :snippet-end: - expect(realm.all().length, 3); - cleanUpRealm(realm); - }); - test("Update Object Properties", () { - final config = Configuration.local([Person.schema, Team.schema]); - final realm = Realm(config); - final spaceshipTeam = Team(ObjectId(), 'Millennium Falcon Crew', - crew: [Person(ObjectId(), 'Han'), Person(ObjectId(), 'Chewbacca')]); - realm.write(() => realm.add(spaceshipTeam)); - // :snippet-start: update-object - realm.write(() { - spaceshipTeam.name = 'Galactic Republic Scout Team'; - spaceshipTeam.crew - .addAll([Person(ObjectId(), 'Luke'), Person(ObjectId(), 'Leia')]); + test('Update Object Properties', () { + final config = Configuration.local([Person.schema, Team.schema]); + final realm = Realm(config); + final spaceshipTeam = Team(ObjectId(), 'Millennium Falcon Crew', + crew: [Person(ObjectId(), 'Han'), Person(ObjectId(), 'Chewbacca')]); + realm.write(() => realm.add(spaceshipTeam)); + // :snippet-start: update-object + realm.write(() { + spaceshipTeam.name = 'Galactic Republic Scout Team'; + spaceshipTeam.crew + .addAll([Person(ObjectId(), 'Luke'), Person(ObjectId(), 'Leia')]); + }); + // :snippet-end: + expect(spaceshipTeam.name, 'Galactic Republic Scout Team'); + expect(spaceshipTeam.crew.length, 4); + cleanUpRealm(realm); }); - // :snippet-end: - expect(spaceshipTeam.name, 'Galactic Republic Scout Team'); - expect(spaceshipTeam.crew.length, 4); - cleanUpRealm(realm); - }); - test('Upsert data', () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - // :snippet-start: upsert - final id = ObjectId(); - // Add Anakin Skywalker to the realm with primary key `id` - final anakin = Person( - id, - "Anakin Skywalker", - ); - realm.write(() { - realm.add(anakin); - }); + test('Upsert data', () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + // :snippet-start: upsert + final id = ObjectId(); + // Add Anakin Skywalker to the realm with primary key `id` + final anakin = Person( + id, + "Anakin Skywalker", + ); + realm.write(() { + realm.add(anakin); + }); - // Overwrite the 'Anakin' Person object - // with a new 'Darth Vader' object - final darthVader = Person(id, 'Darth Vader'); - realm.write(() { - realm.add(darthVader, update: true); + // Overwrite the 'Anakin' Person object + // with a new 'Darth Vader' object + final darthVader = Person(id, 'Darth Vader'); + realm.write(() { + realm.add(darthVader, update: true); + }); + // :snippet-end: + final darthAnakin = realm.find(id); + expect(darthAnakin!.name, 'Darth Vader'); + cleanUpRealm(realm); }); - // :snippet-end: - final darthAnakin = realm.find(id); - expect(darthAnakin!.name, 'Darth Vader'); - cleanUpRealm(realm); - }); - test("Delete a single object", () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - final obiWan = - realm.write((() => realm.add(Person(ObjectId(), 'Obi-Wan')))); - expect(realm.all().length, 1); - // :snippet-start: delete-one-object - realm.write(() { - realm.delete(obiWan); + test("Delete a single object", () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + final obiWan = + realm.write((() => realm.add(Person(ObjectId(), 'Obi-Wan')))); + expect(realm.all().length, 1); + // :snippet-start: delete-one-object + realm.write(() { + realm.delete(obiWan); + }); + // :snippet-end: + expect(realm.all().length, 0); + cleanUpRealm(realm); }); - // :snippet-end: - expect(realm.all().length, 0); - cleanUpRealm(realm); - }); - test("Delete multiple objects", () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - final obiWan = - realm.write((() => realm.add(Person(ObjectId(), 'Obi-Wan')))); - final quiGon = - realm.write((() => realm.add(Person(ObjectId(), 'Qui-Gon')))); - expect(realm.all().length, 2); - // :snippet-start: delete-multiple-objects - realm.write(() { - realm.deleteMany([obiWan, quiGon]); + test("Delete multiple objects", () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + final obiWan = + realm.write((() => realm.add(Person(ObjectId(), 'Obi-Wan')))); + final quiGon = + realm.write((() => realm.add(Person(ObjectId(), 'Qui-Gon')))); + expect(realm.all().length, 2); + // :snippet-start: delete-multiple-objects + realm.write(() { + realm.deleteMany([obiWan, quiGon]); + }); + // :snippet-end: + expect(realm.all().length, 0); + cleanUpRealm(realm); }); - // :snippet-end: - expect(realm.all().length, 0); - cleanUpRealm(realm); - }); - test("Delete all objects of a type", () { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - realm.write( - () => realm.addAll( - [Person(ObjectId(), 'Boba Fett'), Person(ObjectId(), 'Jango Fett')]), - ); - expect(realm.all().length, 2); - // :snippet-start: delete-all-objects-of-type - realm.write(() { - realm.deleteAll(); + test("Delete all objects of a type", () { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + realm.write( + () => realm.addAll([ + Person(ObjectId(), 'Boba Fett'), + Person(ObjectId(), 'Jango Fett') + ]), + ); + expect(realm.all().length, 2); + // :snippet-start: delete-all-objects-of-type + realm.write(() { + realm.deleteAll(); + }); + // :snippet-end: + expect(realm.all().length, 0); + cleanUpRealm(realm); }); - // :snippet-end: - expect(realm.all().length, 0); - cleanUpRealm(realm); - }); - test('Write async', () async { - final config = Configuration.local([Person.schema]); - final realm = Realm(config); - // :snippet-start: write-async - // Add Leia to the realm using `writeAsync` - Person leia = Person(ObjectId(), "Leia"); - realm.writeAsync(() { - realm.add(leia); + test('Write async', () async { + final config = Configuration.local([Person.schema]); + final realm = Realm(config); + // :snippet-start: write-async + // Add Leia to the realm using `writeAsync` + Person leia = Person(ObjectId(), "Leia"); + realm.writeAsync(() { + realm.add(leia); + }); + // :snippet-end: + final leiaAgain = realm.query("name == \$0", ['Leia']); + expect(leiaAgain.length, 0); + expect(realm.isInTransaction, true); + // let transaction resolve + await Future.delayed(Duration(milliseconds: 500)); + expect(realm.isInTransaction, false); + expect(realm.query("name == \$0", ['Leia']).length, 1); + cleanUpRealm(realm); }); - // :snippet-end: - final leiaAgain = realm.query("name == \$0", ['Leia']); - expect(leiaAgain.length, 0); - expect(realm.isInTransaction, true); - // let transaction resolve - await Future.delayed(Duration(milliseconds: 500)); - expect(realm.isInTransaction, false); - expect(realm.query("name == \$0", ['Leia']).length, 1); - cleanUpRealm(realm); }); } diff --git a/examples/dart/test/read_write_data_test.realm.dart b/examples/dart/test/read_write_data_test.realm.dart index c9182e2f3e..b1a04434c5 100644 --- a/examples/dart/test/read_write_data_test.realm.dart +++ b/examples/dart/test/read_write_data_test.realm.dart @@ -90,11 +90,13 @@ class Team extends _Team with RealmEntity, RealmObjectBase, RealmObject { ObjectId id, String name, { Iterable crew = const [], + RealmValue eventLog = const RealmValue.nullValue(), }) { RealmObjectBase.set(this, 'id', id); RealmObjectBase.set(this, 'name', name); RealmObjectBase.set>( this, 'crew', RealmList(crew)); + RealmObjectBase.set(this, 'eventLog', eventLog); } Team._(); @@ -116,6 +118,13 @@ class Team extends _Team with RealmEntity, RealmObjectBase, RealmObject { set crew(covariant RealmList value) => throw RealmUnsupportedSetError(); + @override + RealmValue get eventLog => + RealmObjectBase.get(this, 'eventLog') as RealmValue; + @override + set eventLog(RealmValue value) => + RealmObjectBase.set(this, 'eventLog', value); + @override Stream> get changes => RealmObjectBase.getChanges(this); @@ -128,6 +137,7 @@ class Team extends _Team with RealmEntity, RealmObjectBase, RealmObject { 'id': id.toEJson(), 'name': name.toEJson(), 'crew': crew.toEJson(), + 'eventLog': eventLog.toEJson(), }; } @@ -138,11 +148,13 @@ class Team extends _Team with RealmEntity, RealmObjectBase, RealmObject { 'id': EJsonValue id, 'name': EJsonValue name, 'crew': EJsonValue crew, + 'eventLog': EJsonValue eventLog, } => Team( fromEJson(id), fromEJson(name), crew: fromEJson(crew), + eventLog: fromEJson(eventLog), ), _ => raiseInvalidEJson(ejson), }; @@ -156,6 +168,7 @@ class Team extends _Team with RealmEntity, RealmObjectBase, RealmObject { SchemaProperty('name', RealmPropertyType.string), SchemaProperty('crew', RealmPropertyType.object, linkTarget: 'Person', collectionType: RealmCollectionType.list), + SchemaProperty('eventLog', RealmPropertyType.mixed, optional: true), ]); }(); diff --git a/source/examples/generated/flutter/data_types_test.snippet.data-types-example-model.dart b/source/examples/generated/flutter/data_types_test.snippet.data-types-example-model.dart index 754aedec47..826fa16b1c 100644 --- a/source/examples/generated/flutter/data_types_test.snippet.data-types-example-model.dart +++ b/source/examples/generated/flutter/data_types_test.snippet.data-types-example-model.dart @@ -1,6 +1,18 @@ part 'car.realm.dart'; +@RealmModel() +class _Car { + @PrimaryKey() + late ObjectId id; + + String? licensePlate; + bool isElectric = false; + double milesDriven = 0; + late List attributes; + late _Person? owner; +} + // The generated `Address` class is an embedded object. @RealmModel(ObjectType.embeddedObject) class _Address { diff --git a/source/examples/generated/flutter/data_types_test.snippet.map-work-with.dart b/source/examples/generated/flutter/data_types_test.snippet.map-work-with.dart index c7fa0f7452..9f7af9de70 100644 --- a/source/examples/generated/flutter/data_types_test.snippet.map-work-with.dart +++ b/source/examples/generated/flutter/data_types_test.snippet.map-work-with.dart @@ -17,7 +17,7 @@ final mapExample = MapExample( // Add RealmObject to realm database realm.write(() => realm.add(mapExample)); -// Qeury for all MapExample objects +// Query for all MapExample objects final realmMap = realm.all()[0]; // Modify RealmMaps in write transactions diff --git a/source/examples/generated/flutter/data_types_test.snippet.realm-value-from.dart b/source/examples/generated/flutter/data_types_test.snippet.realm-value-from.dart index ad9459ec08..c7fc5b8a9a 100644 --- a/source/examples/generated/flutter/data_types_test.snippet.realm-value-from.dart +++ b/source/examples/generated/flutter/data_types_test.snippet.realm-value-from.dart @@ -1,12 +1,27 @@ final realm = Realm(Configuration.local([RealmValueExample.schema])); realm.write(() { - realm.addAll([ - RealmValueExample( - singleAnyValue: RealmValue.from(1), - listOfMixedAnyValues: [Uuid.v4(), "abc", 123].map(RealmValue.from)), - RealmValueExample( - singleAnyValue: RealmValue.nullValue(), - listOfMixedAnyValues: ["abc", 123].map(RealmValue.from)) - ]); -}); + // Use 'RealmValue.from()' to set values + var anyValue = realm.add(RealmValueExample( + // Add a single `RealmValue` value + singleAnyValue: RealmValue.from(1), + // Add a list of `RealmValue` values + listOfMixedAnyValues: [Uuid.v4(), 'abc', 123].map(RealmValue.from), + // Add a set of `RealmValue` values + setOfMixedAnyValues: { + RealmValue.from('abc'), + RealmValue.from('def') + }, + // Add a map of string keys and `RealmValue` values + mapOfMixedAnyValues: { + '1': RealmValue.from(123), + '2': RealmValue.from('abc') + })); + + // Use 'RealmValue.nullValue()' to set null values + var anyValueNull = realm.add(RealmValueExample( + singleAnyValue: RealmValue.nullValue(), + listOfMixedAnyValues: [null, null].map(RealmValue.from), + setOfMixedAnyValues: {RealmValue.nullValue()}, + mapOfMixedAnyValues: {'null': RealmValue.nullValue()})); + diff --git a/source/examples/generated/flutter/data_types_test.snippet.realm-value-model.dart b/source/examples/generated/flutter/data_types_test.snippet.realm-value-model.dart index bc99b234b4..f148b752e5 100644 --- a/source/examples/generated/flutter/data_types_test.snippet.realm-value-model.dart +++ b/source/examples/generated/flutter/data_types_test.snippet.realm-value-model.dart @@ -3,5 +3,7 @@ class _RealmValueExample { @Indexed() late RealmValue singleAnyValue; late List listOfMixedAnyValues; + late Set setOfMixedAnyValues; + late Map mapOfMixedAnyValues; } diff --git a/source/examples/generated/flutter/data_types_test.snippet.realm-value-nested-collections.dart b/source/examples/generated/flutter/data_types_test.snippet.realm-value-nested-collections.dart new file mode 100644 index 0000000000..132be9ad51 --- /dev/null +++ b/source/examples/generated/flutter/data_types_test.snippet.realm-value-nested-collections.dart @@ -0,0 +1,22 @@ +realm.write(() { + realm.add(RealmValueCollectionExample( + // Set the RealmValue as a map of mixed data + singleAnyValue: RealmValue.from({ + 'int': 1, + // You can nest RealmValues in collections + 'listOfInt': [2, 3, 4], + 'mapOfStrings': {'1': 'first', '2': 'second'}, + // You can also nest collections within collections + 'mapOfMaps': [ + { + 'nestedMap_1': {'1': 1, '2': 2}, + 'nestedMap_2': {'3': 3, '4': 4} + } + ], + 'listOfMaps': [ + { + 'nestedList_1': [1, 2, 3], + 'nestedList_2': [4, 5, 6] + } + ] + }))); diff --git a/source/examples/generated/flutter/data_types_test.snippet.realm-value-type-value.dart b/source/examples/generated/flutter/data_types_test.snippet.realm-value-type-value.dart deleted file mode 100644 index f62721b6e9..0000000000 --- a/source/examples/generated/flutter/data_types_test.snippet.realm-value-type-value.dart +++ /dev/null @@ -1,6 +0,0 @@ -final data = realm.all(); -for (var obj in data) { - if (obj.singleAnyValue.type == int) { - print(obj.singleAnyValue.value.toString()); - } -} diff --git a/source/examples/generated/flutter/data_types_test.snippet.realm-value-type.dart b/source/examples/generated/flutter/data_types_test.snippet.realm-value-type.dart new file mode 100644 index 0000000000..f90cf08691 --- /dev/null +++ b/source/examples/generated/flutter/data_types_test.snippet.realm-value-type.dart @@ -0,0 +1,25 @@ +final data = realm.all(); +for (var obj in data) { + final anyValue = obj.singleAnyValue; + // Access the RealmValue.type property + switch (anyValue.type) { + // Work with the returned RealmValueType enums + case RealmValueType.int: + approximateAge = DateTime.now().year - anyValue.as(); + break; + case RealmValueType.dateTime: + approximateAge = + (DateTime.now().difference(anyValue.as()).inDays / + 365) + .floor(); + break; + case RealmValueType.string: + final birthday = DateTime.parse(anyValue.as()); + approximateAge = + (DateTime.now().difference(birthday).inDays / 365).floor(); + break; + // Handle other possible types ... + default: + log('Unhandled type: ${anyValue.type}'); + } +} diff --git a/source/examples/generated/flutter/data_types_test.snippet.realm-value-value.dart b/source/examples/generated/flutter/data_types_test.snippet.realm-value-value.dart new file mode 100644 index 0000000000..4e198aa555 --- /dev/null +++ b/source/examples/generated/flutter/data_types_test.snippet.realm-value-value.dart @@ -0,0 +1,19 @@ +for (var obj in data) { + for (var mixedValue in obj.listOfMixedAnyValues) { + // Use RealmValue.value to access the value + final value = mixedValue.value; + if (value is int) { + sum = sum + value; + } else if (value is String) { + combinedStrings += value; + } + + // Use RealmValue.as to cast value to a specific type + try { + final intValue = mixedValue.as(); + sum = sum + intValue; + } catch (e) { + log('Error casting value to int: $e'); + } + } +} diff --git a/source/examples/generated/flutter/data_types_test.snippet.realmlist-use.dart b/source/examples/generated/flutter/data_types_test.snippet.realmlist-use.dart index 317d699392..962dd4cf01 100644 --- a/source/examples/generated/flutter/data_types_test.snippet.realmlist-use.dart +++ b/source/examples/generated/flutter/data_types_test.snippet.realmlist-use.dart @@ -17,4 +17,4 @@ final elvishSword = // Query RealmList with Realm Query Language final playersWithBodyArmor = realm.query("inventory.name == \$0", ['body armor']); -print("LEN " + playersWithBodyArmor.length.toString()); +print("LEN ${playersWithBodyArmor.length}"); diff --git a/source/examples/generated/flutter/define_realm_model_test.snippet.create-unstructured-data-example.dart b/source/examples/generated/flutter/define_realm_model_test.snippet.create-unstructured-data-example.dart new file mode 100644 index 0000000000..ea71da6b8e --- /dev/null +++ b/source/examples/generated/flutter/define_realm_model_test.snippet.create-unstructured-data-example.dart @@ -0,0 +1,25 @@ +realm.write(() { + // Add `eventLog` property data as a map of mixed data, which + // also includes nested lists of mixed data + realm.add(EventLog(ObjectId(), 'purchase', DateTime.now(), 'user123', + details: RealmValue.from({ + 'ipAddress': '192.168.1.1', + 'items': [ + {'id': 1, 'name': 'Laptop', 'price': 1200.00}, + {'id': 2, 'name': 'Mouse', 'price': 49.99} + ], + 'total': 1249.99 + }))); + + final eventLog = realm.all().first; + final items = eventLog.details.asMap(); + print(''' + Event Type: ${eventLog.eventType} + Timestamp: ${eventLog.timestamp} + User ID: ${eventLog.userId} + Details: + Item: + '''); + for (var item in items.entries) { + print('${item.key}: ${item.value}'); + } diff --git a/source/examples/generated/flutter/define_realm_model_test.snippet.unstructured-data-model.dart b/source/examples/generated/flutter/define_realm_model_test.snippet.unstructured-data-model.dart new file mode 100644 index 0000000000..b0de34b4c6 --- /dev/null +++ b/source/examples/generated/flutter/define_realm_model_test.snippet.unstructured-data-model.dart @@ -0,0 +1,11 @@ +// Define class with a `RealmValue` property +@RealmModel() +class _EventLog { + @PrimaryKey() + late ObjectId id; + + late String eventType; + late DateTime timestamp; + late String userId; + late RealmValue details; +} diff --git a/source/examples/generated/flutter/read_write_data_test.snippet.filter-nested-collections.dart b/source/examples/generated/flutter/read_write_data_test.snippet.filter-nested-collections.dart new file mode 100644 index 0000000000..375cecbeed --- /dev/null +++ b/source/examples/generated/flutter/read_write_data_test.snippet.filter-nested-collections.dart @@ -0,0 +1,53 @@ +realm.write(() { + realm.addAll([ + (Team(ObjectId(), 'Janitorial Staff', + eventLog: RealmValue.from({ + '1': { + 'date': DateTime.utc(5622, 8, 18, 12, 30, 0), + 'type': ['work_order', 'maintenance'], + 'summary': 'leaking pipes in control room', + 'priority': 'high', + }, + '2': { + 'date': DateTime.utc(5622, 9, 18, 12, 30, 0), + 'type': ['maintenance'], + 'summary': 'trash compactor jammed', + 'priority': 'low', + 'comment': 'this is the second time this week' + } + }))), + (Team(ObjectId(), 'IT', + eventLog: RealmValue.from({ + '1': { + 'date': DateTime.utc(5622, 9, 20, 12, 30, 0), + 'type': ['hardware', 'repair'], + 'summary': 'lightsaber damage to server room', + 'priority': 'high', + } + }))) + ]); + + final teams = realm.all(); + // Use bracket notation to query collection values at the specified path + final teamsWithHighPriorityEvents = + // Check any element at that path with [*] + teams.query("eventLog[*].priority == 'high'"); + print(teamsWithHighPriorityEvents.length); // prints `2` + + final teamsWithMaintenanceEvents = + // Check for the first element at that path with [FIRST] + teams.query("eventLog[*].type[FIRST] == 'maintenance'"); + print(teamsWithMaintenanceEvents.length); // prints `1` + + final teamsWithMultipleEvents = + // Check for collection at that path with matching elements + // Note that the order must match unless you use ANY or ALL + teams.query("eventLog[*].type[*] == {'maintenance', 'work_order'}"); + print( + teamsWithMultipleEvents.length); // prints `0` because order matters + + final teamsWithEventsAsLists = + // Check the collection type with @type + teams.query("eventLog[*].type.@type == 'list'"); + print(teamsWithEventsAsLists.length); // prints `2` +}); diff --git a/source/examples/generated/flutter/read_write_data_test.snippet.models.dart b/source/examples/generated/flutter/read_write_data_test.snippet.models.dart index 442f3b8a49..54cf8c31f0 100644 --- a/source/examples/generated/flutter/read_write_data_test.snippet.models.dart +++ b/source/examples/generated/flutter/read_write_data_test.snippet.models.dart @@ -14,4 +14,5 @@ class _Team { late String name; late List<_Person> crew; + late RealmValue eventLog; } diff --git a/source/sdk/flutter/crud/read.txt b/source/sdk/flutter/crud/read.txt index 7ac0f1bd55..400f6d2569 100644 --- a/source/sdk/flutter/crud/read.txt +++ b/source/sdk/flutter/crud/read.txt @@ -204,6 +204,43 @@ or primitives. .. literalinclude:: /examples/generated/flutter/read_write_data_test.snippet.query-realm-list.dart :language: dart +.. _flutter-filter-nested-collections-mixed: + +Query Nested Collections of Mixed Data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.0 + +In Flutter SDK v2.0.0 and later, :ref:`RealmValue ` +properties can contain collections (a list or map) of mixed data. These +collections can be nested within collections and can contain other +collections of mixed data. + +You can query these using the same syntax as you would for a +normal list or dictionary collection. Refer to the :ref:`rql` documentation for more +information on supported operators and list comparisons. + +For nested collections, you can also use: + +- Bracket notation, which provides the following collection query operators: + + - ``[FIRST]`` and [``LAST``]: match the first or last elements within the collection. + - ``[]``: match the element at the specific index. + - ``[*]``: match any element within the collection (this operator assumes a + collection type at that path). + - ``[SIZE]``: match the collection length. + +- The ``@type`` operator, which supports the following values: + + - ``array`` and ``list``: match a list collection. + - ``dictionary`` and ``object``: match a map collection. + - ``collection``: match a list or a map collection. + +.. literalinclude:: /examples/generated/flutter/read_write_data_test.snippet.filter-nested-collections.dart + :language: dart + +.. TODO Link to RQL documentation when available + .. _flutter-as-results: Convert Lists or Sets to Results @@ -241,6 +278,11 @@ You can use iterable arguments in your filter. For example: .. literalinclude:: /examples/generated/flutter/read_write_data_test.snippet.filter-iterable.dart :language: dart +.. _flutter-filter-inverse-relationships: + +Filter Inverse Relationships +```````````````````````````` + You can also filter by inverse relationships using the ``@links..`` syntax. For example, a filter can match a ``Task`` object based on properties of the ``User`` @@ -279,7 +321,7 @@ fields: .. _flutter-fts-details: Full-Text Search Tokenizer Details -'''''''''''''''''''''''''''''''''' +`````````````````````````````````` Full-Text Search (FTS) indexes support: diff --git a/source/sdk/flutter/realm-database/model-data/data-types.txt b/source/sdk/flutter/realm-database/model-data/data-types.txt index 56a0c7b55c..635b91dac8 100644 --- a/source/sdk/flutter/realm-database/model-data/data-types.txt +++ b/source/sdk/flutter/realm-database/model-data/data-types.txt @@ -5,9 +5,13 @@ Data Types - Flutter SDK ======================== .. meta:: - :keywords: code example, flutter + :keywords: code example :description: Learn about the data types Atlas Device SDK for Flutter supports and how to use them. +.. facet:: + :name: genre + :values: tutorial + .. contents:: On this page :local: :backlinks: none @@ -151,9 +155,9 @@ When defining a RealmSet in a schema: - A set of primitive types can be defined as either nullable or non-nullable. For example, both ``Set`` and ``Set`` are valid in a Realm schema. - A set of ``RealmObject`` and ``RealmValue`` types can only be non-nullable. - For example ``Set`` **is valid** and ``Set`` **is not valid**. -- You **cannot** define default values when defining a set in a schema. - For example, ``Set mySet = {0,1,2}`` **is not valid**. + For example ``Set`` is valid, but ``Set`` *is not* valid. +- You *cannot* define default values when defining a set in a schema. + For example, ``Set mySet = {0,1,2}`` *is not* valid. .. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-set-model.dart :language: dart @@ -238,10 +242,10 @@ Collections are Live Like :ref:`live objects `, Realm collections are *usually* live: -- Live **results collections** always reflect the current results of the associated query. -- Live **lists** of ``RealmObjects`` always reflect the current state of the relationship on the realm instance. +- **Live results collections** always reflect the current results of the associated query. +- **Live lists** of ``RealmObjects`` always reflect the current state of the relationship on the realm instance. -There are two cases, however, when a collection is **not** live: +There are two cases, however, when a collection is *not* live: - The collection is unmanaged: a ``RealmList`` property of a Realm object that has not been added to a realm yet or that has been copied from a @@ -339,30 +343,106 @@ following things are true when using ``compareTo()``: RealmValue ~~~~~~~~~~ +.. important:: Flutter SDK v2.0.0 Changes to ``RealmValue`` + + Flutter SDK version 2.0.0 updates ``RealmValue`` to allow a ``List`` + or ``Map`` type of ``RealmValue``, which enables more flexibility + when modeling unstructured data. Refer to :ref:`flutter-model-unstructured-data` + for more information. + + This update also includes the following breaking changes, which may affect + your app when updating to v2.0.0 or later: + + - ``RealmValue.type`` is now an enum of ``RealmValueType`` instead of ``Type``. + - ``RealmValue.uint8List`` is renamed to ``RealmValue.binary``. + +.. TODO: Add this back to above note once upgrade page is available +.. For more information on how to update an existing app from an earlier +.. version to v2.0.0 or later, refer to :ref:`flutter-upgrade-v2`. + The `RealmValue `__ -data type is a mixed data type that can represent any other valid Realm data type except a collection. -You can create collections of type ``RealmValue``, but a ``RealmValue`` itself -cannot be a collection. ``RealmValue`` is indexable, but cannot be a primary key. +data type is a mixed data type that can represent any other valid +data type except embedded objects. In Flutter SDK v2.0.0 and later, ``RealmValue`` +can represent a ``List`` or ``Map``. -.. note:: +Define a RealmValue Property +```````````````````````````` + +To define a ``RealmValue`` property, set its type in your object model. +``RealmValue`` is indexable, but cannot be a primary key. You can also define +properties as collections (lists, sets, or maps) of type ``RealmValue``. + +.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-model.dart + :language: dart + +.. note:: ``RealmValue`` Not Nullable But Can Contain Null Values When defining your Realm object schema, you cannot create a nullable ``RealmValue``. However, if you want a ``RealmValue`` property to contain a null value, you can use the special ``RealmValue.nullValue()`` property. -To define a property as ``RealmValue``, set its type in your Realm object model. +Create a RealmValue +``````````````````` -.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-model.dart +To add a ``RealmValue`` to a Realm object, call ``RealmValue.from()`` on the data or ``RealmValue.nullValue()`` to set a null value. + +.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-from.dart :language: dart -To add a ``RealmValue`` to a Realm object, call ``RealmValue.from()`` on the data. +.. _flutter-access-realm-value-type: -.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-from.dart +Access RealmValue Data Type +``````````````````````````` + +.. versionchanged:: 2.0.0 + + ``RealmValueType`` enum replaces ``RealmValue.type``. + ``RealmValue.binary`` replaces ``RealmValue.uint8List``. + +To access the data stored in a ``RealmValue``, you can use: + +- ``RealmValue.value``, which returns an ``Object?``. +- ``RealmValue.as``, which fetches the data and casts it to a desired type. + +.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-value.dart :language: dart + :emphasize-lines: 4, 13 + +You can check the type of data currently stored in a ``RealmValue`` property by +accessing the ``type`` property. Starting with Flutter SDK v2.0.0, this returns a +``RealmValueType`` enum. In earlier SDK versions, the SDK returned a +``RealmValue.value.runtimeType``. + +The following example uses ``RealmValueType`` to run calculations based on the +data type. + +.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-type.dart + :language: dart + :emphasize-lines: 7, 10, 16 + +.. _flutter-nested-collections-realm-value: + +Nested Collections of RealmValue +```````````````````````````````` + +.. versionadded:: v2.0.0 + +Starting with Flutter SDK v2.0.0, a ``RealmValue`` data type can +contain collections (a list or map, but *not* a set) of ``RealmValue`` elements. + +These collections of mixed data can be nested and can contain other +collections. They can also be :ref:`listened to for changes like a +normal collection `. + +You can leverage these nested collections to define unstructured +or variable data in your data model. For more information, refer to +:ref:``. -Access the type of data with ``RealmValue.type`` and the value with ``RealmValue.value``. +To create nested collections of mixed data in your app, define the mixed type +property in your data model the same way you would any other ``RealmValue`` type. +Then, you can create the list or map collections using ``RealmValue.from()``. -.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-type-value.dart +.. literalinclude:: /examples/generated/flutter/data_types_test.snippet.realm-value-nested-collections.dart :language: dart Uint8List @@ -371,7 +451,7 @@ Uint8List `Uint8List `__ is a binary data type from `dart:typed_data `__. -You can use this binary data type in object models and property values. +You can use this data type in object models and property values. To define a property as ``Uint8List``, you must first import ``dart:typed_data``. Then, set the object's type as ``Uint8List`` in your :ref:`object model diff --git a/source/sdk/flutter/realm-database/model-data/define-realm-object-schema.txt b/source/sdk/flutter/realm-database/model-data/define-realm-object-schema.txt index fdb94d0322..6934cdf7a9 100644 --- a/source/sdk/flutter/realm-database/model-data/define-realm-object-schema.txt +++ b/source/sdk/flutter/realm-database/model-data/define-realm-object-schema.txt @@ -6,6 +6,7 @@ Define a Realm Object Schema - Flutter SDK .. meta:: :keywords: code example, android, ios, data modeling + :description: Define the properties and relationships of database objects within your data model. .. facet:: :name: genre @@ -233,6 +234,67 @@ If you're using Atlas Device Sync, the name that you specify in the :language: dart :emphasize-lines: 15-16 +.. _flutter-model-unstructured-data: + +Model Unstructured Data +----------------------- + +.. versionadded:: 2.0.0 + +Starting in Flutter SDK version 2.0.0, you can +:ref:`define nested collections +of mixed data ` +within a ``RealmValue`` property. + +The ability to nest collections of mixed data enables you to define data +that doesn't otherwise conform to an expected schema, including data with +variable structure or data whose shape or type is not known at runtime. +For example, you might have highly variable user-created objects, event logs, +or survey response data that are collected and stored in a variety of JSON +formats. This approach allows you to :ref:`react to changes ` +in the nested data and to update specific elements, but it is less +performant than using a structured schema or serializing JSON blobs +into a single string property. + +To model unstructured data in your schema using collections of mixed type, +define the appropriate properties in your schema as +:ref:`RealmValue ` types. You can then set these +``RealmValue`` properties as a :ref:`RealmList ` or a +:ref:`RealmMap ` collection of ``RealmValue`` elements. +Note that ``RealmValue`` *cannot* represent a ``RealmSet`` or an embedded object. + +For example, you might use a ``RealmValue`` that contains a map of mixed +data when modeling a variable event log object: + +.. literalinclude:: /examples/generated/flutter/define_realm_model_test.snippet.unstructured-data-model.dart + :language: dart + :emphasize-lines: 9 + :caption: Data model + +.. io-code-block:: + :copyable: true + :caption: Create unstructured data + + .. input:: /examples/generated/flutter/define_realm_model_test.snippet.create-unstructured-data-example.dart + :language: dart + + .. output:: + :language: shell + + Event Type: purchase + Timestamp: 2024-03-18 13:50:58.402979Z + User ID: user123 + Details: + Item: + ipAddress: RealmValue(192.168.1.1) + items: RealmValue([RealmValue({id: RealmValue(1), name: RealmValue(Laptop), price: RealmValue(1200.0)}), RealmValue({id: RealmValue(2), name: RealmValue(Mouse), price: RealmValue(49.99)})]) + total: RealmValue(1249.99) + +.. tip:: + + - Use a map of mixed data types when the type is unknown but each value will have a unique identifier. + - Use a list of mixed data types when the type is unknown but the order of objects is meaningful. + .. _flutter-generate-realm-object: Generate the RealmObject