Room is a wrapper over SQLite.
It makes working with databases on Android so much easier
and is by far my favorite Jetpack library.
In this video, I want to tell you how to use and test Room Kotlin APIs.
And while we do that, I'll also share how things work under the hood.
We're going to use the Room with a view codelab
as a basis.
So here we're building a list of words
that are saved in the database and displayed on the screen,
and the user can also add words to the list.
In our database, we have only one table, the one containing words.
The Word class represents an entry in the table,
and it needs to be annotated with @Entity.
We use @PrimaryKey to define the primary key for the table.
Based on this, Room will generate an SQLite table
with the same name as the class name.
Each member of the class becomes a different column
in the table.
The column name and type is given by the name and type of each field,
and you can change the name by using the @ColumnInfo annotation.
We recommend that you always use the @ColumnInfo annotation,
as it gives you more flexibility to rename the members
without having to change the database column names.
Changing the column names leads to a change in the database schema,
and therefore you need to implement the migration.
To access the data in the table, we create a Data Access Object,
DAO for short.
This will be an interface called WordDao annotated with @Dao.
We want to insert, delete, and get data from the table.
So, these will be defined as abstract methods in the DAO.
Working with the database is a time-consuming I/O operation,
so this needs to be done on the background thread.
And we'll use the Room integrations
with Kotlin coroutines and flow to achieve this.
We covered the basics of coroutines in this Kotlin vocabulary video.
Check out this video for information about flow.
To insert data, create an abstract suspend function
that gets as parameter the word to be inserted and annotated
with @Insert.
Room will generate all the work that needs to be done
to insert the data in the database.
And because we made the function suspend,
Room moves all the work to be done to a background thread.
Thus, the suspend function is main safe.
So it's safe to be called from the main thread.
Under the hood, Room generates the implementation of a DAO.
Here's how the implementation of our insert method looks like.
CoroutinesRoom.execute function is called with three parameters,
the database, a flag to indicate whether we're in a transaction,
and a callable object.
Callable call contains the code that handles the database insertion.
If we check the CoroutinesRoom execute implementation,
we see that Room moves callable.call to a different coroutine context.
This is derived from the executors you provide when building your database,
or by default,
it will use the Architecture Components I/O executor.
To query the table, we'll create the abstract function
and annotate it with @Query,
passing in the SQLite query needed to get the data we want.
In our case, all the words from the word table
ordered alphabetically.
We want to be notified whenever the data in the database changes.
To do that, we return a Flow.
Because of the return type, Room also runs the query on the background thread.
Under the hood, Room generates the getAlphabetizedWords for us.
We see a CoroutinesRoom.createFlow call with four parameters:
the database, a flag indicating whether we're in a transaction,
a list of tables that should be observed for changes,
or in our case, just the word table, and the callback.
Callback call contains the implementation of the query to be triggered.
If we change the CoroutinesRoom.createFlow implementation,
we see that here as well the query call is moved to a different coroutine context.
Like with the insert call, this dispatcher is derived from the executors you provide
when building your database.
Or by default, it will use the Architectural Component's IO executor.
We've defined the data to be stored in the database
and how to access it.
Now it's time to define the database itself.
For this, we create an abstract class that extends RoomDatabase.
Annotate it with @Database, passing in Word
as the entity to be stored and 1 as database version.
We'll define an abstract method that returns the WordDao.
Everything is abstract because Room generates
the implementation for us.
There is a lot of logic that we no longer need to implement,
like creating the database, the tables, handling migrations, and more.
The only step left is to build the database.
We want to make sure that we don't have multiple database instances
open at the same time,
so this means that we need to create a singleton.
One way to handle this is to define a Room database instance
in the companion object of our class
and a getDatabase function that builds a database.
If we want Room queries to be executed
on a different executor than the I/O executor created by Room,
we can pass it in the builder by calling setQueryExecutor,
and that's all.
So we've done quite a lot of work so far.
Let's see how we test the work that we've just done.
To test the DAO, we need to implement an AndroidJUnit test
as Room creates an SQLite database.
When implementing the DAO test, before each test is run,
we create the database.
After each test is run, we close the database.
As we don't need to save the data on the device
when creating the database, we can use an in-memory one.
And as this is a test, we can allow queries to be run on the main thread,
but don't do this in the production code.
To test if a word is inserted successfully,
we'll create the word.
Insert it.
Get the first word from the list of alphabetized words,
and ensure it's the same as the word we created.
As we're calling suspend functions, we'll run the test in a runBlocking block.
Since this is a test, we don't mind if the test blocks the test code.
And that's it. We've tested our DAO.
Room offers a lot more functionality and flexibility
than what we've just covered in this video.
It can define how Room should handle database conflicts.
You can store types that otherwise natively with SQLite
can't be stored, like Date, by creating type converters.
It can implement complex queries using JOIN and other SQLite functionality,
create database views,
prepopulate your database, or trigger certain database actions
whenever the database is created or opened.
Check out our Room documentation for more information
and the Room with a View codelab for hands-on learning.
Thanks for watching.