0

In my Android app I am using domain level Repository interface, which is backed with local DB implemented using SqlBrite and network api with Retrofit observables. So I have method getDomains(): Observable<List<Domain>> in Repository and two corresponding methods in my Retrofit and SqlBrite. I don't want to concatenate or merge, or amb these two observables. I want my Repository to take data only from SqlBrite and since SqlBrite returns QueryObservable, which triggers onNext() every time underlying data changed, I can run my network request independently and store results to SqlBrite and have my Observable updated with fetched from network and stored to DB data. So I tried to implement my Repository's getDomains() method as follow:

fun getDomains(): Observable<List<Domain>> {
    return db.getDomains()
                   .doOnSubscribe {
                       networkClient.getDomains()
                                    .doOnNext { db.putDomains(it) }
                                    .onErrorReturn{ emptyList() }
                                    .subscribe()
                   }
}

But in this case every time the client should subscribe, every time it would make network requests, that is not so good. I thought about other do... operators to move requests there, but doOnCompleted() in case of QueryObservable would never be called, until I call toBlocking() somewhere, which I won't, doOnEach() also not good as it makes requests every time item from db extracted. I also tried to use replay() operator, but though the Observable cached in this case, the subscription happens and results in network requests. So, how can combine these two Observables in the desired way?

Oleg Osipenko
  • 2,409
  • 1
  • 20
  • 28

2 Answers2

1

Ok, it depends on the concrete use case you have: i.e. assuming you want to display the latest data from your local database and from time to time update the database by doing a network request in the background.

Maybe there is a better way, but maybe you could do something like this

 fun <T> createDataAwareObservable(databaseQuery: Observable<T>): Observable<T> =
      stateDeterminer.getState().flatMap {
        when (it) {
          State.UP_TO_DATE -> databaseQuery // Nothing to do, data is up to date so observable can be returned directly

          State.NO_DATA ->
            networkClient.getDomains() // no data so first do the network call
                .flatMap { db.save(it) } // save network call result in database
                .flatMap { databaseQuery } // continue with original observable

          State.SYNC_IN_BACKGROUND -> {
            // Execute sync in background
            networkClient.getDomains()
                .flatMap { db.save(it) }
                .observeOn(backgroundSyncScheduler)
                .subscribeOn(backgroundSyncScheduler)
                .subscribe({}, { Timber.e(it, "Error when starting background sync") }, {})

            // Continue with original observable in parallel, network call will then update database and thanks to sqlbrite databaseQuery will be update automatically
            databaseQuery
          }
        }
      }

So at the end you create your SQLBrite Observable (QueryObservable) and pass it into the createDataAwareObservable() function. Than it will ensure that it loads the data from network if no data is here, otherwise it will check if the data should be updated in background (will save it into database, which then will update the SQLBrite QueryObservable automatically) or if the data is up to date.

Basically you can use it like this:

createDataAwareObservable( db.getAllDomains() ).subscribe(...)

So for you as user of this createDataAwareObservable() you always get the same type Observable<T> back as you pass in as parameter. So essentially it seems that you were always subscribing to db.getAllDomains() ...

sockeqwe
  • 15,574
  • 24
  • 88
  • 144
  • where the State should come from? – Oleg Osipenko Jun 27 '16 at 08:05
  • That is an implementation detail depending on your use case. `stateDeterminer.getState()` returns an `Observable`. this could be a check if the database is empty and then return `State.NO_DATA` or a timebase thing (i.e. return ` State.SYNC_IN_BACKGROUND` will be returned every 24 hours, so that data will be refreshed once per day) and so on. It really depends on what you want to achieve and how often and when you want to sync by doing a network call. – sockeqwe Jun 27 '16 at 08:16
  • makes sense. I want to fetch fresh data every time users starts application and `getDomains` called first time, then in some amount of time, e.g. 120 seconds, when subscribe() calls I want to return only from db. But if users pulls down list to refresh I want to reload data from network – Oleg Osipenko Jun 27 '16 at 08:54
0

if your problem is that you have to subscribe your observer every time that you want to get data you can use relay, which never unsubscribe the observers because does not implement onComplete

   /**
 * Relay is just an observable which subscribe an observer, but it wont unsubscribe once emit the items. So the pipeline keep open
 * It should return 1,2,3,4,5 for first observer and just 3, 4, 5 fot the second observer since default relay emit last emitted item,
 * and all the next items passed to the pipeline.
 */
@Test
public void testRelay() throws InterruptedException {
    BehaviorRelay<String> relay = BehaviorRelay.create("default");
    relay.subscribe(result -> System.out.println("Observer1:" + result));
    relay.call("1");
    relay.call("2");
    relay.call("3");
    relay.subscribe(result -> System.out.println("Observer2:" + result));
    relay.call("4");
    relay.call("5");
}

Another examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/relay/Relay.java

paul
  • 12,873
  • 23
  • 91
  • 153
  • No. It's not my problem. It's ok to unsubscribe and subscribe during the application lifecycle. My problem is that I don't want to execute network request every time I subscribe. If I'd have to work with network directly I'd use `replay(120, TimeUnit.Seconds)` operator. But I work with db. I want to subscribe it, but execute network request not every time I subscribe – Oleg Osipenko Jun 26 '16 at 18:09
  • Did you try cache operator? – paul Jun 26 '16 at 18:50
  • I tried to use `replay()` operator on my network observable – Oleg Osipenko Jun 26 '16 at 19:15