Data Persistence With Room

Learn how to persist data in your Android app using the Room SQLite wrapper from Google. By Andrej Vukelic.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Entities

An Entity is an object that holds data for you to use in your application, with some extra information to tell Room about its structure in the database. In the example we used before, a registry of a person is an Entity.

To start, you’ll create an Entity to store a category_name and id in a table called list_categories.

Under the com.kodeco.listmaster.domain package in your app, you’ll see a Kotlin class named Category. Open it by double-clicking and replace the data class under TODO 2 with the following code:

@Entity(tableName = "list_categories")
data class Category(
    @ColumnInfo(name = "id") 
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    @ColumnInfo(name = "category_name") 
    val categoryName: String,
)

The @Entity(tableName = "list_categories") annotation tells Room that this is an Entity object that’s mapped to a table called list_categories. The @ColumnInfo annotation tells Room about the columns in your table. For example, the name argument in the @ColumnInfo(name = "category_name") annotation tells Room that the data class property categoryName is a column with the name category_name in the table.

DAOs

Now that you have your Entity that contains your data, you’ll need a way to interact with it. This is done with a Data Access Object, also referred to as a DAO. To start, you’ll create a DAO to insert and retrieve records from the table that you’ve created with your Entity.

Right-click the data package in com.kodeco.listmaster in your project, open CategoryDao file and replace interface under TODO 3 with the next snippet.

// 1
@Dao
interface CategoryDao {
    // 2
    @Query("SELECT * FROM list_categories")
    fun getAll(): Flow<List>

    @Query("SELECT * FROM list_categories WHERE id=:categoryId")
    suspend fun getCategory(categoryId: Long): Category
    
    // 3
    @Insert
    suspend fun insert(category: Category): Long

    // 4
    @Delete
    suspend fun delete(category: Category)
}

1. The first thing you’ll notice is that for a DAO, you’re creating an interface instead of a class. That’s because Room is creating the implementation for you. The @Dao annotation tells Room that this is a Dao interface.

2. The @Query annotation on getAll() tells room that this function definition represents a query and takes a parameter in the form of a SQL statement. In this case, you have a statement that retrieves all the records in the list_categories table in form of Flow. More on that later in the Let the Data Flow section below.

Your getAll() method has a return type of List wrapped in Flow. Under the hood, Room looks at the results of the query and maps any fields that it can to the return type you have specified. In your case, you’re querying for multiple Category entities and have your return value specified as a List of Category items.

If you have a query that returns data that Room is not able to map, Room will print a CURSOR_MISMATCH warning and will continue to set any fields that it’s able to. Also note that when a method like getAll() returns a flow, suspend modifier is not added to it. More on this here.

3. Your insert method is annotated with @Insert. This tells Room that you’re inserting data into the database. Because Room knows about the table and its columns, it takes care of generating the SQL for you.

4. Same goes for the delete method annotated with @Delete.

Database

Now that you have an Entity and a DAO, you’ll need an object to tie things together. This is what the Database object does. Under the com.kodeco.listmaster package in your app, open the AppDatabase file and replace TODO 4 with the following code:

@Database(entities = [(Category::class)], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun listCategoryDao(): CategoryDao
}

You tell Room that the class is a Database object using the @Database annotation. The entities parameter tells your database which entities are associated with that database. There’s also a version set to 1. The database version will need to be changed when performing a database migration, which will be covered in a later tutorial.

Your database class can be named whatever you want, but it needs to be abstract, and it needs to extend RoomDatabase. All your DAOs need to have abstract methods that return the corresponding DAO. This is how Room associates a DAO with a database.

Database Instances

To use your database, you’ll need to create an instance of it in your application. Although you only needed to create a few classes, behind the scenes, Room is doing a lot of work for you to manage, map and generate SQL. Because of this, an instance of Room is resource-intensive, which is why its creators recommend you use a singleton pattern when using it in your application. To achieve that, you’ll be using Koin dependency injection tool, which is already added as dependency to the app.

Koin is a runtime dependency injection tool, which means it’s creating the instance of the requested class once it needs to be injected for the first time.

Go to the com.kodeco.listmaster package, open di package by double-clicking, and open DataModule file. You’ll find TODO 5 and TODO 6. Replace TODO 5 with the following code:

single {
    Room.databaseBuilder(
        androidApplication(),
        AppDatabase::class.java,
        DB_NAME
    ).build()
}

And TODO 6 with the next one:

single {
    val db = get()
    db.listCategoryDao()
}

When looking at the call to create your database instance, you’ll notice three parameters are passed in. A reference to the app context androidApplication(), a reference to your database class AppDatabase::class.java, and a name DB_NAME. Behind the scenes, this name corresponds to a filename that stores your database in your apps’ internal storage.

Because we don’t want to expose Database object everywhere, we’re also creating singleton of the ListCategoryDao or any other Dao that we need.

That’s it. Your app is ready to be used. But before we jump to writing down the shopping cart notes, let’s look at an awesome Room feature that lets you open the data stream to the database.

Let the Data Flow

Back in Dao section you saw that the method annotated with @Query has return type of Flow. Flow allows you to create a continuous connection to the database so you don’t have to make a query everytime you change something. Instead, it will do the job for you.

In reactive paradigm, when a source of the data changes, all the subscribers immediately get the updated data. In this case, the source is the database, and whenever you make a change in terms of inserting, updating or deleting the record from the database, all the active subscribers will get the new state for the query you’ve requested.

Inspect CategoryListScreen under the package com.kodeco.listmaster.listcategory and in the method CategoryListScreen to see how we subscribed to the stream of data that starts at CategoryDao interface in the CategoryListViewModel by calling the getAll method. Using the line of code: val categories by viewModel.categories.collectAsState(initial = emptyList())

Build the app, add a few categories and then try to delete some of them. Observe that the change is visible immediately on the list screen.

Now, close the app and try to run it again. You’ll see all the categories remain visible.