Exploring Room Architecture component
Content
Preface
In this article we will dive deep into Room component. In case you missed the previous articles or don’t know about architecture components then head on to my previous article Introduction to Android Architecture Components.
The code in this article is written in Kotlin. In case you don’t know Kotlin. Read this series of article to get started.
Introduction
Room is a persistence library designed to help you save your app data into the SQLite database which ships with every Android version.
Room is a robust SQL mapping library
Features
- It provides local data persistence
- Abstraction layer over existing SQLite database
- Checks SQL query at compile time
- Ability to observe for changes in the database using LiveData
- Eliminates the boilerplate code
- Plays well with RxJava
Persistence Spectrum
The spectrum shows some of the persistence libraries and where they fall in between SQL and Objects. The Room library falls in between SQL and Objects that’s because you need to write SQL.
Anatomy of Room
Database
- It defines the database holder
- It is main access point to the database connection
- It defines list of entities and database access objects (DAO)
Database Access Objects (DAO)
- They are main component of Room because they are responsible for defining methods that access database
- It defines the methods for interaction with the database
- The implementation is auto-generated at compile-time
Entity
- For each entity a database table is created
- It represents a class which holds a database row
Adding it to your project
Add the following dependencies in your app or module build.gradle
compile "android.arch.persistence.room:runtime:1.0.0-rc1"// Java
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-rc1"// Kotlin
kapt "android.arch.persistence.room:compiler:1.0.0-rc1"
PS: Release Candidate 1 (RC1) is the latest version of Architecture Components (except Paging) at the moment of writing this article.
Let’s code with Room!
Entity
The Entity class is marked with @Entity
annotation. It defines the structure of the column.
The above entity creates a table named users
with 2 columns namely id
— auto-generated primary key and name
.
By default,
- Table name is same as class name; you can change it using
tableName
property in@Entity
. - A column is created for each field and same name that’s defined in the entity; you can change the column name using
@ColumnInfo(name = "ANYTHING")
annotation.
Primary Key
It is required for every entity to have primary key. You can even have composite primary keys:
Ignore column
In order to tell Room to ignore a particular field you need to annotate it with @Ignore
Since beta1 release, Room ignores transient
fields by default unless they are annotated with @ColumnInfo
, @Embedded
or @Relation
PS: The table names in SQLite are case-insensitive
Database Access Objects (DAO)
The DAO is the one who talks with the database to perform CRUD operations based upon functions defined in the DAO. It can be either interface
or abstract
class as the implementation is written by Room.
I’m pretty sure it’s understandable except @Query
annotation. During the Persistence spectrum we saw Room is in between SQL and Objects because it supports SQL. Well @Query
is the place where you’ll mostly need to write SQL.
The SQL query is checked at the compile time itself so if you made a typo or the query is wrong then you’ll get a compile time error. You can even query multiple tables.
Observable queries
Usually we want to change the state of UI when data in the database is updated. Room allows that by providing observable queries which will notify when the data in the database is changed/updated. It is achieved using LiveData
.
Room supports Flowable
and Publisher
from RxJava:
Missed my previous article about LiveData or need to know more then check the article below:
Database
The last component is database; it brings all the pieces of Room together viz; all the entities and their DAOs.
The version indicates the schema version. It needs to be incremented when the database schema is changed/altered.
Getting the database instance:
The database instance is expensive. It is recommended to keep it in a singleton maybe by using Dagger 2
Ideally, we need to tell Room when the database version is changed and provide a guide or steps to perform migration from old version to new version.
To find out more about how to implement database migrations and how they work under the hood, check out this post:
Relations
Object Relationships are a bit tricky in Room. It’s one of the thing which needs little improvement, nonetheless let’s dive into it.
Unlike other object-relational mapping libraries, Room doesn’t allow entities directly referencing each other due to lazy loading problem. That said, it allows relations using ForeignKey
which is quite powerful but tricky.
Nested Objects
Sometimes we would want to decompose our object into sub-fields but keep them under one logical unit.
In layman terms, let’s say user
is your entity object which has address
which contains it’s own fields like city, and you want to store it under the same table called users
because logically address belongs to a user.
Let’s look at code because we understand that better ;)
Note: There is no @Entity
on top of Address
class because we don’t want to create a separate address
table in the database. Moreover, as mentioned earlier Room doesn’t support nested entity.
This is how the table will be created in the database. The fields from Address
class will be automatically embedded by Room.
Foreign Key
Let’s look at same example of user
and address
using ForeignKey
— one to one relation.
In, the user entity we mention it’s foreign key relation with the Address
entity by specifying the foreign key of User
to primary key of Address
.
We define the same Address
class only this time with @Entity
annonation which needs PrimaryKey
.
Database schema
The entities which we defined above will be stored in this fashion in the database.
Now the important question arises, how do I fetch the data? We need to create an extra POJO to define what we need. This is done to tackle the lazy loading issue.
We define a new POJO to fetch user
object and city name from addresses
table.
PS: You can define POJO to fetch other things of addresses table or all of the fields. It depends upon what you need
Finally we need to write SQL query in the DAO.
And that’s how it’s done! I know it’s slightly tricky. It gets more tricky with one to many.
For more details check Relation and One to Many with Room.
Testing
I’ll be writing a series of articles soon; showcasing testing for each architecture components which includes Room.
Meanwhile, if your curious and can’t wait then you can read about testing Room database from official docs.
Pro-tips
- Use
inMemoryDatabaseBuilder()
while developing/prototyping or testing your application. It clears the database on closing the connection to the database. - Use
fallbackToDestructiveMigrations()
if you are really sure about it. The good part is you don’t need to write migrations. The bad part is data will be purged from the database every time you increment the database version. - Use Repository pattern to sync the data between Room and Web (server).
Conclusion
I love the fact that Room is here to save us from the persistence problem and it supports reactive nature and all it’s goodies. I don’t like that we need to write the migration code that too in SQL. I wish it was automatic or semi-automatic; another thing is working with relation is tricky with Room which I think will get easier after v1.0.
Lastly, I’m glad that it uses SQLite database which is available on every Android device; preventing additional size indirectly helping us build slim APKs.
Room is like Retrofit for database
Update: What’s next?
In the next part we will look at RxJava, transactions Room and few more small things as pointed out by Andrei.