Illustration by Virginia Poltrack

Room 🔗 Coroutines

Add some suspense to your database

Room 2.1 (currently in alpha) adds support for Kotlin coroutines. DAO methods can now be marked as suspending to ensure that they are not executed on the main thread. By default, Room will use the Architecture Components I/O Executor as the Dispatcher to run SQL statements, but you can also supply your own Executor when building the RoomDatabase. Read on to see how to use this, how it works under the hood and how to test this new functionality.

Coroutines support for Room is currently under heavy development, with more features planned to be supported in the future versions of the library.

Add some suspense to your database

To use coroutines and Room in your app, update to Room 2.1 and add the new dependency to your build.gradle file:

implementation "androidx.room:room-coroutines:${versions.room}"

You’ll also need Kotlin 1.3.0 and Coroutines 1.0.0 or newer.

You can now update your DAO methods to use suspension functions:

DAO with suspend methods

@Transaction methods can also be suspending and they can call other suspending DAO functions:

DAO with suspend transaction function

Room treats suspending functions differently, based on whether they are called within a transaction or not:

1. In a transaction

Room doesn’t do any handling of the CoroutineContext on which the database statement is triggered. It’s the responsibility of the caller of the function to make sure that this is not on a UI thread. Since suspend functions can only be called from other suspend functions or from coroutines, make sure that the Dispatcher you’re using is not Dispatcher.Main, rather Dispatchers.IO or your own custom one.

2. Not in a transaction

Room makes sure that the database statement is triggered on the Architecture Components I/O Dispatcher. This Dispatcher is created based on the same I/O Executor used to run LiveData work on a background thread.

Testing DAO suspension functions

Testing a DAO suspending function is no different from testing any other suspending function. For example, to check that after inserting a user we are able to retrieve it, we wrap the test in a runBlocking block:

Testing DAO suspend functions

Under the hood

To see what’s under the hood, let’s take a look at the DAO class implementation Room generates for a synchronous and for a suspending insert:

Synchronous and suspending insert functions

For the synchronous insert, the generated code starts a transaction, executes the insert, marks the transaction as successful and ends it. The synchronous method will just execute the insert on whatever thread it’s called from.

Room synchronous insert generated implementation

Now let’s see how adding the suspend modifier changes things: the generated code will make sure that your data gets inserted but also that this happens off of the UI thread.

The generated code passes a continuation and the data to be inserted. The same logic from the synchronous insert method is used but within a Callable#call method.

Room suspending insert generated implementation

The interesting part though is the CoroutinesRoom.execute function, since this is the one that handles the context switch, depending on whether the database is opened and we are in a transaction or not.

Case 1. The database is opened and we are in a transaction

Here we’re just triggering the call method — i.e. the actual insertion of the user in the database

Case 2. We’re not in a transaction

Room makes sure that the work done in the Callable#call method is performed on a background thread by using the Architecture Components IO Executor.

CoroutinesRoom.execute implementation

Start using Room and coroutines in your app, the database work is guaranteed to be run on a non-UI Dispatcher. Mark your DAO method with the suspend modifier and call them from other suspend functions or coroutines!