16 Październik 2018

Room Persistence Library Introduction – part 1

android

Autor: Michał Konkel

Room Persistence Library Introduction – part 1

This article is the first part of the three-part series that will smoothly introduce Room Persistence Library to you.
The first part will be focused on configuring the project and explaining the basic structures.
The second part will touch on Room additional features such as embedded entities and type converters, we will also write some migrations and tests.
The third part will be focused around the database relations and some queries, also we will use RxJava2 and LiveData

All sources can be found in related GitHub project.

What is Room?

The Room is a persistence library that provides an object-mapping abstraction layer over SQLite to more robust database access while harnessing the full power of SQLite. It comes along with the Architecture components and was presented at Google I/O in 2017. Now it has reached version 2.0 and it is a part of Android Jetpack (on 10.10.2018 the 2.1 alpha 1 version was released). In this article, we will focus on version 1.1.1.

Let’s start!

At the beginning we need to create an Android Project with the Kotlin support, API 27 (or greater) with “No Activity” option.
To use Room we need to add some dependencies to our gradle file

Basic elements

Room consists of a few basic elements that you should know before starting any work.

@Entity

A class annotated with @Entity will represent our column in the database. Basically, it is just a POJO set of related fields.

By default, Room creates a column for each field that’s defined in the entity. If an entity has fields that you don’t want to persist, you can annotate them using @Ignore annotation.
To persist a field, Room must have access to it. You have to make a field public, or at least provide a getter and setter for it.

The tricky part with @Ignore and Kotlin _behind the scenes_ generation of constructors for nullable fields. In that particular case, Kotlin will generate every possible constructor for class with nullable values – this will cause an error – because Room needs an empty constructor. One way is to override constructors as described in the kotlins documentation. The Second one (the easiest one) is to set default values for the fields

@PrimaryKey

Each entity must define at least one field as a primary key. Even when there is only one field, you still need to annotate the field with the @PrimaryKey annotation. In addition, if you want Room to assign automatic IDs to entities, you should set the @PrimaryKey autoGenerate property to true. You can also provide a composite primary key, just use the primaryKeys property of the @Entity.

By default, Room uses the class name as the database table name. If you want the table to have a different name, set the tableName property of the @Entity annotation.
Similarly to the tableName property, Room uses the field names as the column names in the database. If you want a column to have a different name, add the @ColumnInfo annotation to a field.

Let’s write some code!

What we’ve got here is a simple user entity whose name will be users, primary key will be autogenerated long. The code above will generate a corresponding table in the database.

@Dao

To access your app’s data using the Room persistence library, you should work with data access objects or DAOs.
This set of DAO objects forms the main component of Room, as each DAO includes methods that offer abstract access to your app’s database.
Basically, this is the point where you will be communicating with the database – here you will be defining your data interactions, mapping SQL queries to functions and more.

It is recommended to have multiple Dao classes in your codebase depending on the tables they touch.
Room creates each DAO implementation at compile time.

DAO consists of 4 major methods @Insert, @Update, @Delete and @Query.

@Insert

The implementation of the method will insert its parameters into the database.
If the @Insert method receives only one parameter, it can return a long, which is the new row_id for the inserted item. If the parameter is an array or a collection, it should return long[] or List<Long> instead.
@Insert contains the onConflict property which determines the SQLite conflict resolving strategy when inserting data.

@Update

The implementation of the method will update its parameters in the database if they already exist (checked by primary keys). If they don’t already exist, this option will not change the database.

@Delete

The implementation of the method will delete its parameters from the database. It uses the primary keys to find the entities to delete.

@Query

The main annotation used in DAO classes allows you to perform read/write operations on a database. The query is verified at compile time by Room to ensure that it compiles fine against the database.

Let’s write some code!

As you can see we have some basic methods that allow us to insert, update or delete a single user or a list of users. The methods mentioned above use the primary key to determine which row should be affected.

You can also pass parameters into queries to perform filtering operations, such as only displaying users with a certain name.

Room only supports named bind parameter to avoid any confusion between the method parameters and the query bind parameters.
Room will automatically bind the parameters of the method into the bind arguments. This is done by matching the name of the parameters to the name of the bind arguments.

We will focus on this a little bit more in the second part of the article series.

@Database

Contains the database holder and serves as the main access point for the underlying connection to your app’s persisted, relational data.
A class annotated with @Database should be an abstract class and extend RoomDatabase.
You can receive an implementation of the class via Room.databaseBuilder.

RoomDatabase provides direct access to the underlying database implementation but you should prefer using Dao classes.

Database class should consist at least of:

  • List of entities
  • DB version
  • Abstract methods returning DAO’s
  • Database builder method.

Let’s code to see this in action!

From the beginning:
Annotation @Database requires two properties:

  • List of entities – the tables in our DB (classes)
  • DB version

At the top of the class, we should declare all DAO’s as the abstract functions – it’s just a convention.
Then, we should always use the singleton pattern to obtain the database object, because the creation of the DB connection is quite expensive.
The code above will create room database when anything asks for its instance.

We are also using:

  • @Volatile – that has semantics for memory visibility. Basically, the value of a volatile field becomes visible to all readers (other threads in particular) after a write operation completes on it. Without volatile, readers could see some non-updated value.
  • synchronized(this) – in the simplest words, when you have two threads that are reading and writing to the same ‚resource’ you need to ensure that these threads access the variable in an atomic way. Without it, the thread 1 may not see the change thread 2 made to a variable.

Furthermore, it’s also quite handy to get some Injector class that will provide necessary objects that we will later use in our Activity.

Another thing we should take care of is pre-populating our database in some data set. With the traditional approach the data will probably come from some web API – for this short tutorial, we will use prepared data.

For this task, we will add the onCreate callback to the databaseBuilder. We should keep in mind that any operation related with the DB cannot be performed on the mainThread – because Room will throw an exception – so kotlin coroutine sounds like a good plan for this task.

We will need to add some dependencies to our app gradle.

Then we also need to add some code to the database builder.

Our prefilled data can look like that.

The given changes will create two new users when DB is first created, this will allow us to pre-populate DB when it’s first created. Now there is nothing more left for us, let’s finally check if everything works.

We can add some simple activity to validate the code.

Now if you run the application and everything went OK and the is showing a blank screen you should see a log message similar to this

That’s All Folks! We’ve reached the end of the first part of the introduction to the Room Persistence Library. I hope you’ve enjoyed the post and you can’t wait for more.
The second part will cover some details of @Entities, we will learn how to use TypeConverters and Embedded Entities.

Cheers!

W celu poprawy jakości naszych usług używamy ciasteczek.

Możesz je zablokować poprzez zmianę ustawień przeglądarki.