Android: Repository Pattern Retrofit 2 and RxJava 2 (and Room..)

So I wanted to add offline view-ability of data to my app. Arguably the most popular way to go currently seems to be the repository pattern (or a variation of it…). There are a whole bunch of (blog)posts out there, but so far I hadn’t found any that covers all the details for what I required in my use case.

So what did I need?

  1. Whenever I request data I want my data layer to make an api call to get me the latest up-to-date data.
  2. If for some reason the api call takes to long or the server responds with an error or the network is unavailable I want my local stored data to be displayed on the UI and the UI to either get refreshed when delayed server data is received or inform the user of the issue through a snackbar, popup dialog, etc.
  3. Also I want to prevent any kind of UI flickering due to an UI refresh call when data from different sources (database, api call & memory cache f.e.) are received shortly after one another.

Local Storage

For caching data on the device I decide to make use of Google’s Room Persistance Library. I won’t be talking about how to implement Room here since there are sufficient resources out there. If you would like to read up on Room I’ve added a couple of the links at the bottom of this article. Also you could argue whether or not to use Room and give preference over another option like Realm that is available for iOS as well for example, but that’s outside of the scope of this article.

RxJava for fetching data via the Repository

I liked Ovidiu Latcu’s post on the repository pattern with room and Rx. Pretty clear, straight forward and easy to understand. To combine observables from different sources he uses the concatArray operator. If you look at the Repository class you see that the public function getUsers() returns an observable of List<User> which combines observables from two sources. The concatArray operator is used here which first emits an observable of a list of users stored in the local database(getUsersFromDb) and secondly the list of users from the api call.

class UserRepository(val userApi: UserApi, val userDao: UserDao) {
fun getUsers(): Observable<List<User>> {        
return Observable.concatArray(
getUsersFromDb(),
getUsersFromApi())}
 ...
}
merge versus concatArray

Another way to combine observables from different sources is by using the merge operator which Miguel Juárez uses in his post Chaining multiple sources with RxJava . I opted for the concatArray operator though because one problem that could occur and that I would need to take care of is that the merge operator would emit the observables in order of whichever source would return it’s observable first. This could mean that if the local database for some reason would be slower than the network call to emit its observable there would be a possibility that older not up-to-date data (from db) would be set on the ui instead of the most up-to-date date from the api request would be used.

Using concatArrayEager

The problem with concatArray is that requesting the observable from the subsequent source doesn't start until the first Observable completes. This could hypothetically mean that if the database takes 3 seconds to provide the user list and the api request 5 seconds the total operation would take ~8 seconds. This is not an optimal scenario. Ideally we want all operations to start simultaneously so that the returned result takes (more or less) as much time as the longest operating function to finish (so 5 sec. here) instead of their execution times combined. For this RxJava introduced concatArrayEager which does exactly that. It starts both requests but it buffers the result from the latter one (if that comes in first) until the former Observable finishes.

Preventing UI Flicker

So far requesting to get the user list from the UserRepository in the example could result in the subscriber receiving 2 observables quickly after each other and updating the ui twice which could be perceived as ui flicker. To solve this issue we can make use of the debounce operator.

debounce operator

In Ovidiu’s example you can see how one could use the debounce operator. Here he calls getUsers() from a ViewModel. The debounce operator will then take care of NOT delivering the db item if the api call is fast enough (under 400 ms here) while returning an empty user list when an error occurs…

class UserListViewModel(val userRepository: UserRepository) { 
  fun getUsers(): Observable<UsersList> { 
return userRepository.getUsers()
.debounce(400, TimeUnit.MILLISECONDS)
.onErrorReturn { UsersList(emptyList(), “An error
occurred”, it)
}
}

What’s missing?

So the pattern here so far is clean and simple. For what I needed it didn’t suffice yet though. For example imagine the api call returning with an error responds within the debounce time of 400 ms. Instead of returning an empty list (through .onErrorReturn) I actually want the user list from the database returned. Plus I want the error returned so that my Presenter/ViewModel (or whatever you want to use..) can determine what to do and inform the user for example of network issues, retry possibilities, etc.

Possible solutions

So there are 2 problems I needed to solve:

  1. If the network call returns an error I want the data from the database to be displayed.
  2. If the network call returns an error I also wanted the Presenter to be informed of the error so it can handle the specific error accordingly.

*For brevity I am not concerned with database issues at the moment

materialize() -> filter { … } -> dematerialize() operators…

One way, as Chandra pointed out to me in this stackoverflow post, to make sure the database items are not debounced is to make use of the materialize operator. What .materialize() does is basically wrap the observed object types into an observable Notification object on which we can check whether the onNext, onError and/or onComplete methods are called. Dematerialize, as you might guess, reverses the effect.

So using .materialize() I can filter out any Observable on which the .onError method is called before passing it the subscribers. This way if an api call returns with an error the database items are not debounced when getUsers() is called on the UserRepository.

So the UserRepository’s getUsers function would look something like this:

class UserRepository(val userApi: UserApi, val userDao: UserDao) {
  fun getUsers(): Observable<List<User>> {        
return Observable.concatArray(
getUsersFromDb(),
getUsersFromApi()
.materialize()
.filter{ !it.isOnError }
.dematerialize<
List<User>>()
)}
...
}

This would solve the first problem, but as to inform my Presenter of which error occurred I still need to find a solution.

As hrskrs suggested in this article we could do all our error handling in one place preventing us from having to write and duplicate a lot of code every time we’re subscribing to Observables. Since I needed to have my error handled in different ways (either showing a permanent or non-permanent snackbars, alert dialogs and/or log to crashlytics) depending on which screen of the app the user is in and the type of error that occurred i decided to use a callback interface. This way I could have the error handling code done by case and explicitly in every presenter, which i thought would also add to code readability.

The getUser() function of the UserRepository would then start to look something like this:

fun getUsers(errorCallback: ErrorCallback): Observable<List<User>> {        
return Observable.concatArrayEager(
getUsersFromDb(),
getUsersFromApi()
.materialize()
.observeOn(AndroidSchedulers.mainThread())
.map {
// if the observables onError is called, invoke
// callback so that presenters can handle error
it.error?.let {
handleErrorCallback(it, errorCallback)
}
// put item back into stream
it
}
.filter{ !it.isOnError }
.dematerialize<
List<User>>()
.debounce(400, MILLISECONDS)
)}

Note here observeOn mainThread is used since the map function can encounter an error that triggers a callback to the presenter handling a UI event.

Conclusion

Pretty cool, no?! With just a bunch of lines RxJava can handle a whole bunch of problems i needed to solve. If you have any remarks or additional information I’d love to hear about it.