1. Code
  2. Android SDK

Android Architecture Components: the Room Persistence Library

This post is part of a series called Android Architecture Components.
Android Architecture Components: LiveData

In this final article of the Android Architecture Components series, we’ll explore the Room persistence library, an excellent new resource that makes it a lot easier to work with databases in Android. It provides an abstraction layer over SQLite, compile-time checked SQL queries, and also asynchronous and observable queries. Room takes database operations on Android to another level.

Since this is the fourth part of the series, I’ll assume that you’re familiar with the concepts and components of the Architecture package, such as LiveData and LiveModel. However, if you didn’t read any of the last three articles, you’ll still be able to follow. Still, if you don’t know much about those components, take some time to read the series—you may enjoy it.

1. The Room Component

As mentioned, Room isn’t a new database system. It is an abstract layer that wraps the standard SQLite database adopted by Android. However, Room adds so many features to SQLite that it is almost impossible to recognize. Room simplifies all the database-related operations and also makes them much more powerful since it allows the possibility of returning observables and compile-time checked SQL queries.

Room is composed of three main components: the Database, the DAO (Data Access Objects), and the Entity. Each component has its responsibility, and all of them need to be implemented for the system to work. Fortunately, such implementation is quite simple. Thanks to the provided annotations and abstract classes, the boilerplate to implement Room is kept to a minimum.

  • Entity is the class that is being saved in the Database. An exclusive database table is created for each class annotated with @Entity.
  • The DAO is the interface annotated with @Dao that mediates the access to objects in the database and its tables. There are four specific annotations for the basic DAO operations: @Insert, @Update, @Delete, and @Query.
  • The Database component is an abstract class annotated with @Database, which extends RoomDatabase. The class defines the list of Entities and its DAOs.

2. Setting Up the Environment

To use Room, add the following dependencies to the app module in Gradle:

If you’re using Kotlin, you need to apply the kapt plugin and add another dependency.

3. Entity, the Database Table

An Entity represents the object that is being saved in the database. Each Entity class creates a new database table, with each field representing a column. Annotations are used to configure entities, and their creation process is really simple. Notice how simple it is to set up an Entity using Kotlin data classes.

Once a class is annotated with @Entity, the Room library will automatically create a table using the class fields as columns. If you need to ignore a field, just annotate it with @Ignore. Every Entity also must define a @PrimaryKey.

Table and Columns

Room will use the class and its field names to automatically create a table; however, you can personalize the table that's generated. To define a name for the table, use the tableName option on the @Entity annotation, and to edit the columns name, add a @ColumnInfo annotation with the name option on the field. It is important to remember that the table and column names are case sensitive.

Indices and Uniqueness Constraints

There are some useful SQLite constraints that Room allows us to easily implement on our entities. To speed up the search queries, you can create SQLite indices at the fields that are more relevant for such queries. Indices will make search queries way faster; however, they will also make insert, delete and update queries slower, so you must use them carefully. Take a look at the SQLite documentation to understand them better.

There are two different ways to create indices in Room. You can simply set the ColumnInfo property, index, to true, letting Room set the indices for you.

Or, if you need more control, use the indices property of the @Entity annotation, listing the names of the fields that must compose the index in the value property. Notice that the order of items in value is important since it defines the sorting of the index table.

Another useful SQLite constraint is unique, which forbids the marked field to have duplicate values. Unfortunately, in version 1.0.0, Room doesn’t provide this property the way it should, directly on the entity field. But you can create an index and make it unique, achieving a similar result.

Other constraints like NOT NULL, DEFAULT, and CHECK aren’t present in Room (at least until now, in version 1.0.0), but you can create your own logic on the Entity to achieve similar results. To avoid null values on Kotlin entities, just remove the ? at the end of the variable type or, in Java, add the @NonNull annotation.

Relationship Between Objects

Unlike most object-relational mapping libraries, Room doesn’t allow an entity to directly reference another. This means that if you have an entity called NotePad and one called Note, you can’t create a Collection of Notes inside the NotePad as you would do with many similar libraries. At first, this limitation may seem annoying, but it was a design decision to adjust the Room library to Android’s architecture limitations. To understand this decision better, take a look at Android’s explanation for their approach.

Even though Room’s object relationship is limited, it still exists. Using foreign keys, it is possible to reference parent and child objects and cascade their modifications. Notice that it's also recommended to create an index on the child object to avoid full table scans when the parent is modified.

Embedding Objects

It is possible to embed objects inside entities using the @Embedded annotation. Once an object is embedded, all of its fields will be added as columns in the entity’s table, using the embedded object’s field names as column names. Consider the following code.

In the code above, the Location class is embedded in the Note entity. The entity’s table will have two extra columns, corresponding to fields of the embedded object. Since we’re using the prefix property on the @Embedded annotation, the columns' names will be ‘note_location_lat’ and ‘note_location_lon’, and it will be possible to reference those columns in queries.

4. Data Access Object

To access the Room’s Databases, a DAO object is necessary. The DAO can be defined either as an interface or an abstract class. To implement it, annotate the class or interface with @Dao and you’re good to access data. Even though it is possible to access more than one table from a DAO, it is recommended, in the name of a good architecture, to maintain the Separation of Concerns principle and create a DAO responsible for accessing each entity.

Insert, Update, and Delete

Room provides a series of convenient annotations for the CRUD operations in the DAO: @Insert, @Update, @Delete, and @Query. The @Insert operation may receive a single entity, an array, or a List of entities as parameters. For single entities, it may return a long, representing the row of the insertion. For multiple entities as parameters, it may return a long[] or a List<Long> instead.

As you can see, there is another property to talk about: onConflict. This defines the strategy to follow in case of conflicts using OnConflictStrategy constants. The options are pretty much self-explanatory, with ABORT, FAIL, and REPLACE being the more significant possibilities.

To update entities, use the @Update annotation. It follows the same principle as @Insert, receiving single entities or multiple entities as arguments. Room will use the receiving entity to update its values, using the entity PrimaryKey as reference. However, the @Update may only return an int representing the total of table rows updated.

Again, following the same principle, the @Delete annotation may receive single or multiple entities and return an int with the total of table rows updated. It also uses the entity’s PrimaryKey to find and remove the register in the database’s table.

Making Queries

Finally, the @Query annotation makes consultations in the database. The queries are constructed in a similar manner to SQLite queries, with the biggest difference being the possibility to receive arguments directly from the methods. But the most important characteristic is that the queries are verified at compile time, meaning that the compiler will find an error as soon as you build the project.

To create a query, annotate a method with @Query and write a SQLite query as value. We won’t pay too much attention to how to write queries since they use the standard SQLite. But generally, you'll use queries to retrieve data from the database using the SELECT command. Selections may return single or collection values.

It is really simple to pass parameters to queries. Room will infer the parameter’s name, using the method argument’s name. To access it, use :, followed by the name.

LiveData Queries

Room was designed to work gracefully with LiveData. For a @Query to return a LiveData, just wrap up the standard return with LiveData<?> and you’re good to go.

After that, it will be possible to observe the query result and get asynchronous results quite easily. If you don’t know the power of LiveData, take some time to read our tutorial about the component.

5. Creating the Database

The database is created by an abstract class, annotated with @Database and extending the RoomDatabase class. Also, the entities that will be managed by the database must be passed in an array in the entities property in the @Database annotation.

Once the database class is implemented, it is time to build. It is important to stress that the database instance should ideally be built only once per session, and the best way to achieve this would be to use a dependency injection system, like Dagger. However, we won’t dive into DI now, since it is outside the scope of this tutorial.

Normally, operations on a Room database cannot be made from the UI Thread, since they are blocking and will probably create problems for the system. However, if you want to force execution on the UI Thread, add allowMainThreadQueries to the build options. In fact, there are many interesting options for how to build the database, and I advise you to read the RoomDatabase.Builder documentation to understand the possibilities.

6. Datatype and Data Conversion

A column Datatype is automatically defined by Room. The system will infer from the field’s type which kind of SQLite Datatype is more adequate. Keep in mind that most of Java’s POJO will be converted out of the box; however, it is necessary to create data converters to handle more complex objects not recognized by Room automatically, such as Date and Enum.

For Room to understand the data conversions, is necessary to provide TypeConverters and register those converters in Room. It is possible to make this registration taking into consideration specific context—for example, if you register the TypeConverter in the Database, all entities of the database will use the converter. If you register on an entity, only the properties of that entity may use it, and so on.

To convert a Date object directly to a Long during Room’s saving operations and then convert a Long to a Date when consulting the database, first declare a TypeConverter.

Then, register the TypeConverter in the Database, or in a more specific context if you want.

7. Using Room in an App

The application we've developed during this series used SharedPreferences to cache weather data. Now that we know how to use Room, we’ll use it to create a more sophisticated cache that’ll allow us to get cached data by city, and also consider the weather date during the data retrieval.

First, let’s create our entity. We’ll save all our data using only the WeatherMain class. We only need to add some annotations to the class, and we’re done.

We also need a DAO. The WeatherDAO will manage CRUD operations in our entity. Notice that all queries are returning LiveData.

Finally, it is time to create the Database.

Ok, we now have our Room database configured. All that is left to do is wire it up with Dagger and start using it. In the DataModule, let's provide the Database and the WeatherDAO.

As you should remember, we have a repository responsible for handling all data operations. Let’s continue to use this class for the app’s Room data request. But first, we need to edit the providesMainRepository method of the DataModule, to include the WeatherDAO during the class construction.

Most of the methods that we’ll add to the MainRepository are pretty straightforward. It's worth looking more closely at clearOldData(), though. This clears all data older than a day, maintaining only relevant weather data saved in the database.

The MainViewModel is responsible for making consultations to our repository. Let’s add some logic to address our operations to the Room database. First, we add a MutableLiveData, the weatherDB, which is responsible for consulting the MainRepository. Then, we remove references to SharedPreferences, making our cache rely only on the Room database.

To make our cache relevant, we’ll clear old data every time a new weather consultation is made.

Finally, we’ll save the data to the Room database every time new weather is received.

You can see the complete code in the GitHub repo for this post.


Finally, we're at the conclusion of the Android Architecture Components series. These tools will be excellent companions on your Android development journey. I advise you to continue exploring the components. Try to take some time to read the documentation

And check out some of our other posts on Android app development here on Envato Tuts+!

Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.