How to fix ORM limitations on Android using Room Architecture Component

Data persistence library on Android doesn’t fit all your needs? You can use Room Architecture Component together with it and enjoy advantages of both!

Introduction

As an early stage startup, we have to move fast. And in order to do so, we’ve made a decision to rely on a Serverless technology stack. Our platform heavily relies on such great services as Firestore and Cloud Functions, Contentful, BigQuery, Data Studio, Algolia and others.

But relying on 3rd party services has its downsides. You can run into scenarios that aren’t supported. As a result, you have to make a choice — either cut the corners in your feature, or change existing architecture — which is usually painful and time consuming! (also known as ‘vendor lock-in’ problem)

Luckily, sometimes you can find creative ways of solving the problems, thanks to open-source nature of modern SDKs. I’d like to showcase a true story scenario below.

Our case: eventually data fetching slowed down

For months we’ve been using data persistent library Vault from Contentful. It significantly simplified our life, since it handled a lot of things pretty well: data modelling and relationships, querying, (de)serialization, network requests, synchronization, caching — all typical responsibilities of ORM plus networking layer to CMS.

But it has a limitation, inherent to many ORM libraries:

1. Relations between entities

Even if N+1 query problem is solved by ORM, you can’t get optimal experience when relations are defined on entities level. Two approaches are possible, and both have downsides:

  • Relations are loaded together with entities: while it’s a typical approach on server side, on mobile side it leads to performance issues. Especially in lists, when full entities are retrieved every time, while most of the time you need only some of the fields.
  • Do lazy loading when related entity is needed: even when ORM supports lazy loading (and Vault don’t), it doesn’t really help. If requests are executed on UI thread, App becomes unresponsive. If requests are async you significantly complicate your UI controllers with callback hell. Both options are no go.

Room was designed with this issue in mind, it’s well explained in Room documentation.

Not all needed queries are supported

Some compound requests were not supported by ORM, so we had to do workarounds, such as querying all entities and filter the results. After amount of content has grown significantly (thanks to new team members), queries became really slow!

Both mentioned issues impacted us, as a result it took some screens 5–10 seconds to load locally available data of single screen’s ViewModel.

The situation was not easy. Replacing Vault was an option, but it was already used across all App layers! It would also take weeks to rewrite boring parts, such as caching, synchronization, API calls.

Idea: rewrite data querying layer using Room

Our architectural approach is heavily influenced by official Android App Guidelines. It keeps various layers independent, so why don’t we switch data source of our repositories? Let’s use Room for data fetching only, and keep Vault responsible for DB creation and synchronization.

Let’s use Room for fast data fetching, and keep existing Vault logic for DB creation and synchronization

How to use Room for querying existing database

We would assume that you already know basics of Room, so we’ll focus only on notable parts. Let’s keep the story short ;)

Step 1 — Create entities replicating existing database tables

Room validates all requests on compile, so it’s important to replicate existing tables structure with primary keys.

Step 2 — Create DAO with needed requests

You should define required requests to data source next. Both sync requests and async observables are supported (LiveData or RxJava).

Step 3 — Create SQL selections returning data you need

That’s the key point. You not only query your entity, but also JOIN all needed related data. Your list of entities and relations will be loaded using only 1 request.

Step 4 — Create POJO’s which reflect selected columns

You should create regular POJO class (without @Entity but with @ColumnInfo annotations). You can have multiple POJO`s for different use cases: e.g. one for entity overview, another one for entity details.

Please note that on Step 2 DAO returns NewGuidedata type.

Step 5 — Dynamically update database hash

Since Room is not managing database schema and creation, but still validates it’s consistency — we need to fake it =) After each entities definitions update we need to insert generated hash into the database (e.g. on App start). The hash is generated by Room compiler in YourDatabase_Impl.java class.

Getting writableDatabase is package-private, but we can use Kotlin extension functions to extend Vault main class. Yeah, that’s a bit dirty implementation-specific trick.

Step 6— Update repository to using room DAO

We’re almost done. What is left is to replace repository queries:

Step 7 — Transform returned POJO into your original model

Together with updating data source requests, we’re converting result set from Room-related POJO’s into regular model classes, which are already used across the App.

That’s it!

Please note that we didn’t have to change any Views, Fragments & even ViewModels. All UI-related code and data bindings stayed unchanged.

So we can keep using Vault where it shines, and replace it’s downsides with Room Architecture Component.

Time to celebrate!

PS. If you’re in our Founders Club and have the Mind Settlers App, please go Explore and Search! We think you’ll be much happier with the instant load times!

And if you’re not, please sign up for Early Access of our Platform.


How to fix ORM limitations on Android using Room Architecture Component was originally published in Agility Scales on Medium, where people are continuing the conversation by highlighting and responding to this story.