Search code examples
androidpersistenceandroid-roomandroid-livedataandroid-jetpack

Returning LiveData of an Interface from Room?


TL;DR version

I'd like to have a method whose returning signature is LiveData<User>, in which User is an interface. And the implementation returns a LiveData<UserEntity> in which UserEntity is a concrete class that extends User. Like following:

fun getUser(id: String): LiveData<User> {
    // for brevity
    return LiveData<UserEntity>() // actual code will retrieve data from db
}

The compiler complains return type not match error

TL; version

I'm reading the Jetpack guide. In it, they designed a DataRepository class which can retrive data from either DB or WebService, as shown in this graph: app structure from Jetpack guide And they claim that

Notice that each component depends only on the component one level below it.

It's an appealing design, I actually tried to replicate it in my own project. However, I found WebService inevitably depends on Room, because it's method getUser() returns a LiveData of User, which is a Room Entity.

This issue might not break the app, but it's contradicting the fundamental design of the"separation of concerns" principle, and is aesthetically ugly IMHO.

My question is: are there any workaround? I've tried to refactor User as an interface and implemented an UserEntity class that extends it. Shown below:

interface User {
    val id: String
    val name: String
    val lastName: String
}
@Entity(tableName="users")
data class UserEntity(
    override val id: String
    override val name: String
    override val lastName: String) : User
@Dao
interface UserDao() {
    @Query("SELECT * FROM users where id = :id")
    fun getUser(id: String): LiveData<UserEntity> // UserEntity instead of User, because Room doesn't know how to construct an interface
}

Note I have to return UserEntity intead of User, because Room doesn't know how to construct an interface.

Here is the problem: the following code won't compile:

interface DataRepository {
    fun getUser(id: String): LiveData<User>
}
class DbRepository : DataRepository {
    // ... init db connection/userDao ...
    override fun getUser(id: String): LiveData<User> = userDao.getUser(id)
}

The error indicates data type mismatch: LiveData<User> vs. LiveData<UserEntity>


Solution

  • The above code won't work, as you can't expect a function to return multiple data types. I recommend using one User class which merges both Room and GSON mapping.

    @Entity(tableName="users")
    data class Users(
      @PrimaryKey           // Room annotation
      @SerializedName("id") // Gson annotation
      val id: String,
      @ColumnInfo(name = "name") 
      @SerializedName("name")   
      val name: String,
      @ColumnInfo(name = "lastName") 
      @SerializedName("lastName")    
      val lastName: String,
    )
    

    You can get rid of your UserEntity class and use the above as the DTO for both API and DB repository.