kotlinrealmkotlin-multiplatformrealm-mobile-platform

How to get reference objects in many-to-many relationship in Realm (Kotlin SDK)?


I'm trying to implement a many-to-many relationship between Record and Tag objects. Each Record can have multiple tags.

open class Record : RealmObject {
    @PrimaryKey
    var id: String = ""
    var title: String = ""
    var description: String = ""

    var tags: RealmList<Tag> = realmListOf()
}

open class Tag : RealmObject {
    @PrimaryKey
    var id: String = ""
    var name: String = ""

    val record: RealmResults<Record> by backlinks(Record::tags)
}

When testing the saving of Record objects, I noticed the following issue. In the case where we save a Record with a Tag that doesn't exist in the Tags table, that tag is automatically added. However, if we try to save a Record with a tag that already exists in the Tags table, we get the following exception - "Attempting to create an object of type 'Tag' with an existing primary key value '1'".

I write a Record object with a list of Tags as follows:

val record = Record().apply {
    id = randomUUID()
    title = "TestRecord"
    description = "Desc"
    tags = realmListOf(
        Tag().apply {
            id = "1"
            name = "Tag1"
        },
        Tag().apply {
            id = "2"
            name = "Tag2"
        }
    )
}
realm.write {
    copyToRealm(record)
}

Please help me figure out what I'm doing wrong?


Solution

  • I believe the issue here is the understanding of how Realm objects interact; unmanaged vs managed

    Let me address this at a high level with some pseudo code

    Suppose theres a Record and Tag objects and nothing yet exists in your Realm database. Record has a RealmList of Tags

    Create a Tag and add it to a Record.

    var t0 = Tag(withPrimaryKey: 0)
    var myRecord = Record()
    myRecord.myTags.add(t0)
    realm.add(myRecord) //success since it doesn't exist
    

    This will succeed and both a tag and record will be added to Realm! But then later we do this

    let myRecord = read a record from Realm
    var t0 = Tag(withPrimaryKey: 0)
    myRecord.myTags.add(t0) //fail as this primary key exists
    

    The above will fail because the Tag with primary key 0 already exists, and primary keys must be unique

    However, assume the Realm database has only one entry, a Tag with primary key of 0

    let t0 = read Tag with primary key 0 from realm
    let myRecord = Record()
    myRecord.myTags.add(t0)
    realm.add(myRecord) //success since t0 was already managed
    

    will succeed and the Tag with primary key 0 will NOT be duplicated. Since it was already a managed object (e.g. stored in realm), it's simply added to the myRecord, myTags property as a reference to the managed object.

    If you know a specific objects primary key, you can also Upsert it; that process will either create the object if it doesn't exist, or modify the object if it does.