Room and Reactive Frameworks

Database I/O can be slow, particularly for larger databases and unoptimized operations. As a result, we invariably want to do that database I/O on background threads.

There are many options for doing that, including simply spinning up your own Thread or Executor that you use to make your DAO calls. However, the more popular way of addressing this nowadays is to use “reactive frameworks”, which wrap up threading and results delivery for you. In this chapter, we will examine Room’s support for a few reactive options: LiveData, Kotlin coroutines, and RxJava.

But, first, a word from our UI.

Room and the Main Application Thread

You might not be worried so much about the speed of your database I/O. Maybe you think that your database will never get large. Maybe you think that your users will all be using expensive devices (and think that expensive devices means that they will have fast database I/O). Maybe threads make your head hurt.

Hence, you might think that you can just go ahead and use Room on the main application thread, despite the fact that it will freeze your UI while that database I/O is going on. After all, in the code that we have seen so far in the book, we have not used Thread, Executor, or any fancy reactive framework — we have just made the database calls.

But that is because test functions are called on a background thread automatically. We do not have to fork a background thread of our own.

To see how Room behaves on the main application thread, we have to write our test to use the main application thread, such as via runOnMainSync():

package com.commonsware.room.misc

import androidx.room.Room
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.isEmpty

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

@RunWith(AndroidJUnit4::class)
class MainAppThreadGoBoomTest {
  private val db = Room.inMemoryDatabaseBuilder(
    InstrumentationRegistry.getInstrumentation().targetContext,
    MiscDatabase::class.java
  )
    .build()
  private val underTest = db.autoGenerate()

  @Test
  fun goBoom() {
    InstrumentationRegistry.getInstrumentation().runOnMainSync {
      assertThat(underTest.loadAll(), isEmpty)

      val original = AutoGenerateEntity(id = 0, text = "This will get its own ID")
      val inserted = underTest.insert(original)

      assertTrue(original === inserted)
      assertThat(inserted.id, !equalTo(0L))
    }
  }
}

This is a clone of the test we had for using @PrimaryKey(autoGenerate = true) on an entity, except that the test code is wrapped in a call to runOnMainSync(), to force it to run on the main application thread. While the original test succeeds, this one crashes with:

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

The developers who created Room block Room usage on the main application thread, as that is an anti-pattern.

So, we need to do something to avoid this sort of crash.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.