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
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")
);
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;
}
}
});
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.
You can concatenate your Observable
s 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
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.
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
...
}
All of which are lazily done on demand.