Google I/O 2017 was quite revolutionary. We’ve seen a lot of amazing new products announced and heard that Google will be investing a lot into machine learning-related technologies.
This year’s I/O was also extraordinary for Android developers, as we’ve finally received official support for Kotlin (yay!) and a brand new collection of Android libraries called “Android Architecture Components”.
Now the time has come to test the new solutions. Earlier, we looked into new lifecycle components, and now we are going to take a deep dive into the Room Persistence Library which has the potential of becoming a new Retrofit for local storage – not only because of its popularity but also the similarities between the two that you will spot while using it.
Currently, clean SQLite is almost forgotten and does not really exist in commercial projects. Even though it is still the only database solution suggested by official documentation and gives you great (if not the best) control over local storage, the price of its usage is just too high. The reason for it is that developers end up producing lots of boilerplate writing all of the queries and then converting them to Java objects. This can be problematic for people with no SQL knowledge, not to mention the lack of default support for any reactive components, which was the big advantage of some ORMs.
Mistakes are bound to occur in the process of writing SQL queries, and the worst thing is that you won’t get to know that until you compile your app and try to execute your query. Some of those issues were addressed in popular libs like SQLBrite and SQLDelight, but working with these libraries still wasn’t easy, and the learning curve was exponential. Because of this, the number of persistence-related libs on Android is high, and we are spoilt for choice.
At Netguru, we tested lots of available solutions, and as a result of this research, we’ve settled mostly with Realm and greenDAO, which became part of our default stack. We have embraced their advantages and accepted their disadvantages, but time goes by, and new solutions arise, so we’ve decided to test Room.
Just like Retrofit builds an abstraction over HTTP calls, Room builds an abstraction over SQL queries. To create a Retrofit and set up a client, you use Retrofit.Builder(). With the use of the client, you generate Retrofit call implementations by calling retrofit.create(...) and simply providing it with an interface describing your calls, which is done using various annotations.
Room takes a similar approach, but DAO interfaces are registered in a class extending RoomDatabase. Then you build your class by calling Room.databaseBuilder(...) and passing an abstract class extending RoomDatabase. Room will generate proper implementations of your interfaces just like Retrofit does, and you will be able to access those the through the implementation of your database class returned by the builder. It’s even possible to share data access interfaces between Retrofit and Room by defining the same interface for remote and local storage access.
Still, it isn’t much more than an interesting fact, because it will work well in very few cases, and you should be aware of drawbacks and limitations of this solution, which I will share in Part 2 of this article. Just like Retrofit, Room provides support for RxJava2.
It will execute and convert queries to Java objects for you in a way that might be similar to what you know from Retrofit and Gson. It will create tables for you, so no more create table queries. It will allow you to easily build reactive streams of data.
Room won’t handle object references for you (although it is possible to embed an object into a table by using @Embedded annotation for our class type). It is a feature of Room’s design, so you are not allowed to put any fields reflecting relations between objects – it has to be done by referencing primary key.
In our case, it meant that we weren’t able to create a list of ChecklistItemDb’s in TaskDb to reflect our one-to-many relationship. Why? Some ORMs (such as greenDAO, which allows you to create object dependencies) are lazily initialized, the outcome of which is that when you call a getter returning another object in a relation, you are actually making another query. Poor design might cause a developer or code reviewer to not even notice the moment such a call is made on the UI thread, and we all know that making such calls is not a good idea because it might cause framerate drops.
Actually, Room is well secured against calls on the UI thread, and by default, you won't be able to make any – dare to do so and an exception will be thrown causing a crash. This is a place where RxJava might come in handy, because it allows the delegation of all work to another thread easily, and this is the way I'm going to present in Part 2. Of course, the choice is yours, and you might use any other solution known to you.
Another thing Room won’t stop you from doing is writing SQL queries, but there will be much fewer of them, and they will be much more friendly thanks to the built-in annotations and compile time checks. This will allow you to experience the full power of SQLite without a hassle.
We went through the basic functionalities of Room and its similarities to Retrofit, but this is not the end. In Part 2, we are going to dig into the implementation details, some problems and cool features of Room spotted while we were implementing our example app (which is going to be published together with Part 2 of this article).