Home » Android » android – How to query a complex nested objects in Room?

android – How to query a complex nested objects in Room?

Posted by: admin June 15, 2020 Leave a comment

Questions:

I have several entities that you can see below, the problem is how to select nested lists properly when you have more than one level of nesting. If you take a look at Google repository on github, you can find very simple example, but not something complex. As documentation says here, Room doesn’t allow object references. How to get ResultObject (and how it should look) including all nested lists?

ResultObject should have this info:

  1. List<Dialog>
  2. Dialog entity has List<Message>.
  3. Message has List<ImageContent>.
  4. ImageContent has List<ImageContentItem>

Dialog

@Entity(tableName = "dialog")
data class Dialog(@PrimaryKey val id String, val title: String)

Message

@Entity(tableName = "message",
        foreignKeys = [(ForeignKey(entity = Dialog::class, parentColumns ["id"], childColumns = ["dialogId"]))])
data class Message(@PrimaryKey val id: String, val dialogId: String)

ImageContent

@Entity(tableName = "image_content",
        foreignKeys = [(ForeignKey(entity = Message::class, parentColumns = ["id"], childColumns = ["messageId"]))])
data class ImageContent(@PrimaryKey val id: String, val messageId: String)

ImageContentItem

@Entity(tableName = "image_content_item",
        foreignKeys = [(ForeignKey(entity = ImageContent::class, parentColumns = ["id"], childColumns = ["imageContentId"]))])
data class ImageContentItem(val imageContentId: String,
                        @PrimaryKey(autoGenerate = true) val id: Long = 1)

DAO:

@Dao
interface DialogDao {
    @Query("SELECT * FROM dialog " +
            "INNER JOIN message ON message.dialogId = dialog.id " +
            "INNER JOIN image_content ON image_content.messageId = message.id " +
            "INNER JOIN image_content_item ON image_content_item.imageContentId = image_content.id")
    fun getDialogAllInformation(): Flowable<List<**ResultObject**>>
}
How to&Answers:

Rooms works with entity/table and it does not allow to manage structured object like you needed. The only thing you can do is execute queries separately and then build your ResultObject.

Answer:

You can nest classes defining relationships as much as you want.

class DeepDialog {
    @Embedded lateinit var embedded: Dialog
    @Relation(parentColumn = "id", entityColumn = "dialogId", entity = Message::class)
    lateinit var messages: List<DeepMessage>
}

class DeepMessage {
    @Embedded lateinit var embedded: Message    
    @Relation(parentColumn = "id", entityColumn = "messageId", entity = ImageContent::class)
    lateinit var imageContents: List<DeepImageContent>
}

... etc

I think Room guys should come with something more elegant as this solution has at least two issues.

  1. It introduces quite annoying boilerplate.
  2. @Relation annotation can be used only on Lists or Sets so it doesn’t cover scenarios where you have 1:1 relationship.

Imagine your Dialog would have only one Message by definition (instead of many). In that case, you wouldn’t want your DeepDialog to have messages: List<> accessor but message: Message instead. The only way around I found is:

class DeepDialog {
    @Embedded lateinit var embedded: Dialog
    @Relation(parentColumn = "id", entityColumn = "dialogId", entity = Message::class)
    internal lateinit var messages: List<DeepMessage>
    val message get() = messages.firstOrNull()
}