Caching and synchronization with Realm, Retrofit2 and RxJava on Android

Thibaud Giovannetti

Publié le 10 mars 2017 - 3 min de lecture

asset 1

A few years ago, mobile applications that required access to the internet could not be launched when you had no connection. This time has changed considerably and today, despite the improved coverage and quality of mobile networks, connected applications offering a complete offline mode have become familiars.

At Playmoweb we often develop read-only applications for our customers. Offline access to such apps is now essential, and despite the time it takes to develop it, it is rather trivial to implement with Realm.

Let’s see what we can do.


I’m not going to cover the whole project initialization in this post. I’m going to use an MVP pattern with Dagger, Retrofit2, RxJava and Realm.

Warning, Realm will be used as a simple storage provider and I won’t use it as a reactive database (notifications and live datas).

Naive sync and caching approach with RxJava1

Let’s imagine we want to sync a list of Issues from a distant API to our Android app. First, let’s define a couple of classes to achieve this.

A simple model, an Issue class extending RealmObjectThe Retrofit2 interface to get datas from our APIIssueService.java — Sync realm and api callMainPresenter to get and sync Issues

We have something here, we are able to get local Issues and sync them with an async call. In an offline situation, even if the Retrofit call fails, realm will always return its local objects as copies !

But an app is never that simple. Most of the time you have 5/10 more models to sync, and sometimes it’s only a get, a post, …

Do I have to write all this every time ? Hum, yes :)

Store2Store to make your life easier

Actually, you don’t ! You can use a little library named Store2Store to do all this job for you, seamlessly. You can find it here. This library facilitates sync and caching between two stores in an abstract way. The realm concrete implementation can be found here. If you want to build your own store (mysqli, file, …) it’s up to you and it’s very easy.

The Store2Realm library now use RxJava2 by default and previous 3.0.0 versions are out of date. The Rxjava1 branch is incompatible and not maintained.

Configuration

To use it you need to add this line into your application build.gradle file:

compile 'com.github.playmoweb:store2realm:3.0.1'

And this in your root build.gradle file:

allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}

Usage

Let’s change our IssueService to use a Realm store by default.

IssueService use now a realm store as a synced store

The Retrofit call does not change, but as you can see all the realm part is gone. The method getAll() is now implemented in a DAO and is inherited from StoreDao. The two parameters filter and sorting can be used to send filters to the request. Finally, the retrofit response is wrapped into a Optional.

From the MainPresenter, things change a little bit as well, in a good way.

MainPresenter.java with a CustomObserver

As you can see, we do not change our habits and you can chain as many functions you need to on your datas with RxJava.

The onNext will be called twice. The first call will happen when the first store (Realm) will retrieve the data (cached). The second call will be emitted by Retrofit after synchronization with the Realm store.

The onNext() callback will be called twice and the view will receive two updates

For now, every time we ask for fresh data through our IssueService an API call is made and the view receives two updates. That’s pretty cool, isn’t it ?

Let’s take a look to the synchronization and caching flow.

Operations flow for a getAll() call
Operations flow for a getAll() call

Under the hood, Store2Store will do its best to handle errors during synchronization. For instance, when you delete an Issue locally, if the api is unreachable an error will be thrown by Retrofit. Store2Store will catch the error for you, re-insert the Issue and throw again an error (to notify you that something went wrong. This process is important to have a local storage coherent with the second store (API or anything else).

In the version 3.0.0, all the CRUD operations are currently available. When you want to use a new method like insert() you simply need to override the public method of StoreDao in the IssueDao to use it. Thereafter an example with insert().

insert() must be override before use

That’s all for now, I let you explore the library by yourself if you think you can do something cool with it. Thanks for reading.