diff --git a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/DataTypesTest.kt b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/DataTypesTest.kt index 7b7d002b75..a33fe28e4e 100644 --- a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/DataTypesTest.kt +++ b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/DataTypesTest.kt @@ -38,76 +38,6 @@ class Contact : RealmObject { } class DataTypesTest : RealmTest() { - @Test - fun createEmbeddedObject() { - runBlocking { - val config = RealmConfiguration.Builder( - setOf(Contact::class, EmbeddedAddress::class) - ) - .build() - val realm = Realm.open(config) - realm.write { - val contact = copyToRealm(Contact()) - contact.apply { - name = "Nick Riviera" - address = EmbeddedAddress().apply { - street = "123 Fake St" - city = "Some Town" - state = "MA" - postalCode = "12345" - } - } - } - - val asyncCall: Deferred = async { - val address: EmbeddedAddress = realm.query().find().first() - val contact: Contact = realm.query().find().first() - // :snippet-start: update-embedded-object - // Modify embedded object properties in a write transaction - realm.write { - // Fetch the objects - val addressToUpdate = findLatest(address) ?: error("Cannot find latest version of embedded object") - val contactToUpdate = findLatest(contact) ?: error("Cannot find latest version of parent object") - - // Update a single embedded object property directly - addressToUpdate.street = "100 10th St N" - - // Update multiple properties - addressToUpdate.apply { - street = "202 Coconut Court" - city = "Los Angeles" - state = "CA" - postalCode = "90210" - } - - // Update property through the parent object - contactToUpdate.address?.state = "NY" - } - // :snippet-end: - - // :snippet-start: overwrite-embedded-object - // Overwrite the embedded object in a write transaction - realm.write { - // Fetch the parent object - val parentObject: Contact = - realm.query("name == 'Nick Riviera'").find().first() - - // Overwrite the embedded object (deletes the existing object) - parentObject.address = EmbeddedAddress().apply { - street = "202 Coconut Court" - city = "Los Angeles" - state = "CA" - postalCode = "90210" - } - } - // :snippet-end: - - } - asyncCall.cancel() - realm.close() - Realm.deleteRealm(config) - } - } @Test fun queryEmbeddedObject() { diff --git a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/UpdateTest.kt b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/UpdateTest.kt index c97a38cea1..aba73731ae 100644 --- a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/UpdateTest.kt +++ b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/UpdateTest.kt @@ -3,56 +3,444 @@ package com.mongodb.realm.realmkmmapp import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.UpdatePolicy -import io.realm.kotlin.ext.query -import io.realm.kotlin.ext.realmDictionaryOf +import io.realm.kotlin.ext.* import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.query.RealmResults +import io.realm.kotlin.types.MutableRealmInt +import io.realm.kotlin.types.RealmAny +import io.realm.kotlin.types.RealmList import org.mongodb.kbson.ObjectId -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue +import kotlin.test.* // :replace-start: { // "terms": { -// "RealmDictionary_": "", // "ExampleRealmObject_": "", -// "frogObjectId": "ObjectId()" +// "RealmObjectProperties_": "", +// "ExampleEmbeddedObject_": "", +// "ExampleRelationship_": "", +// "ExampleRealmList_": "", +// "ExampleRealmDictionary_": "", +// "ExampleRealmSet_": "", +// "RealmEmbeddedObject_": "" // } // } +/* +** Snippets used on Update page ** +** Object models defined in Schema.kt ** +*/ + class UpdateTest: RealmTest() { + @Test - fun updateRealmDictionaryType() { + fun updateRealmObject() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(ExampleRealmObject_Frog::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + val PRIMARY_KEY_VALUE = ObjectId() + + realm.write { + deleteAll() + copyToRealm(ExampleRealmObject_Frog().apply { + _id = PRIMARY_KEY_VALUE + name = "Kermit" + age = 42 + owner = "Jim Henson" + }) + } + // :snippet-start: update-a-realm-object + // Query and update a realm object in a single write transaction + realm.write { + val liveFrog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + assertEquals(liveFrog._id, PRIMARY_KEY_VALUE) // :remove: + liveFrog.name = "Michigan J. Frog" + liveFrog.age += 1 + } + // :snippet-end: + // :snippet-start: fetch-latest-to-update-object + val frozenFrog = realm.query().find().first() + assertEquals("Michigan J. Frog", frozenFrog.name) // :remove: + assertEquals(43, frozenFrog.age) // :remove: + + // Open a write transaction + realm.write { + // Get the live frog object with findLatest(), then update it + findLatest(frozenFrog)?.let { liveFrog -> + liveFrog.name = "Kermit" + liveFrog.age -= 1 + } + } + // :snippet-end: + realm.write { + val updatedFrog = query().find().first() + assertEquals("Kermit", updatedFrog.name) + assertEquals(42, updatedFrog.age) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateEmbeddedObject() { runBlocking { val config = RealmConfiguration.Builder( - schema = setOf(RealmDictionary_Frog::class) // Pass the defined class as the object schema + setOf( + ExampleRelationship_Business::class, + ExampleRelationship_Contact::class, + ExampleRelationship_EmbeddedCountry::class, + ExampleRelationship_EmbeddedAddress::class + ) ) .inMemory() .build() val realm = Realm.open(config) - Log.v("Successfully opened realm: ${realm.configuration.name}") + Log.v("Successfully opened realm: ${realm.configuration.path}") + + val PRIMARY_KEY_VALUE = ObjectId() + realm.write { + deleteAll() + copyToRealm(ExampleRelationship_Contact().apply { + _id = PRIMARY_KEY_VALUE + name = "Kermit" + address = ExampleRelationship_EmbeddedAddress().apply { + propertyOwner = ExampleRelationship_Contact().apply { name = "Mr. Frog" } + street = "123 Pond St" + country = ExampleRelationship_EmbeddedCountry().apply { name = "United States" } + } + }) + val currentContact = query().find().first() + assertEquals("Kermit", currentContact.name) + assertEquals("Mr. Frog", currentContact.address?.propertyOwner?.name) + assertEquals("123 Pond St", currentContact.address?.street) + assertEquals("United States", currentContact.address?.country?.name) + } + // :snippet-start: update-embedded-object + realm.write { + val parentObject = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + val embeddedChildObject = parentObject.address + + // Update a property through the embedded child object + embeddedChildObject?.propertyOwner?.name = "Michigan J. Frog" + assertEquals("Michigan J. Frog", embeddedChildObject?.propertyOwner?.name) // :remove: + // Update a property through the parent object + parentObject.address?.country?.name = "Brazil" + assertEquals("Brazil", parentObject.address?.country?.name) // :remove: + + // Update multiple properties + val embeddedAddress = query("street == $0", "123 Pond St").find().first() + embeddedAddress.apply { + propertyOwner?.name = "Kermit Sr." + street = "456 Lily Pad Ln" + country?.name = "Canada" + } + assertEquals("Kermit Sr.", embeddedAddress.propertyOwner?.name) // :remove: + assertEquals("456 Lily Pad Ln", embeddedAddress.street) // :remove: + assertEquals("Canada", embeddedAddress.country?.name) // :remove: + } + // :snippet-end: + // :snippet-start: overwrite-embedded-object + realm.write { + val parentObject = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + + val newAddress = ExampleRelationship_EmbeddedAddress().apply { + propertyOwner = ExampleRelationship_Contact().apply { name = "Michigan J. Frog" } + street = "456 Lily Pad Ln" + country = ExampleRelationship_EmbeddedCountry().apply { name = "Canada" } + } + // Overwrite the embedded object with the new instance (deletes the existing object) + parentObject.address = newAddress + } + // :snippet-end: + realm.write { + val deleteContact = query().find().first() + assertEquals("Kermit", deleteContact.name) + assertEquals("Michigan J. Frog", deleteContact.address?.propertyOwner?.name) + assertEquals("456 Lily Pad Ln", deleteContact.address?.street) + assertEquals("Canada", deleteContact.address?.country?.name) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateMutableRealmInt() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(RealmObjectProperties_Frog::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + val PRIMARY_KEY_VALUE = ObjectId() + realm.write { + deleteAll() + copyToRealm(RealmObjectProperties_Frog().apply { + name = "Michigan J. Frog" + _id = PRIMARY_KEY_VALUE + fliesEaten = MutableRealmInt.create(1) + }) + } + // :snippet-start: update-mutablerealm-property + // Open a write transaction + realm.write { + // Get the live object + val frog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + val counter: MutableRealmInt? = frog.fliesEaten + counter?.get() // 1 + assertEquals(1, counter?.get()) // :remove: + + // Increment the value of the MutableRealmInt property + // ** Note use of decrement() with negative value ** + counter?.increment(0) // 1 + counter?.increment(5) // 6 + counter?.decrement(-2) // 8 + assertEquals(8, counter?.get()) // :remove: + + // Decrement the value of the MutableRealmInt property + // ** Note use of increment() with negative value ** + counter?.decrement(0) // 8 + counter?.decrement(2) // 6 + counter?.increment(-1) // 5 + assertEquals(5, counter?.get()) // :remove: + + // Set the value of the MutableRealmInt property + // ** Use set() with caution ** + counter?.set(0)// 0 + assertEquals(0, counter?.get()) // :remove: + deleteAll() // :remove: + } + // :snippet-end: + realm.close() + } + } + + @Test + fun updateRealmAny(){ + runBlocking { + val config = RealmConfiguration.Builder(setOf(RealmObjectProperties_Frog::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + realm.write { + deleteAll() + copyToRealm(RealmObjectProperties_Frog().apply { + name = "Kermit" + favoriteThings = realmListOf( + RealmAny.create(42), + RealmAny.create("rainbows"), + RealmAny.create(RealmObjectProperties_Frog().apply { + name = "Kermit Jr." + }) + ) + }) + } + // :snippet-start: update-realmany-property + // Find favoriteThing that is an Int + // Convert the value to Double and update the favoriteThing property + realm.write { + val kermit = query().find().first() + val realmAny: RealmList = kermit.favoriteThings + + for (i in realmAny.indices) { + val thing = realmAny[i] + if (thing?.type == RealmAny.Type.INT) { + val intValue = thing.asInt() + val doubleValue = intValue.toDouble() + realmAny[i] = RealmAny.create(doubleValue) + } + } + } + assertEquals(42.0, realm.query().find().first().favoriteThings[0]?.asDouble()) // :remove: + + // Overwrite all existing favoriteThing properties + // ** Null clears the property value ** + realm.write { + val frog = query().find().first() + val realmAny: RealmList = frog.favoriteThings + + realmAny[0] = RealmAny.create("sunshine") + realmAny[1] = RealmAny.create(RealmObjectProperties_Frog().apply { name = "Kermit Sr." }) + realmAny[2] = null + } + // :snippet-end: + realm.write { + val frog = query().find().first() + assertEquals("sunshine", frog.favoriteThings[0]?.asString()) + assertEquals("Kermit Sr.", frog.favoriteThings[1]?.asRealmObject()?.name) + assertNull(frog.favoriteThings[2]) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateRealmCollection() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(ExampleRealmObject_Frog::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + + realm.write { + deleteAll() + copyToRealm(ExampleRealmObject_Frog().apply { + name = "Kermit" + age = 42 + owner = "Jim Henson" + }) + copyToRealm(ExampleRealmObject_Frog().apply { + name = "Wirt" + age = 1 + owner = "L'oric" + }) + copyToRealm(ExampleRealmObject_Frog().apply { + name = "Mr. Toad" + age = 2 + owner = "Kenneth Grahame" + }) + } + // :snippet-start: update-a-collection + val tadpoles = realm.query("age <= $0", 2) + for (tadpole in tadpoles.find()) { + realm.write { + findLatest(tadpole)?.name = tadpole.name + " Jr." + } + } + // :snippet-end: + realm.write { + val updated = query("age <= $0", 2).find() + assertEquals(2, updated.size) + assertContains("Jr.", updated[0].name) + assertContains("Jr.", updated[1].name) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateRealmList() { + runBlocking { + val config = RealmConfiguration.Builder(setOf( + ExampleRealmList_Frog::class, ExampleRealmList_Pond::class, ExampleEmbeddedObject_EmbeddedForest::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + + realm.write { + deleteAll() + copyToRealm(ExampleRealmList_Frog().apply { + name = "Kermit" + favoritePonds.addAll(realmListOf( + ExampleRealmList_Pond().apply { name = "Big Little Pond" }, + ExampleRealmList_Pond().apply { name = "Blue Pond" } + )) + }) + } + // :snippet-start: update-realm-list + realm.write { + // Get the live object + val realmList = query("name = $0", "Kermit").first().find()!!.favoritePonds + realmList[0].name = "Picnic Pond" + realmList.set(1, ExampleRealmList_Pond().apply { name = "Big Pond" }) + } + // :snippet-end: + realm.write { + val kermit = query("name = $0", "Kermit").find().first() + assertEquals("Picnic Pond", kermit.favoritePonds[0].name) + assertEquals("Big Pond", kermit.favoritePonds[1].name) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateRealmSet() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(ExampleRealmSet_Frog::class, ExampleRealmSet_Snack::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") - // Delete frogs to make this test successful on consecutive reruns realm.write { - // fetch all frogs from the realm - val frogs: RealmResults = this.query().find() - // call delete on the results of a query to delete those objects permanently - delete(frogs) - assertEquals(0, frogs.size) + deleteAll() // :remove: + copyToRealm(ExampleRealmSet_Frog().apply { + name = "Kermit" + favoriteSnacks.addAll(setOf( + ExampleRealmSet_Snack().apply { name = "flies" }, + ExampleRealmSet_Snack().apply { name = "crickets" }, + ExampleRealmSet_Snack().apply { name = "worms" } + ))}) + copyToRealm(ExampleRealmSet_Frog().apply { + name = "Froggy the Frog" + favoriteWeather.add("rain") + }) + } + // :snippet-start: update-realm-set + // Find a frog in the realm + val kermit = realm.query("name = $0", "Kermit").find().first() + val realmSet = kermit.favoriteSnacks + // Update the name of each snack in the set + for (snack in realmSet) { + realm.write { + findLatest(snack)?.name = snack.name.uppercase() + } + } + + realm.write { + // Find all frogs who like rain + val frogsWhoLikeRain = realm.query("favoriteWeather CONTAINS $0", "rain").find() + // Add thunderstorms to their favoriteWeather set + for (frog in frogsWhoLikeRain) { + val latestFrog = findLatest(frog) + latestFrog?.favoriteWeather?.add("thunderstorms") + } + } + // :snippet-end: + realm.write { + assertEquals(3, realmSet.size) + val updatedRealmSet = realm.query("name = $0", "Kermit").first().find()!!.favoriteSnacks + for (snack in updatedRealmSet) { + val expectedName = snack.name.uppercase() + assertEquals(expectedName, snack.name) + } + val froggy = realm.query("name = $0", "Froggy the Frog").find().first() + assertTrue(froggy.favoriteWeather.contains("thunderstorms")) + deleteAll() } + realm.close() + } + } + + @Test + fun updateRealmDictionaryType() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(RealmDictionary_Frog::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.name}") - // Create an object with a dictionary property to set up the test realm.write { - this.copyToRealm(RealmDictionary_Frog().apply { + deleteAll() + copyToRealm(RealmDictionary_Frog().apply { name = "Kermit" favoritePondsByForest = realmDictionaryOf("Hundred Acre Wood" to "Picnic Pond", "Lothlorien" to "Linya") }) } // :snippet-start: update-realm-dictionary // Find frogs who have forests with favorite ponds - val frogs = realm.query().find() - val frogsWithFavoritePonds = frogs.query("favoritePondsByForest.@count > 1").find() - val thisFrog = frogsWithFavoritePonds.first() + val thisFrog = realm.query("favoritePondsByForest.@count > 1").find().first() // :remove-start: assertEquals(2, thisFrog.favoritePondsByForest.size) assertTrue(thisFrog.favoritePondsByForest.containsKey("Hundred Acre Wood")) @@ -60,10 +448,7 @@ class UpdateTest: RealmTest() { // Update the value for a key if it exists if (thisFrog.favoritePondsByForest.containsKey("Hundred Acre Wood")) { realm.write { - findLatest(thisFrog)?.favoritePondsByForest?.set( - "Hundred Acre Wood", - "Lily Pad Pond" - ) + findLatest(thisFrog)?.favoritePondsByForest?.set("Lothlorien", "Lily Pad Pond") } } // Add a new key-value pair @@ -75,60 +460,198 @@ class UpdateTest: RealmTest() { assertTrue(thisFrogUpdated.favoritePondsByForest.containsValue("Lily Pad Pond")) assertTrue(thisFrogUpdated.favoritePondsByForest.containsKey("Sherwood Forest")) assertTrue(thisFrogUpdated.favoritePondsByForest["Sherwood Forest"] == "Miller Pond") + realm.write { + deleteAll() + } realm.close() } } @Test fun upsertAnObjectTest() { - val REALM_NAME = getRandom() - runBlocking { - val config = RealmConfiguration.Builder(setOf(ExampleRealmObject_Frog::class, Sample::class)) - .name(REALM_NAME) - .directory(TMP_PATH) + val config = RealmConfiguration.Builder(setOf(ExampleRealmObject_Frog::class)) + .inMemory() .build() val realm = Realm.open(config) - Log.v("Successfully opened realm: ${realm.configuration.name}") - // insert an object that meets our example query - val frogObjectId = ObjectId() - realm.writeBlocking { - // Remove any existing matching frogs to start fresh - val frogs: RealmResults = - this.query("name == 'Wirt'").find() - delete(frogs) - this.copyToRealm(ExampleRealmObject_Frog().apply { - _id = frogObjectId - name = "Wirt" - age = 45 - species = "Green" - owner = "Jim" - }) - } - val frogsMatchingId: RealmResults = - realm.query("_id == $0", frogObjectId).find() - assertEquals(1, frogsMatchingId.count()) + Log.v("Successfully opened realm: ${realm.configuration.path}") + val PRIMARY_KEY_VALUE = ObjectId() // :snippet-start: upsert-an-object realm.write { - // The ID of a particular frog can either already exist or be a new ObjectId - val frogId = frogObjectId - // If a frog matching the ID exists, update its properties, otherwise create it - this.copyToRealm(ExampleRealmObject_Frog().apply { - _id = frogId + // :remove-start: + deleteAll() + copyToRealm(ExampleRealmObject_Frog().apply { + _id = PRIMARY_KEY_VALUE + name = "Kermit" + }) + val frogObjectId = query("_id == $0", PRIMARY_KEY_VALUE).find() + assertEquals(frogObjectId.first()._id, PRIMARY_KEY_VALUE) + assertEquals(1, frogObjectId.size) + // :remove-end: + val existingFrog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + assertEquals(existingFrog.name, "Kermit") + + // Use copyToRealm() to insert the object with the primary key + // ** UpdatePolicy determines whether to update or throw an error if object already exists** + copyToRealm(ExampleRealmObject_Frog().apply { + _id = PRIMARY_KEY_VALUE name = "Wirt" age = 4 species = "Greyfrog" owner = "L'oric" - }, updatePolicy = UpdatePolicy.ALL) + }, UpdatePolicy.ALL) + + val upsertFrog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + assertEquals(upsertFrog.name, "Wirt") } // :snippet-end: - val updatedFrogsMatchingId: RealmResults = - realm.query("_id == $0", frogObjectId).find() + val updatedFrogsMatchingId = realm.query("_id == $0", PRIMARY_KEY_VALUE).find() assertEquals(1, updatedFrogsMatchingId.count()) assertEquals(4, updatedFrogsMatchingId.first().age) assertEquals("Greyfrog", updatedFrogsMatchingId.first().species) assertEquals("L'oric", updatedFrogsMatchingId.first().owner) + + val thrownException = assertFailsWith { + realm.write { + copyToRealm(ExampleRealmObject_Frog().apply { + _id = PRIMARY_KEY_VALUE + name = "Froggy the Frog" + age = 4 + species = "Tree Frog" + owner = "Jim" + }, UpdatePolicy.ERROR) + deleteAll() + } + } + assertTrue(thrownException.message!!.contains("[RLM_ERR_OBJECT_ALREADY_EXISTS]"), "Exception message did not match expected value.") + realm.close() + } + } + + @Test + fun updateInverseRelationship() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(ExampleRelationship_User::class, ExampleRelationship_Post::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + + realm.write { + deleteAll() + val post1 = ExampleRelationship_Post().apply { title = "Forest Life" } + val post2 = ExampleRelationship_Post().apply { title = "Top Ponds of the Year!" } + copyToRealm(ExampleRelationship_User().apply { + name = "Kermit" + posts = realmListOf(post1, post2) + }) + } + // :snippet-start: update-inverse-relationship + realm.write { + // Update child objects through the parent + val parent = query().find().first() + parent.posts[0].title = "Forest Life Vol. 2" + parent.posts.add(ExampleRelationship_Post().apply { title = "Forest Life Vol. 3" }) + + // Update child objects (Realm automatically updates the backlink collection) + val child = query("title == $0", "Top Ponds of the Year!").find().first() + child.title = "Top Ponds of the Year! Vol. 2" + assertEquals("Top Ponds of the Year! Vol. 2", parent.posts[1].title) + // Update the parent object through the child + child.user[0].name = "Kermit Sr." + + // ** You CANNOT directly modify the backlink collection ** + val readOnlyBacklink: RealmResults = child.user + } + // :snippet-end: + realm.write { + val updatedParent = query().find().first() + assertEquals("Forest Life Vol. 2", updatedParent.posts[0].title) + assertEquals("Forest Life Vol. 3", updatedParent.posts[2].title) + assertEquals("Kermit Sr.", updatedParent.name) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateToOneRelationship() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(ExampleRelationship_Frog::class, ExampleRelationship_Pond::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + realm.write { + deleteAll() + copyToRealm(ExampleRelationship_Frog().apply { + name = "Kermit" + age = 12 + favoritePond = ExampleRelationship_Pond().apply { name = "Picnic Pond" } + bestFriend = ExampleRelationship_Frog().apply { name = "Froggy Jay" } + }) + } + // :snippet-start: update-to-one-relationship + realm.write { + val kermit = query("name == $0", "Kermit").find().first() + // Update a property on the related object + kermit.favoritePond?.name = "Big Pond" + + // Assign a new related object + val newBestFriend = ExampleRelationship_Frog().apply { name = "Froggy Jr." } + kermit.bestFriend = newBestFriend + } + // :snippet-end: + realm.write { + val kermit = query("name == $0", "Kermit").find().first() + assertEquals("Big Pond", kermit.favoritePond?.name) + val froggyJr = query("name == $0", "Froggy Jr.").find().first() + assertEquals(froggyJr, kermit.bestFriend) + deleteAll() + } + realm.close() + } + } + + @Test + fun updateToManyRelationship() { + runBlocking { + val config = RealmConfiguration.Builder(setOf(ExampleRelationship_Forest::class, ExampleRelationship_Frog::class, ExampleRelationship_Pond::class)) + .inMemory() + .build() + val realm = Realm.open(config) + Log.v("Successfully opened realm: ${realm.configuration.path}") + + realm.write { + deleteAll() + copyToRealm(ExampleRelationship_Forest().apply { + name = "Froggy Forest" + frogsThatLiveHere = realmSetOf( + ExampleRelationship_Frog().apply { name = "Kermit" }, + ExampleRelationship_Frog().apply { name = "Froggy Jay" } + ) + nearbyPonds = realmListOf( + ExampleRelationship_Pond().apply { name = "Small Picnic Pond" }, + ExampleRelationship_Pond().apply { name = "Big Pond" } + ) }) + } + // :snippet-start: update-to-many-relationship + realm.write { + val forest = query().find().first() + // Update a property on a related object in the set + forest.frogsThatLiveHere.first().name = "Kermit Sr." + // Add a new related object to the list + forest.frogsThatLiveHere.add(ExampleRelationship_Frog().apply { name = "Froggy Jr." }) + } + // :snippet-end: + realm.write { + val forest = query().find().first() + assertEquals("Kermit Sr.", forest.frogsThatLiveHere.first().name) + assertEquals("Froggy Jr.", forest.frogsThatLiveHere.last().name) + deleteAll() + } realm.close() } } diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.fetch-latest-to-update-object.kt b/source/examples/generated/kotlin/UpdateTest.snippet.fetch-latest-to-update-object.kt new file mode 100644 index 0000000000..11fcb324b9 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.fetch-latest-to-update-object.kt @@ -0,0 +1,10 @@ +val frozenFrog = realm.query().find().first() + +// Open a write transaction +realm.write { + // Get the live frog object with findLatest(), then update it + findLatest(frozenFrog)?.let { liveFrog -> + liveFrog.name = "Kermit" + liveFrog.age -= 1 + } +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.overwrite-embedded-object.kt b/source/examples/generated/kotlin/UpdateTest.snippet.overwrite-embedded-object.kt new file mode 100644 index 0000000000..aa79077f83 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.overwrite-embedded-object.kt @@ -0,0 +1,11 @@ +realm.write { + val parentObject = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + + val newAddress = EmbeddedAddress().apply { + propertyOwner = Contact().apply { name = "Michigan J. Frog" } + street = "456 Lily Pad Ln" + country = EmbeddedCountry().apply { name = "Canada" } + } + // Overwrite the embedded object with the new instance (deletes the existing object) + parentObject.address = newAddress +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-a-collection.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-a-collection.kt new file mode 100644 index 0000000000..4f0cf1b2ab --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-a-collection.kt @@ -0,0 +1,6 @@ +val tadpoles = realm.query("age <= $0", 2) +for (tadpole in tadpoles.find()) { + realm.write { + findLatest(tadpole)?.name = tadpole.name + " Jr." + } +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-a-realm-object.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-a-realm-object.kt new file mode 100644 index 0000000000..4e4a37edaa --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-a-realm-object.kt @@ -0,0 +1,6 @@ +// Query and update a realm object in a single write transaction +realm.write { + val liveFrog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + liveFrog.name = "Michigan J. Frog" + liveFrog.age += 1 +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-embedded-object.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-embedded-object.kt new file mode 100644 index 0000000000..8ecf540c46 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-embedded-object.kt @@ -0,0 +1,17 @@ +realm.write { + val parentObject = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + val embeddedChildObject = parentObject.address + + // Update a property through the embedded child object + embeddedChildObject?.propertyOwner?.name = "Michigan J. Frog" + // Update a property through the parent object + parentObject.address?.country?.name = "Brazil" + + // Update multiple properties + val embeddedAddress = query("street == $0", "123 Pond St").find().first() + embeddedAddress.apply { + propertyOwner?.name = "Kermit Sr." + street = "456 Lily Pad Ln" + country?.name = "Canada" + } +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-inverse-relationship.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-inverse-relationship.kt new file mode 100644 index 0000000000..5df1f15ad2 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-inverse-relationship.kt @@ -0,0 +1,16 @@ +realm.write { + // Update child objects through the parent + val parent = query().find().first() + parent.posts[0].title = "Forest Life Vol. 2" + parent.posts.add(Post().apply { title = "Forest Life Vol. 3" }) + + // Update child objects (Realm automatically updates the backlink collection) + val child = query("title == $0", "Top Ponds of the Year!").find().first() + child.title = "Top Ponds of the Year! Vol. 2" + assertEquals("Top Ponds of the Year! Vol. 2", parent.posts[1].title) + // Update the parent object through the child + child.user[0].name = "Kermit Sr." + + // ** You CANNOT directly modify the backlink collection ** + val readOnlyBacklink: RealmResults = child.user +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-mutablerealm-property.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-mutablerealm-property.kt new file mode 100644 index 0000000000..631757791a --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-mutablerealm-property.kt @@ -0,0 +1,23 @@ +// Open a write transaction +realm.write { + // Get the live object + val frog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + val counter: MutableRealmInt? = frog.fliesEaten + counter?.get() // 1 + + // Increment the value of the MutableRealmInt property + // ** Note use of decrement() with negative value ** + counter?.increment(0) // 1 + counter?.increment(5) // 6 + counter?.decrement(-2) // 8 + + // Decrement the value of the MutableRealmInt property + // ** Note use of increment() with negative value ** + counter?.decrement(0) // 8 + counter?.decrement(2) // 6 + counter?.increment(-1) // 5 + + // Set the value of the MutableRealmInt property + // ** Use set() with caution ** + counter?.set(0)// 0 +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-dictionary.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-dictionary.kt index a54caa19a7..8c0d7911f9 100644 --- a/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-dictionary.kt +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-dictionary.kt @@ -1,14 +1,9 @@ // Find frogs who have forests with favorite ponds -val frogs = realm.query().find() -val frogsWithFavoritePonds = frogs.query("favoritePondsByForest.@count > 1").find() -val thisFrog = frogsWithFavoritePonds.first() +val thisFrog = realm.query("favoritePondsByForest.@count > 1").find().first() // Update the value for a key if it exists if (thisFrog.favoritePondsByForest.containsKey("Hundred Acre Wood")) { realm.write { - findLatest(thisFrog)?.favoritePondsByForest?.set( - "Hundred Acre Wood", - "Lily Pad Pond" - ) + findLatest(thisFrog)?.favoritePondsByForest?.set("Lothlorien", "Lily Pad Pond") } } // Add a new key-value pair diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-list.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-list.kt new file mode 100644 index 0000000000..d61a17e159 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-list.kt @@ -0,0 +1,6 @@ +realm.write { + // Get the live object + val realmList = query("name = $0", "Kermit").first().find()!!.favoritePonds + realmList[0].name = "Picnic Pond" + realmList.set(1, Pond().apply { name = "Big Pond" }) +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-set.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-set.kt new file mode 100644 index 0000000000..f34ce74e66 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-realm-set.kt @@ -0,0 +1,19 @@ +// Find a frog in the realm +val kermit = realm.query("name = $0", "Kermit").find().first() +val realmSet = kermit.favoriteSnacks +// Update the name of each snack in the set +for (snack in realmSet) { + realm.write { + findLatest(snack)?.name = snack.name.uppercase() + } +} + +realm.write { + // Find all frogs who like rain + val frogsWhoLikeRain = realm.query("favoriteWeather CONTAINS $0", "rain").find() + // Add thunderstorms to their favoriteWeather set + for (frog in frogsWhoLikeRain) { + val latestFrog = findLatest(frog) + latestFrog?.favoriteWeather?.add("thunderstorms") + } +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-realmany-property.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-realmany-property.kt new file mode 100644 index 0000000000..2d8aeb37e0 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-realmany-property.kt @@ -0,0 +1,26 @@ +// Find favoriteThing that is an Int +// Convert the value to Double and update the favoriteThing property +realm.write { +val kermit = query().find().first() +val realmAny: RealmList = kermit.favoriteThings + + for (i in realmAny.indices) { + val thing = realmAny[i] + if (thing?.type == RealmAny.Type.INT) { + val intValue = thing.asInt() + val doubleValue = intValue.toDouble() + realmAny[i] = RealmAny.create(doubleValue) + } + } +} + +// Overwrite all existing favoriteThing properties +// ** Null clears the property value ** +realm.write { + val frog = query().find().first() + val realmAny: RealmList = frog.favoriteThings + + realmAny[0] = RealmAny.create("sunshine") + realmAny[1] = RealmAny.create(Frog().apply { name = "Kermit Sr." }) + realmAny[2] = null +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-to-many-relationship.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-to-many-relationship.kt new file mode 100644 index 0000000000..98eccaf1f7 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-to-many-relationship.kt @@ -0,0 +1,7 @@ +realm.write { + val forest = query().find().first() + // Update a property on a related object in the set + forest.frogsThatLiveHere.first().name = "Kermit Sr." + // Add a new related object to the list + forest.frogsThatLiveHere.add(Frog().apply { name = "Froggy Jr." }) +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.update-to-one-relationship.kt b/source/examples/generated/kotlin/UpdateTest.snippet.update-to-one-relationship.kt new file mode 100644 index 0000000000..9c463b1756 --- /dev/null +++ b/source/examples/generated/kotlin/UpdateTest.snippet.update-to-one-relationship.kt @@ -0,0 +1,9 @@ +realm.write { + val kermit = query("name == $0", "Kermit").find().first() + // Update a property on the related object + kermit.favoritePond?.name = "Big Pond" + + // Assign a new related object + val newBestFriend = Frog().apply { name = "Froggy Jr." } + kermit.bestFriend = newBestFriend +} diff --git a/source/examples/generated/kotlin/UpdateTest.snippet.upsert-an-object.kt b/source/examples/generated/kotlin/UpdateTest.snippet.upsert-an-object.kt index 54e6906c2e..94ea1d3d0b 100644 --- a/source/examples/generated/kotlin/UpdateTest.snippet.upsert-an-object.kt +++ b/source/examples/generated/kotlin/UpdateTest.snippet.upsert-an-object.kt @@ -1,12 +1,17 @@ realm.write { - // The ID of a particular frog can either already exist or be a new ObjectId - val frogId = ObjectId() - // If a frog matching the ID exists, update its properties, otherwise create it - this.copyToRealm(Frog().apply { - _id = frogId + val existingFrog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + assertEquals(existingFrog.name, "Kermit") + + // Use copyToRealm() to insert the object with the primary key + // ** UpdatePolicy determines whether to update or throw an error if object already exists** + copyToRealm(Frog().apply { + _id = PRIMARY_KEY_VALUE name = "Wirt" age = 4 species = "Greyfrog" owner = "L'oric" - }, updatePolicy = UpdatePolicy.ALL) + }, UpdatePolicy.ALL) + + val upsertFrog = query("_id == $0", PRIMARY_KEY_VALUE).find().first() + assertEquals(upsertFrog.name, "Wirt") } diff --git a/source/includes/kotlin-write-to-synced-realm.rst b/source/includes/kotlin-write-to-synced-realm.rst new file mode 100644 index 0000000000..9c11b7e245 --- /dev/null +++ b/source/includes/kotlin-write-to-synced-realm.rst @@ -0,0 +1,6 @@ +.. note:: Write to a Synced Realm + + The syntax to write to a realm is the same for a local or + a synced realm. However, there are additional considerations that determine + whether the write operation in a synced realm is successful. For more + information, refer to :ref:``. \ No newline at end of file diff --git a/source/sdk/kotlin/realm-database/crud/create.txt b/source/sdk/kotlin/realm-database/crud/create.txt index 4f401b1b9d..da651f85f4 100644 --- a/source/sdk/kotlin/realm-database/crud/create.txt +++ b/source/sdk/kotlin/realm-database/crud/create.txt @@ -299,6 +299,8 @@ In the following example, we instantiate a new ``Frog`` object with a .. To learn how to increment, decrement, and set the counter value after .. you create the object, refer to :ref:``. +.. _kotlin-create-realmany: + Create a RealmAny (Mixed) Property ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -318,10 +320,8 @@ In the following example, we instantiate a new ``Frog`` object with a :language: kotlin After you create the object, you *must* know the stored value type -to work with the ``RealmAny`` property. - -.. To learn how to work with ``RealmAny`` properties after -.. you create the object, refer to :ref:``. +to work with the ``RealmAny`` property. To learn how to update ``RealmAny`` +properties after you create the object, refer to :ref:``. .. _kotlin-create-a-collection: @@ -353,6 +353,8 @@ Kotlin classes and are not persisted to the realm. listen for changes. For more information, refer to :ref:``. +.. _kotlin-create-a-realmlist: + Create a RealmList ~~~~~~~~~~~~~~~~~~ @@ -375,12 +377,13 @@ initial values for several ``RealmList`` properties: .. literalinclude:: /examples/generated/kotlin/CreateTest.snippet.create-realm-list.kt :language: kotlin +.. _kotlin-create-a-realmset: + Create a RealmSet Property ~~~~~~~~~~~~~~~~~~~~~~~~~~ To create a new object instance with a -`RealmSet -<{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-set/index.html>`__ +`RealmSet <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-set/index.html>`__ property, instantiate an object and pass any values of a supported type to the ``RealmSet`` property. For a list of valid types that ``RealmSet`` can hold, refer to :ref:``. @@ -400,6 +403,8 @@ properties: .. literalinclude:: /examples/generated/kotlin/CreateTest.snippet.create-realm-set.kt :language: kotlin +.. _kotlin-create-a-realmdictionary: + Create a Dictionary Property ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -492,10 +497,9 @@ backlinks ``posts`` property that references a list of ``Post`` objects: :language: kotlin After you create the object, you can access the backlinks collection -property to get the child objects. You *cannot* directly modify -the backlink itself. - -.. For more information, refer to :ref:``. +property to get the child objects, but you *cannot* directly modify +the backlink itself. For more information, refer to +:ref:``. .. _kotlin-create-unmanaged-copy: diff --git a/source/sdk/kotlin/realm-database/crud/delete.txt b/source/sdk/kotlin/realm-database/crud/delete.txt index e498f85ea4..30bc3fdd74 100644 --- a/source/sdk/kotlin/realm-database/crud/delete.txt +++ b/source/sdk/kotlin/realm-database/crud/delete.txt @@ -129,7 +129,7 @@ To delete specific objects from a realm: You can check whether an object is still valid to use by calling `isValid() <{+kotlin-local-prefix+}io.realm.kotlin.ext/is-valid.html>`__. - Deleted items return ``false``. + Deleted objects return ``false``. .. _kotlin-delete-a-single-object: @@ -294,16 +294,20 @@ until you manually delete them. Alternatively, deleting a Realm object from a realm also deletes that object from any collection instances that contain the object. -Remove Items from a RealmList -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _kotlin-remove-realmlist-elements: + +Remove Elements from a RealmList +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can remove one or more items from a ``RealmList`` at a time: +You can remove one or more elements from a +`RealmList <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-list/index.html>`__ +at a time: -- To remove one item from the list, pass the element to +- To remove one element from the list, pass the element to `list.remove() <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-list/index.html#731697687%2FFunctions%2F878332154>`__. -- To remove one item at a specified index in the list, pass the index to +- To remove one element at a specified index in the list, pass the index to `list.removeAt() <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-list/index.html#1522148962%2FFunctions%2F878332154>`__. -- To remove multiple items from the list, pass the elements to +- To remove multiple elements from the list, pass the elements to `list.removeAll() <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-list/index.html#1522148962%2FFunctions%2F878332154>`__. You can also remove *all* list elements at once by calling @@ -322,15 +326,19 @@ In the following example, we have a ``Forest`` object with a list of .. literalinclude:: /examples/generated/kotlin/DeleteTest.snippet.list-clear.kt :language: kotlin -Remove Items from a RealmSet -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _kotlin-remove-realmset-elements: + +Remove Elements from a RealmSet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can remove one or more items from a ``RealmSet`` at a time: +You can remove one or more elements from a +`RealmSet <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-set/index.html>`__ +at a time: -- To remove one item from the set, pass the element +- To remove one element from the set, pass the element you want to delete to `set.remove() <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-set/index.html#-1503494415%2FFunctions%2F-1651551339>`__. -- To remove multiple items from the set, pass the +- To remove multiple elements from the set, pass the elements you want to delete to `set.removeAll() <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-set/index.html#430447804%2FFunctions%2F-1651551339>`__. @@ -355,7 +363,9 @@ In the following example, we have a ``Frog`` object with a set of Remove Dictionary Keys/Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can remove ``RealmDictionary`` entries in a few ways: +You can remove +`RealmDictionary <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-dictionary/index.html>`__ +entries in a few ways: - To remove the value but keep the key, set the key to ``null`` (the dictionary's value must be nullable) diff --git a/source/sdk/kotlin/realm-database/crud/update.txt b/source/sdk/kotlin/realm-database/crud/update.txt index 03966ae7d7..5090797107 100644 --- a/source/sdk/kotlin/realm-database/crud/update.txt +++ b/source/sdk/kotlin/realm-database/crud/update.txt @@ -10,71 +10,151 @@ Update Realm Objects - Kotlin SDK :depth: 2 :class: singlecol -.. note:: - - You can only modify objects in a realm within a - :ref:`write transaction `. +This page describes how to update an existing Realm object in a local or synced +realm using the Kotlin SDK. To learn more about creating objects in a +realm, refer to :ref:``. + +Realm supports update and upsert operations on Realm objects and embedded +objects. An **upsert operation** either inserts a new instance of an object +or updates an existing object that meets certain criteria. For more information, +refer to the :ref:`` section on this page. + +You *cannot* update asymmetric objects. This is because asymmetric objects +are special write-only objects that do not persist to the realm. For information +on how to use asymmetric objects in your application, +refer to :ref:`kotlin-stream-data-to-atlas`. + +.. note:: Write to a Synced Realm + + The syntax to update an object in a realm is the same for a local or + a synced realm. However, there are additional considerations that determine + whether the write operation in a synced realm is successful. For more + information, refer to :ref:``. + +Update Operations +----------------- + +All operations that modify a realm - including update and upsert operations - +must be performed inside of a write transaction. Write transactions +are passed to the realm's +`write() <{+kotlin-local-prefix+}io.realm.kotlin/-realm/write.html>`__ +or +`writeBlocking() <{+kotlin-local-prefix+}io.realm.kotlin/-realm/write-blocking.html>`__ +method. Within this callback, you can access a +`MutableRealm <{+kotlin-local-prefix+}io.realm.kotlin/-mutable-realm/index.html>`__ +instance, and then update objects within the realm. For more information on +write transactions and how Realm handles them, refer to +:ref:``. + +Additionally, you can only modify live objects, which are only accessible +inside of a write transaction. You can convert a frozen object to a +live object in a transaction with `mutableRealm.findLatest() +<{+kotlin-local-prefix+}io.realm.kotlin/-mutable-realm/find-latest.html>`__. + +.. example:: Convert Frozen Object Before Modifying + + .. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.fetch-latest-to-update-object.kt + :language: kotlin .. _kotlin-modify-an-object: -Update Object Properties ------------------------- +Update an Existing Object +------------------------- -To modify an object stored within a realm: +To modify the properties of a Realm object or embedded object stored +within a realm: 1. Open a write transaction with `realm.write() <{+kotlin-local-prefix+}io.realm.kotlin/-realm/write.html>`__ or `realm.writeBlocking() <{+kotlin-local-prefix+}io.realm.kotlin/-realm/write-blocking.html>`__. -#. Query the transaction's mutable realm with `realm.query() - <{+kotlin-local-prefix+}io.realm.kotlin.query/-realm-query/query.html>`__. - Specify the object type as a type parameter passed to :file:`query()`. - To ensure your query returns the correct object, filter with unique - identifying information such as a primary key value. +#. Get the live objects by querying the transaction's mutable realm + for the objects that you want to modify using + `query() <{+kotlin-local-prefix+}io.realm.kotlin.query/-realm-query/query.html>`__: -#. Change an object property within the - :ref:`write transaction `. The SDK - automatically persists changes to the realm. + #. Specify the object type as a type parameter passed to ``query()``. + #. (Optional) Filter the set of returned objects by specifying a query. + If you don't include a query filter, you return all objects of the + specified type. For more information on querying with the Kotlin SDK, + refer to :ref:``. -.. literalinclude:: /examples/generated/kotlin/CRUDTest.snippet.modify-an-object.kt - :language: kotlin + .. important:: Objects Must Be Live -.. note:: Updating Strings or Byte arrays + You can only modify live objects. If your query occurs outside of the + write transaction, you must convert the frozen objects + to live objects in the transaction with + ``mutableRealm.findLatest()``. + +#. Modify the object within the write transaction. You can use an `apply block + `__ + to configure multiple properties at once. All changes are + automatically persisted to the realm when Realm commits the write + transaction. + +.. note:: Updating Strings or Byte Arrays Because Realm operates on fields as a whole, it's not possible to directly update individual elements of strings or byte arrays. Instead, - you'll need to read the whole field, make your modification to individual + you must read the whole field, make your modification to individual elements, and then write the entire field back again in a transaction block. -Update a Dictionary Property -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Update a Realm Object +~~~~~~~~~~~~~~~~~~~~~ -You can update a -`RealmDictionary <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-dictionary/index.html>`__ -as you would a Kotlin Map. +To update a Realm object, query for the type using a filter that returns the +specific object that you want to update. Then, modify the object properties. -.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-realm-dictionary.kt +.. tip:: Use Unique Identifying Information + + We recommend filtering with unique identifying information such as + a primary key value to ensure your query returns the correct object. + +In the following example, we query a ``Frog`` object by primary key, +then update the ``name`` and ``age`` properties: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-a-realm-object.kt :language: kotlin -Update an Embedded Object Property -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _kotlin-update-embedded-object: + +Update an Embedded Object +~~~~~~~~~~~~~~~~~~~~~~~~~ -To update a property in an -`EmbeddedRealmObject <{+kotlin-local-prefix+}io.realm.kotlin.types/-embedded-realm-object/index.html>`__, -fetch the object and reassign the embedded object properties in a write -transaction: +You can update an embedded object by modifying individual properties +or by overwriting the entire embedded object. + +.. tip:: Access Embedded Objects with Dot Notation + + You can use dot notation to access embedded object + properties as if it were in a regular, nested object. + +To update one or more properties in an embedded object, +fetch the parent or embedded object and reassign the +embedded object properties in a write transaction. + +In the following example, we have a ``Contact`` object that contains an +embedded ``EmbeddedAddress`` object. We update the embedded object properties +in several operations: .. literalinclude:: /examples/generated/kotlin/DataTypesTest.snippet.update-embedded-object.kt :language: kotlin -.. _kotlin-update-collection: +To overwrite an embedded object completely, assign a new embedded object +instance to the parent property in a write transaction. This deletes the +existing embedded object. + +In the following example, we have a ``Contact`` object that contains an +embedded ``EmbeddedAddress`` object. We define a new ``EmbeddedAddress`` object and +assign it to the parent property: -Update a Collection -------------------- +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.overwrite-embedded-object.kt + :language: kotlin + +Update Multiple Objects +----------------------- -To update a collection of objects in a realm, such as a ``RealmList`` or -``RealmSet``: +You can also update multiple objects in a realm: 1. Query a realm for a collection of objects with `realm.query() @@ -89,47 +169,277 @@ To update a collection of objects in a realm, such as a ``RealmList`` or <{+kotlin-local-prefix+}io.realm.kotlin.query/-realm-results/index.html>`__ returned by the query. -.. literalinclude:: /examples/generated/kotlin/CRUDTest.snippet.update-a-collection.kt +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-a-collection.kt :language: kotlin -Overwrite an Embedded Object +Update Realm Properties +----------------------- + +Depending on how you define your object type, you might have properties +that are special :ref:`Realm-specific types `. + +.. _kotlin-update-mutablerealm: + +Update a MutableRealmInt (Counter) Property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the following Realm API functions to update a ``MutableRealmInt`` +property value: + +- ``increment()`` +- ``decrement()`` +- ``set()`` + +These methods return the ``MutableRealmInt`` property with a mutated value. + +.. tip:: Use set() Operator with Caution + + The ``set()`` operator overwrites any prior calls to + ``increment()`` or ``decrement()``. We do not recommend + mixing ``set()`` with ``increment()`` or ``decrement()``, + unless fuzzy counting is acceptable for your use case. + +In addition to the Realm API functions, you can also use +the following set of operators and infix functions similar +to those provided by `Kotlin's standard library +`__ for ``Long``: + +- Unary prefix operators: ``unaryPlus``, ``unaryMinus`` +- Increment and decrement operators: ``inc``, ``dec`` (not to be confused + with ``increment`` and ``decrement``) +- Arithmetic operators: ``plus``, ``minus``, ``times``, ``div``, ``rem`` +- Equality operators: ``equals`` +- Comparison operators: ``compareTo`` +- Bitwise functions: ``shl``, ``shr``, ``ushr``, ``and``, ``or``, ``xor``, ``inv`` + +However, these operators and infix functions *do not* mutate +the instance on which they are called. Instead, they return +a new unmanaged instance with the result of the operation. + +.. important:: Only Realm Methods Result in a Mutated Value + + The only operations that result in a mutated value are + the Realm API functions: + ``increment``, ``decrement``, and ``set``. + All other operations (including ``inc`` and ``dec``) + return an unmanaged instance with a new value. + +In the following example, we update a ``MutableRealmInt`` +property with ``increment()``, ``decrement()``, and +``set()`` operations: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-mutablerealm-property.kt + :language: kotlin + +.. _kotlin-update-realmany: + +Update a RealmAny (Mixed) Property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`RealmAny <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-any/index.html>`__ +properties are immutable. To update a ``RealmAny`` value, you must create +a new instance of the property with the desired value and type. For more +information on adding ``RealmAny`` properties, refer to :ref:``. + +Additionally, you *must* know the stored type to extract the value from a +``RealmAny`` property. If you call a getter method with the wrong type, +Realm throws an exception. + +.. tip:: Handle Polymorphism with Conditional Expressions + + Because you must know the stored type to extract its value, we + recommend using a ``when`` expression to handle the + ``RealmAny`` type and its possible inner value class. + + .. code-block:: kotlin + + val favoriteThings = frog.favoriteThings + when (favoriteThings.type) { + INT -> rating(favoriteThings.asInteger()) + STRING -> description(favoriteThings.asString()) + BYTE_ARRAY -> image(favoriteThings.asByteArray()) + // Handle other possible types... + else -> { + // Debug or a perform default action + Log.d("ExampleHandler", "Unhandled type: ${favoriteThings.type}") + } + } + +In the following example, we update a ``Frog`` object that +contains a list of ``RealmAny`` properties by creating new +instances of each list element: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-realmany-property.kt + :language: kotlin + +.. note:: Clear a RealmAny Property Value with ``null`` + + You can assign ``null`` directly to a ``RealmAny`` property to + remove the current value. + +.. _kotlin-update-collection: + +Update Collection Properties ---------------------------- -To overwrite an embedded object, assign a new embedded object instance to the -property in a write transaction: +Depending on how you define your object type, you might have properties +that are defined as one of the following +supported :ref:``: -.. literalinclude:: /examples/generated/kotlin/DataTypesTest.snippet.overwrite-embedded-object.kt - :language: kotlin +- ``RealmList`` +- ``RealmSet`` +- ``RealmDictionary`` + +Collections are mutable and are backed by their corresponding built-in Kotlin classes. +You can add and remove elements in a collection within a write transaction. + +.. tip:: Listen for Changes to a Collection + + You can register a notification handler to + listen for changes. For more information, refer to + :ref:``. + +Update a RealmList +~~~~~~~~~~~~~~~~~~ + +You can update elements in a +`RealmList <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-list/index.html>`__ +as you would a +`Kotlin MutableList `__. + +In the following example, we update ``RealmList`` elements +for an existing ``Frog`` object: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-realm-list.kt + :language: kotlin + +For more information on adding and removing ``RealmList`` elements, +refer to :ref:`` and :ref:``. + +Update a RealmSet +~~~~~~~~~~~~~~~~~ + +You can add, update, and remove elements in a +`RealmSet <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-set/index.html>`__ +as you would a +`Kotlin MutableSet `__. + +In the following example, we iterate through and update ``RealmSet`` elements +for existing ``Frog`` objects: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-realm-set.kt + :language: kotlin + +For more information on adding and removing ``RealmSet`` elements, +refer to :ref:`` and :ref:``. + +Update a Dictionary +~~~~~~~~~~~~~~~~~~~ + +You can update keys and values in a +`RealmDictionary <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-dictionary/index.html>`__ +as you would a +`Kotlin MutableMap `__. + +In the following example, we update a ``RealmDictionary`` + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-realm-dictionary.kt + :language: kotlin + +For more information on adding and removing ``RealmDictionary`` entries, +refer to :ref:`` and +:ref:``. + +Update Relationships +-------------------- + +Depending on how you define your object type, you might have properties +that reference another Realm object. This can be a to-one, to-many, or +inverse :ref:`relationship `. + +You can also embed one Realm object directly within another to create +a nested data structure with an ``EmbeddedRealmObject`` type. +For more information, refer to :ref:`` section +on this page. + +Update a To-One Relationship +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can update relationships between objects in a realm by modifying +the properties that define the relationship the same way you would +update any other property. + +In the following example, we have a ``Frog`` object with a ``favoritePond`` +property that references a single ``Pond`` object and a +``bestFriend`` property that references another ``Frog`` object: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-to-one-relationship.kt + :language: kotlin + +Update a To-Many Relationship +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can update to-many relationships the same way you would update any other +collection. Refer to the :ref:`` section on this page. + +In the following example, we have a ``Forest`` object with a +``frogsThatLiveHere`` property that references a set of ``Frog`` +objects and a ``nearByPonds`` property that references a list of +``Pond`` objects: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-to-many-relationship.kt + :language: kotlin + +.. _kotlin-update-inverse-relationships: + +Update an Inverse Relationship +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can access and update objects in an inverse relationship. However, you *cannot* +directly modify the backlink collection itself. Instead, Realm automatically +updates the implicit relationship when you modify any related objects. +For more information, refer to :ref:``. + +In the following example, we have a parent ``User`` object with a +backlinks ``posts`` property that references a list of ``Post`` child objects. +We update properties on the parent and child objects through the +backlink: + +.. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.update-inverse-relationship.kt + :language: kotlin .. _kotlin-upsert-an-object: Upsert a Realm Object --------------------- -The **upsert** operation either inserts a new instance of an object or -updates an existing object that meets certain criteria. +To upsert an object into a realm, insert an object with a primary key +using +`copyToRealm() <{+kotlin-local-prefix+}io.realm.kotlin/-mutable-realm/copy-to-realm.html>`__, +as you would when :ref:`creating a new object `, and pass an +`UpdatePolicy <{+kotlin-local-prefix+}io.realm.kotlin/-update-policy/index.html>`__ +parameter to specify how the SDK handles existing objects with the same +primary key: -To upsert into a realm: +- ``UpdatePolicy.ALL``: Update all properties on any existing objects + identified with the same primary key. +- ``UpdatePolicy.ERROR`` (default): Disallow updating existing objects and instead + throw an exception if an object already exists with the same primary key. If + you do not specify an update policy, Realm uses this policy by default. -1. Open a write transaction with `realm.write() - <{+kotlin-local-prefix+}io.realm.kotlin/-realm/write.html>`__ or - `realm.writeBlocking() - <{+kotlin-local-prefix+}io.realm.kotlin/-realm/write-blocking.html>`__. +The following can occur depending on the update policy: -#. Insert an object with a primary key using `copyToRealm() - <{+kotlin-local-prefix+}io.realm.kotlin/-mutable-realm/copy-to-realm.html>`__. - If an object matching the primary key already exists, the Kotlin SDK - updates the existing object. If no object exists that matches the - primary key, the Kotlin SDK inserts a new object. +- If no object exists that matches the primary key, the SDK inserts the new object. +- If an object with the same primary key already exists, the SDK either: -You can specify the -`UpdatePolicy <{+kotlin-local-prefix+}io.realm.kotlin/-update-policy/index.html>`__ -to use when you upsert an object with an existing primary key: + - Updates all properties on any existing objects identified with the + same primary key. Note that properties are marked as updated in change + listeners, even if the property was updated to the same value. + - Throws an exception indicating that an object already exists in the realm. -- ``UpdatePolicy.ALL``: Update all properties on any existing objects - identified with the same primary key. -- ``UpdatePolicy.ERROR``: Disallow updating existing objects and instead - throw an exception if an object already exists with the same primary key. +In the following example, we attempt to insert a ``Frog`` object with a +primary key that already exists in the realm with ``UpdatePolicy.ALL`` +and confirm the object is successfully upserted: .. literalinclude:: /examples/generated/kotlin/UpdateTest.snippet.upsert-an-object.kt :language: kotlin diff --git a/source/sdk/kotlin/realm-database/schemas/relationships.txt b/source/sdk/kotlin/realm-database/schemas/relationships.txt index 990ffead7e..7ef433957a 100644 --- a/source/sdk/kotlin/realm-database/schemas/relationships.txt +++ b/source/sdk/kotlin/realm-database/schemas/relationships.txt @@ -68,8 +68,10 @@ enforce a strict one-to-one relationship, use an embedded object instead. .. note:: - Setting a to-one relationship field to ``null`` removes the connection between the objects, but Realm does not delete the referenced object. - If you want to delete the referenced object when the parent object is deleted, use an embedded object instead. + Setting a to-one relationship field to ``null`` removes the connection + between the objects, but Realm does not delete the referenced object. + If you want to delete the referenced object when the parent object is + deleted, use an embedded object instead. To define a to-one relationship between objects, define an optional object property whose type is a ``RealmObject`` object diff --git a/source/sdk/kotlin/realm-database/schemas/supported-types.txt b/source/sdk/kotlin/realm-database/schemas/supported-types.txt index 07bc9bae9d..6238a70ca1 100644 --- a/source/sdk/kotlin/realm-database/schemas/supported-types.txt +++ b/source/sdk/kotlin/realm-database/schemas/supported-types.txt @@ -297,7 +297,8 @@ Additionally, ``MutableRealmInt`` fields: they create a new, unmanaged ``MutableRealmInt`` instance with the updated value. -Learn how to :ref:``. +Learn how to :ref:`` and +:ref:``. .. _kotlin-timestamps: @@ -327,7 +328,9 @@ RealmAny (Mixed) represents a non-nullable mixed data type. It behaves like the value type that it contains. ``RealmAny`` can hold: -- :ref:`supported Kotlin data types ` +- :ref:`supported Kotlin data types ` (note that + ``Byte``, ``Char``, ``Int``, ``Long``, and ``Short`` values are converted + internally to ``int64_t`` values) - :ref:`supported BSON types ` - the following Realm-specific types: @@ -343,7 +346,21 @@ or another ``RealmAny``. - are :ref:`indexable ` but *cannot* be used as a :ref:`primary key ` -- must be declared nullable (``RealmAny?``), but *cannot* store null values +- must be declared nullable (``RealmAny?``), but they *cannot* store null values +- can be aggregated with + `RealmQuery.max <{+kotlin-local-prefix+}io.realm.kotlin.query/-realm-query/max.html>`__, + `RealmQuery.min <{+kotlin-local-prefix+}io.realm.kotlin.query/-realm-query/min.html>`__, + and + `RealmQuery.sum <{+kotlin-local-prefix+}io.realm.kotlin.query/-realm-query/sum.html>`__. +- can be sorted. Sort order from highest to lowest: + + #. ``Boolean`` + #. ``Byte``, ``Double``, ``Decimal128``, ``Int``, ``Float``, ``Long``, ``Short`` + #. ``byte[]``, ``String`` + #. ``Date`` + #. ``ObjectId`` + #. ``UUID`` + #. ``RealmObject`` You can store multiple ``RealmAny`` instances in ``RealmList``, ``RealmDictionary``, or ``RealmSet`` fields.