Room Furnishings

Roughly speaking, your use of Room is dominated by three sets of classes:

  1. Entities, which are simple classes that model the data you are transferring into and out of the database
  2. The data access object (DAO), that provides the description of the API that you want for working with certain entities
  3. The database, which ties together all of the entities and DAOs for a single SQLite database

If you have used Square’s Retrofit, some of this will seem familiar:

The NoteBasics module mentioned above has a few classes related to a note-taking application, exercised via instrumented tests.

Entities

In many ORM systems, the entity (or that system’s equivalent) is a simple class that you happen to want to store in the database. It usually represents some part of your overall domain model, so a payroll system might have entities representing departments, employees, and paychecks.

With Room, a better description of entities is that they are classes representing:

That difference may sound academic. It starts to come into play a bit more when we start thinking about relations.

However, it also more closely matches the way Retrofit maps to Web services. With Retrofit, we are not describing the contents of the Web service’s database. Rather, we are describing how we want to work with defined Web service endpoints. Those endpoints have a particular set of content that we can work with, courtesy of whoever developed the Web service. We are simply mapping those to methods and classes, both for input and output. Room is somewhere in between a Retrofit-style “we just take what the Web service gives us” approach and a full ORM-style “we control everything about the database” approach.

From a coding standpoint, an entity is a Java/Kotlin class marked with the @Entity annotation. For example, here is a NoteEntity class that serves as a Room entity:

package com.commonsware.room.notes

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "notes")
data class NoteEntity(
  @PrimaryKey val id: String,
  val title: String,
  val text: String,
  val version: Int
)

There is no particular superclass required for entities, and the expectation is that often they will be simple data classes, as we see here.

The @Entity annotation can have properties customizing the behavior of your entity and how Room works with it. In this case, we have a tableName property. The default name of the SQLite table is the same as the entity class name, but tableName allows you to override that and supply your own table name. Here, we override the table name to be notes.

Sometimes, your properties will be marked with annotations describing their roles. In this example, the id field has the @PrimaryKey annotation, telling Room that this is the unique identifier for this entity. Room will use that to know how to update and delete Note objects by their primary key values. In Java, Room also requires that any @PrimaryKey field of an object type — like String — be annotated with @NonNull, as primary keys in SQLite cannot be null. In Kotlin, you can just use a non-nullable type, such as String.

We will explore entities in greater detail in an upcoming chapter.

DAO

“Data access object” (DAO) is a fancy way of saying “the API into the data”. The idea is that you have a DAO that provides methods for the database operations that you need: queries, inserts, updates, deletes, and so on.

In Room, the DAO is identified by the @Dao annotation, applied to either an abstract class or an interface. The actual concrete implementation will be code-generated for you by the Room annotation processor.

The primary role of the @Dao-annotated abstract class or interface is to have one or more methods, with their own Room annotations, identifying what you want to do with the database and your entities. This serves the same role as the functions annotated @GET or @POST in a Retrofit interface.

The sample app has a NoteStore that is our DAO:

package com.commonsware.room.notes

import androidx.room.*

@Dao
interface NoteStore {
  @Query("SELECT * FROM notes")
  fun loadAll(): List<NoteEntity>

  @Insert
  fun insert(note: NoteEntity)

  @Update
  fun update(note: NoteEntity)

  @Delete
  fun delete(vararg notes: NoteEntity)
}

Besides the @Dao annotation on the NoteStore interface, we have four functions, each with their own annotations: @Query, @Insert, @Update, and @Delete, each which map to the corresponding database operations.

The loadAll() function has the @Query annotation. Principally, @Query will be used for SQL SELECT statements, where you put the actual SQL in the annotation itself. Here, we are retrieving everything from the notes table.

The remaining three functions use the @Insert, @Update, and @Delete annotations, mapped to functions of the same name. The actual function names do not matter — they could be larry(), curly(), and moe() and work just as well. As you might expect, @Insert inserts an entity into our table, @Update updates an existing table row to reflect the supplied entity’s properties, and @Delete deletes table rows corresponding with the supplied entities’ primary keys. In this sample, insert() and update() each take a single NoteEntity, while delete() takes a vararg of NoteEntity. Room supports either pattern, as well as others, such as a List of NoteEntity — choose what fits your needs.

We will explore the DAO in greater detail in an upcoming chapter.

Database

In addition to entities and DAOs, you will have at least one @Database-annotated abstract class, extending a RoomDatabase base class. This class knits together the database file, the entities, and the DAOs.

In the sample project, we have a NoteDatabase serving this role:

package com.commonsware.room.notes

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [NoteEntity::class], version = 1)
abstract class NoteDatabase : RoomDatabase() {
  abstract fun notes(): NoteStore
}

The @Database annotation configures the code generation process, including:

Here, we are saying that we have just one entity class (NoteEntity), and that this is schema version 1.

You also need abstract functions for each DAO class that return an instance of that class. Here, we have a notes() function that returns NoteStore.


Prev Table of Contents Next

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