7 Pro-tips for Room

Florina Muntenescu
Nov 2, 2017 · 7 min read

1. Pre-populate the database

Do you need to add default data to your database, right after it was created or when the database is opened? Use RoomDatabase#Callback! Call the addCallback method when building your RoomDatabase and override either onCreate or onOpen.

DataDatabase::class.java, "Sample.db")
// prepopulate the database after onCreate was called
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// moving to a new thread
ioThread {

2. Use DAO’s inheritance capability

Do you have multiple tables in your database and find yourself copying the same Insert, Update and Delete methods? DAOs support inheritance, so create a BaseDao<T> class, and define your generic @Insert, @Update and @Delete methods there. Have each DAO extend the BaseDao and add methods specific to each of them.

interface BaseDao<T> {
fun insert(vararg obj: T)
abstract class DataDao : BaseDao<Data>() {
@Query("SELECT * FROM Data")
abstract fun getData(): List<Data>

3. Execute queries in transactions with minimal boilerplate code

Annotating a method with @Transaction makes sure that all database operations you’re executing in that method will be run inside one transaction. The transaction will fail when an exception is thrown in the method body.

abstract class UserDao {

open fun updateData(users: List<User>) {
abstract fun insertAll(users: List<User>)
@Query("DELETE FROM Users")
abstract fun deleteAllUsers()
  • When the result of the query is a POJO with @Relation fields. The fields are queries separately so running them in a single transaction will guarantee consistent results between queries.

4. Read only what you need

When you’re querying the database, do you use all the fields you return in your query? Take care of the amount of memory used by your app and load only the subset of fields you will end up using. This will also improve the speed of your queries by reducing the IO cost. Room will do the mapping between the columns and the object for you.

@Entity(tableName = "users")
data class User(@PrimaryKey
val id: String,
val userName: String,
val firstName: String,
val lastName: String,
val email: String,
val dateOfBirth: Date,
val registrationDate: Date)
data class UserMinimal(val userId: String,
val firstName: String,
val lastName: String)
interface UserDao {
@Query(“SELECT userId, firstName, lastName FROM Users)
fun getUsersMinimal(): List<UserMinimal>

5. Enforce constraints between entities with foreign keys

Even though Room doesn’t directly support relations, it allows you to define Foreign Key constraints between entities.

@Entity(tableName = "pets",
foreignKeys = arrayOf(
ForeignKey(entity = User::class,
parentColumns = arrayOf("userId"),
childColumns = arrayOf("owner"))))
data class Pet(@PrimaryKey val petId: String,
val name: String,
val owner: String)

6. Simplify one-to-many queries via @Relation

In the previousUser-Pet example, we can say that we have a one-to-many relation: a user can have multiple pets. Let’s say that we want to get a list of users with their pets: List<UserAndAllPets>.

data class UserAndAllPets (val user: User,
val pets: List<Pet> = ArrayList())
@Query(“SELECT * FROM Users”)
public List<User> getUsers();
@Query(“SELECT * FROM Pets where owner = :userId”)
public List<Pet> getPetsForUser(String userId);
class UserAndAllPets {   @Embedded
var user: User? = null
@Relation(parentColumn = “userId”,
entityColumn = “owner”)
var pets: List<Pet> = ArrayList()
@Query(“SELECT * FROM Users”)
List<UserAndAllPets> getUsers();

7. Avoid false positive notifications for observable queries

Let’s say that you want to get a user based on the user id in an observable query:

@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>
// or@Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): Flowable<User>
  1. Room creates an InvalidationTracker that uses Observers that track whenever something has changed in the observed tables.
  2. Both LiveData and Flowable queries rely on the InvalidationTracker.Observer#onInvalidated notification. When this is received, it triggers a re-query.
@Daoabstract class UserDao : BaseDao<User>() {/**
* Get a user by id.
* @return the user from the table with a specific id.
@Query(“SELECT * FROM Users WHERE userid = :id”)
protected abstract fun getUserById(id: String): Flowable<User>
fun getDistinctUserById(id: String):
Flowable<User> = getUserById(id)
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
return distinctLiveData
abstract class UserDao : BaseDao<User>() {
@Query(“SELECT * FROM Users WHERE userid = :id”)
protected abstract fun getUserById(id: String): LiveData<User>
fun getDistinctUserById(id: String):
LiveData<User> = getUserById(id).getDistinct()

Android Developers

The official Android Developers publication on Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store