Search code examples
androidrx-javarx-kotlin

How to implement robust offline support with rx-kotlin?


TL;DR Looking for recommendations on robust offline support using rx-kotlin.

I've followed a nice guide on offline-support in Android apps. It works like this:

  1. Load data from the Internet, go to step 3 if error
  2. Store the data in the local database
  3. Load the data from the local database
  4. Display the data in the UI

The code is:

 Observable.mergeDelayError(
                loadRemoteData()
                    .doOnNext { writeDataToLocalDatabase(it) }
                    .subscribeOn(Schedulers.io()), 
                loadDataFromLocalDatabase()
                    .subscribeOn(Schedulers.io())
            )

Unfortunately, this approach relies on the database code always working. If the database operations for some reason fail, everything fails, even when the data is loaded successfully from the remote server.

Is there a way I can achieve the following using rx-kotlin/rx-java?:

  1. Load data from the Internet, go to step 3 if error
  2. Store the data in the local database
  3. Load the data from the local database
  4. (if steps 2 or 3 failed) Use the data from step 1
  5. Display the data in the UI

I'd like to avoid loading the data from the Internet twice. I'm using room + retrofit, if that matters.

EDIT: Thanks to @MinseongPark, I ended up with the code below. The mergeDelayError reports an error only if both the remote and the local source fails, and if the writeDataToLocalDatabase method fails (throws an Exception), then that does not keep the remote data from being reported to the UI. The information about errors in writeDataToLocalDatabase is saved by reporting it remotely. Now, the code is robust to one of the two sources failing, and to writing new entries to the database failing.

        return Observable.mergeDelayError(
            loadRemoteData().doOnNext {
                try {
                    writeDataToLocalDatabase(it)
                } catch (error: Throwable) {
                    Timber.d(error)
                    Crashlytics.logException(error)
                }
            },
            loadDataFromLocalDatabase()
        )
            .subscribeOn(Schedulers.io())

Solution

  • Try this.

        Observable.mergeDelayError(
            loadRemoteData()
                .doOnNext { runCatching { writeDataToLocalDatabase(it) } }
                .subscribeOn(Schedulers.io()),
            loadDataFromLocalDatabase()
                .onErrorResumeNext(Observable.empty())
                .subscribeOn(Schedulers.io())
    
        )