Hoinzey

Javascript. Kotlin. Android. Java

Android: Room one to many objects

I first discovered the tools in this post reading this blog by Florina Muntenscu who puts out a lot of amazing content. Find her on twitter.


There are plenty of scenarios where you build a database schema that has a one-to-many relationship. In this example I am using Game Developer's and their games. A developer can have many games. Modelled below.

Game developer
Game
                    
    @Entity(tableName = "gamedevelopers")
    data class GameDeveloper(
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "developerid")
        var developerId: Long = 0,
        var developerName: String
    )
                                        
                    
    @Entity(
        tableName = "playstationgames",
        foreignKeys = [ForeignKey(
            entity = GameDeveloper::class,
            parentColumns = arrayOf("developerid"),
            childColumns = arrayOf("developerid")
        )]
    )
    data class PlaystationGame(
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "gameid")
        var gameId: Long = 0,
        var gameName: String,
        @ColumnInfo(name = "developerid")
        val developerId: Long
    )
                                        

It is common to take data like this and represent it in a list. In this example it means that we need to do two queries in order to get the developers and their games. So you'll end up calling queries like below to first get the developers and then the list of games for them.

DAO queries
Example usage
                    
    @Query("select * from gamedevelopers")
    fun getAllDevelopers(): List<GameDeveloper>

    @Query("select * from playstationgames where developerid = :id")
    fun getAllGamesForDeveloper(id: Long): List<PlaystationGame>
                        
                    
    fun logDevelopersAndGamesV1() {
        val developers = gameDeveloperDAO.getAllDevelopers()
        for (dev in developers) {
            val games = playstationGameDAO.getAllGames()
            Log.d(TAG, "$dev - $games")
        }
    }
                                        

We can create a new class called DeveloperAndGames which will contain our GameDeveloper and its list of games. We do this by adding the @Relation annotation along with some details of the columns to be used. This can only be applied to a List or Set of objects.

Updated query
DeveloperAndGames
                    
    @Query("select * from gamedevelopers")
    fun getAllDevelopersAndGamesAsFlow(): Flow<List<DeveloperAndGames>>
                                        
                    
    class DeveloperAndGames {
        @Embedded
        var gameDeveloper: GameDeveloper? = null
    
        @Relation(parentColumn = "developerid", entityColumn = "developerid")
        var playstationGames: List<PlaystationGame> = ArrayList()
    }
                                        

Whats also useful is that observing this object as LiveData will have updates triggered when either the GameDeveloper or Game tables are updated using this example. You can find more of this example here.

                    
    var allDevsWithGame: LiveData<List<DeveloperAndGames>> 
        = gameDeveloperDAO.getAllDevelopersAndGamesAsFlow().asLiveData()
    //...
    allDevsWithGame?.observe(this) {
        logThatDatasetHasChanged()
    }
    //...
    private fun logThatDatasetHasChanged() {
        Log.d(TAG, "Dataset has changed")
        Log.d(TAG, "${allDevsWithGame.value}")
    }