Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RealmList.remove(RealmObject) does not work #1712

Closed
RileyGB opened this issue Mar 27, 2024 · 6 comments
Closed

RealmList.remove(RealmObject) does not work #1712

RileyGB opened this issue Mar 27, 2024 · 6 comments

Comments

@RileyGB
Copy link

RileyGB commented Mar 27, 2024

How frequently does the bug occur?

Always

Description

If I have two simple RealmObjects, where one is the parent and the other is a list of child objects within the parent, when I call parent.children.remove(child), the child is not removed from the RealmList.

It appears that Collections.removeAt is overridden in ManagedRealmList, whereas Collections.remove is not.

Stacktrace & log output

No response

Can you reproduce the bug?

Yes

Reproduction Steps

Here's models and tests that demonstrate the issue:

Models

class ParentModel : RealmObject {
    @PrimaryKey
    var id: String = ""
    var children: RealmList<ChildModel> = realmListOf()
    var strings: RealmList<String> = realmListOf()
}

class ChildModel : RealmObject {
    @PrimaryKey
    var id: String = ""
}

Tests

Note: RealmInstrumentedTest is internal to my project, it basically just opens Realm with our RealmConfiguration and initializes myTestRealm.

Of note here is that the assertion in Test RealmList object remove fails.

class RealmListTest : RealmInstrumentedTest() {
    @Test
    fun `Test RealmList model removeAt`() {
        myTestRealm?.let { realm ->
            realm.writeBlocking {
                val child = ChildModel().apply {
                    id = "a"
                }
                val parent = ParentModel().apply {
                    id = "1"
                    children = realmListOf(child)
                }

                copyToRealm(parent)
            }

            realm.writeBlocking {
                val parent = realm.query<ParentModel>("id = $0", "1").first().find()!!
                val child = parent.children.first()
                val index = parent.children.indexOfFirstOrNull { it.id == child.id } ?: -1

                val removedChild = findLatest(parent)!!.children.removeAt(index)

                assertNotNull(child) // returns true
                assertNotNull(removedChild) // returns true <--- removeAt works as expected
            }
        }
    }

    @Test
    fun `Test RealmList object remove`() {
        myTestRealm?.let { realm ->
            realm.writeBlocking {
                val child = ChildModel().apply {
                    id = "a"
                }
                val parent = ParentModel().apply {
                    id = "1"
                    children = realmListOf(child)
                }

                copyToRealm(parent)
            }

            realm.writeBlocking {
                val parent = realm.query<ParentModel>("id = $0", "1").first().find()!!
                val child = parent.children.first()

                val removedChild = findLatest(parent)!!.children.remove(child)

                assertThat(removedChild, `is`(true)) // returns false <--- remove(RealmObject) does not work as expected, this is the problem
            }
        }
    }

    @Test
    fun `Test RealmList string remove`() {
        myTestRealm?.let { realm ->
            realm.writeBlocking {
                val parent = ParentModel().apply {
                    id = "1"
                    strings = realmListOf("a")
                }

                copyToRealm(parent)
            }

            realm.writeBlocking {
                val parent = realm.query<ParentModel>("id = $0", "1").first().find()!!
                val string = parent.strings.first()

                val removedString = findLatest(parent)!!.strings.remove(string)

                assertThat(removedString, `is`(true)) // true <--- remove(String) works as expected
            }
        }
    }
}

Version

1.8.0

What Atlas App Services are you using?

Local Database only

Are you using encryption?

Yes

Platform OS and version(s)

All

Build environment

Android Studio version: Iguana 2023.2.1
Android Build Tools version: 7.4.2
Gradle version: 7.5

Copy link

sync-by-unito bot commented Mar 27, 2024

➤ PM Bot commented:

Jira ticket: RKOTLIN-1063

@kneth
Copy link
Contributor

kneth commented Apr 4, 2024

There is a difference between removing an object from a list and delete the object: https://www.mongodb.com/docs/atlas/device-sdks/sdk/kotlin/realm-database/crud/delete/#remove-elements-from-a-realmlist

@sync-by-unito sync-by-unito bot added Waiting-For-Reporter Waiting for more information from the reporter before we can proceed More-information-needed More information needed from the reporter. The issue will autoclose if no more information is given. and removed Waiting-For-Reporter Waiting for more information from the reporter before we can proceed labels Apr 4, 2024
@RileyGB
Copy link
Author

RileyGB commented Apr 4, 2024

There is a difference between removing an object from a list and delete the object: https://www.mongodb.com/docs/atlas/device-sdks/sdk/kotlin/realm-database/crud/delete/#remove-elements-from-a-realmlist

Hi Kneth, thanks for the reply!

Are you referencing this?

Realm collection instances that contain objects only store references to those objects. You can remove one or more referenced objects from a collection without deleting the objects themselves. The objects that you remove from a collection remain in the realm 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.

If it is intentional that .removeAt deletes the object, but .remove does not, I would find that to be pretty unintuitive. Every other list in Kotlin removes an item from a list when .remove is called (so long as a the item exists within the list).

The docs are pretty unclear or wrong here if you look at the examples provided below:

To remove one element from the list, pass the element to
list.remove().

    // Remove the first pond in the list
    val removeFirstPond = forestPonds.first()
    forestPonds.remove(removeFirstPond)
    assertEquals(4, forestPonds.size)

According to my example above, this assertion would fail. Presumably because RealmInterop.realm_list_erase is not called via RealmList.remove as it is not overridden in ManagedRealmList.

@github-actions github-actions bot removed the More-information-needed More information needed from the reporter. The issue will autoclose if no more information is given. label Apr 4, 2024
@c-villain
Copy link

Any changes for this issue?

@rorbech
Copy link
Contributor

rorbech commented Apr 16, 2024

Hi @c-villain. I think the issue here is that you are actually looking up the child in an outdated context.

With

realm.writeBlocking {
    val parent = realm.query<ParentModel>("id = $0", "1").first().find()!!
    val child = parent.children.first()

    val removedChild = findLatest(parent)!!.children.remove(child)

    assertThat(removedChild, `is`(true)) // returns false <--- remove(RealmObject) does not work as expected, this is the problem
}

The instances parent and child are queried from your realm instead of the MutableRealm-receiver of the block argument or writeBlocking. This will try to remove an outdated (or not most recent) version of the object from the list. This will not match and hence will not be removed.

To achieve your intent you will have to either ensure that you are removing an up-to-date reference with:

val removedChild = findLatest(parent)!!.children.remove(findLatests(child)!!) // Added findLatest around child

Alternatively you could just operate solely on objects from the MutableRealm with

realm.writeBlocking { // this: MutableRealm ->
    // using this (MutableRealm) instead of global frozen `realm`
    val parent = this.query<ParentModel>("id = $0", "1").first().find()!! 
    val child = parent.children.first()

    val removedChild = parent.children.remove(child)

    assertThat(removedChild, `is`(true))
}

@rorbech
Copy link
Contributor

rorbech commented Apr 16, 2024

I have created #1723 to improve the APIs and/or throw in these case, so will close this issue for now. If the advised details in #1712 (comment) is not fixing your issue, please leave a note and we can reinvestigate.

@rorbech rorbech closed this as completed Apr 16, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants