Fallback Observable for RxJava

浪子不回头ぞ 提交于 2019-12-20 09:38:38

问题


I'm in search for a better way to achieve a simple Observable fallback system for empty results when using RxJava. The idea is that, if a local query for a set of data results in zero items, then a fallback query (could be a network call, or something else) should be made instead. Currently, my code consists of the following:

Observable.create(new Observable.OnSubscribe<Object>() {
  @Override
  public void call(Subscriber<? super Object> subscriber) {
     List<Object> results = queryLocalDatabase();
     if (results.isEmpty()) {
       subscriber.onError(new Throwable("Empty results"));
     } else {
       // Process results...
     }
  }
}).onErrorResumeNext(/* Fallback Observable goes here */);

Although this works, it doesn't quite make sense to throw an exception for an empty result set. I noticed that there are conditional operators available such as isEmpty, however it doesn't seem like it gets me where I want to be. For example, using isEmpty...

localObservable = Observable.create(new Observable.OnSubscribe<Object>() {
  @Override
  public void call(Subscriber<? super Object> subscriber) {
     List<Object> results = queryLocalDatabase();
     for (Object obj : results) {
       // Process object
       subscriber.onNext(object);           
     }
     subscriber.onCompleted();
  }
});

localObservable.isEmpty()
  .switchMap(new Func1<Boolean, Observable<? extends Object>>() {
    @Override
    public Observable<? extends Object> call(Boolean isEmpty) {
      if (isEmpty) {
        // Return fallback Observable.
        return fallbackObservable;
      }

      // Return original Observable, which would mean running the local query again... Not desired.
      return localObservable;
    }
  });

This almost gets me to where I want to be, with the exception that the localObservable will seemingly be ran twice in the event that there are non-empty items, which is a deal breaker.


回答1:


Use switchIfEmpty operator.

There is example of usage:

Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        // return no item
        //subscriber.onNext(...);
        System.out.println("Generating nothing :)");
        subscriber.onCompleted();
    }
}).switchIfEmpty(Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        System.out.println("Generating item");
        subscriber.onNext("item");
        subscriber.onCompleted();
    }
})).subscribe(new Observer<String>() {
    @Override
    public void onCompleted() {
        System.out.println("onCompleted");
    }

    @Override
    public void onError(Throwable e) {
        System.out.println("onError");
    }

    @Override
    public void onNext(String s) {
        System.out.println("onNext: " + s);
    }
});

Simplified with lamdas:

Observable.create(subscriber -> {
    // return no item
    //subscriber.onNext(...);
    System.out.println("Generating nothing :)");
    subscriber.onCompleted();
}).switchIfEmpty(Observable.create(subscriber -> {
    System.out.println("Generating item");
    subscriber.onNext("item");
    subscriber.onCompleted();
})).subscribe(
    s -> System.out.println("onNext: " + s),
    throwable -> System.out.println("onError"),
    () -> System.out.println("onCompleted")
);



回答2:


I'm sure there's more elegant solution to this problem but you could simply use flatMap in this case. Since you're already dealing the List<Object> which is returned from queryLocalDatabase() method you could do something like this.

Observable.create(new Observable.OnSubscribe<List<String>>() {
    @Override
    public void call(Subscriber<? super List<String>> subscriber) {
        List<String> results = queryLocalDatabase();
        subscriber.onNext(results);
        subscriber.onCompleted();
    }

    private List<String> queryLocalDatabase() {
        return Arrays.asList();
    }

}).flatMap(new Func1<List<String>, Observable<String>>() {
    @Override
    public Observable<String> call(List<String> list) {
        if (list.isEmpty()) {
            return getFallbackObservable();
        } else {
            return Observable.from(list);
        }
    }

    private Observable<String> getFallbackObservable() {
        return Observable.from("3", "4");
    }

}).subscribe(new Observer<String>() {
    @Override
    public void onCompleted() {
        System.out.println("onCompleted");
    }

    @Override
    public void onError(Throwable e) {
        System.out.println("onError");
    }

    @Override
    public void onNext(String s) {
        System.out.println("onNext: " + s);
    }
});

I've replace Object for String for the purpose of this code.




回答3:


I found a more elegant solution to this problem.

Observable<String> cachedObservable =
        Observable.from(Collections.<String>emptyList()) // Swap this line with the below one to test the different cases
        //Observable.from(Arrays.asList("1", "2"))
                .cache();

cachedObservable.isEmpty().switchMap(isEmpty -> {
    if (isEmpty)
        return Observable.from("3", "4");
    else
        return cachedObservable;
}).subscribe(System.out::println);

The key contender here is cache which replays the original emitted values.

Keep in mind the javadoc about cache.

You sacrifice the ability to unsubscribe from the origin when you use the cache Observer so be careful not to use this Observer on Observables that emit an infinite or very large number of items that will use up memory.




回答4:


After some research and looking at other peoples responses, I believe using a Transformer is the most robust solution, like so...

Observable.from(Collections.emptyList())
  .compose(new Observable.Transformer<List<Object>, List<Object>>() {
    @Override
    public Observable<List<Object> call(Observable<List<Object>> source) {
      boolean isEmpty = observable.isEmpty().toBlocking().first();
      if (isEmpty) {
        return backupObservable();
      } else {
        return source;
      }
    }
  });



回答5:


Another variant. In my case I need to try one cached entry first, then lazily attempt the other if not present:

public void test() {
    String cacheRowKey = "row";
    Observable<String> = cacheLookup(cacheRowKey, "newVersion").switchIfEmpty(
        Observable.defer(() -> cacheLookup(cacheRowKey, "legacyVersion").switchIfEmpty(
        Observable.defer(() -> onMiss(cacheRowKey;
}

private Observable<String> cacheLookup(String key, String version) {
    // Delegate to the real cache - will return effectively
    // Observable.just(xxx) or Observable.empty()
    ...
}

private Observable<String> onMiss(String key) {
    // do the expensive lookup to then populate the cache
    ...
}
  1. This first tries a lookup in cache for the key and 'new' version
  2. On cache miss, tries the cache again for the 'legacy' version
  3. On cache misses for both 'new' and 'legacy' versions, perform the expensive lookup (presumably to populate the cache).

All of which are lazily done on demand.




回答6:


You can concatenate your Observables as well:

Observable<Integer> o1 = Observable.empty();
Observable<Integer> o2 = Observable.empty();
Observable<Integer> o3 = Observable.just(3);
Observable<Integer> o4 = Observable.just(4);
Observable<Integer> os = Observable.concat(o1, o2, o3, o4);
Integer v = os.toBlocking().first();  // returns 3


来源:https://stackoverflow.com/questions/25206735/fallback-observable-for-rxjava

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!