rxJava2 Combining Single, Maybe, Completable in complex Streams
Asked Answered
T

3

14

I am very excited with new RxJava Sources such as: Single, Maybe, Completable, which make your interfaces classes cleaner and prevent from a lot of mistakes during create of your 'Source' (e.g. forgetting to call onComplete())

But it requires lots of boilerplate to combine them into a complex stream.

E.g. we have common Android situation of loading and caching data. Let's assume we have 2 sources api and cache and we would like to combine it:

public interface Api {
    Single<Integer> loadFromNetwork();
}

public interface Cache {
    Maybe<Integer> loadFromCache(); //maybe because cache might not have item.
}

let's try to combine it:

final Single<Integer> result = cache.loadFromCache()
        .switchIfEmpty(api.loadFromNetwork());

it will not compile, because Maybe doesn't have overload Maybe.switchIfEmpty(Single):Single

so we have to convert everything:

final Single<Integer> result = cache.loadFromCache()
        .switchIfEmpty(api.loadFromNetwork().toMaybe())
        .toSingle();

Another possible way to combine it also requires сonversion:

final Single<Integer> result = Observable.concat(
            cache.loadFromCache().toObservable(),
            api.loadFromNetwork().toObservable()
        ).firstOrError();

So I don’t see any way to use the new sources without many transformations that add code noise and create a lot of extra objects.

Due to such issues, I can't use Single, Maybe, Completable and continue to use Observable everywhere.

So my question is:

  • What are the best practices of combining Single, Maybe, Completable.

  • Why these Sources don't have overloads to make combing easier.

  • Why these Sources don't have common ancestor and use it as
    parameter of switchIfEmpty and other methods?


P.S. Does anybody know why these classes doesn't have any common hierarchy?
From my perspective if some code can work for example with Completable it will also works fine with Single and Maybe?

Tibbitts answered 26/7, 2017 at 15:39 Comment(2)
I think this covers everything except the best practices. If you need a method such as switchIfEmptySingle perhaps you could ask for it.Trembles
When an item is not available in cache and you load it from network, would you want the network call to update cached data as well? What I am saying is, why would you want to combine the two? Let's say you have a cache storage, a Room Database for example, and you observe a table with a Flowable, this stream will not emit if there isn't anything specific to your query. At the same time you send a request out and if there is data returned and when you put it into your table and it's different the Flowable will emit.Cellobiose
T
8

RxJava 2.1.4 that was released on Sep 22, 2017 adds needed overload Maybe.switchIfEmpty(Single):Single.

So in case when we would like to combine following classes:

public interface Api {
    Single<Integer> loadFromNetwork();
}

public interface Cache {
    Maybe<Integer> loadFromCache(); //maybe because cache might not have item.
}

We can finally do:

final Single<Integer> result = cache.loadFromCache()
        .switchIfEmpty(api.loadFromNetwork());

Rx team has done great job by adding extra overloads to Maybe, Single, Observable, that simplifies combining them together.

As for release 2.1.16 we have following methods for combining Maybe, Single and Completable:

Maybe: flatMapSingleElement(Single):Maybe, flatMapSingle(Single):Single, switchIfEmpty(Single):Maybe, flatMapCompletable(Completable):Completable

Single: flatMapMaybe(Maybe):Maybe, flatMapCompletable(Completable):Completable

Completable: andThen(Single):Single, andThen(Maybe):Maybe

Tibbitts answered 12/7, 2018 at 10:20 Comment(0)
C
2

I know the question is already old but, it seems no accepted answer yet.

Since RxJava 2.1.4, they finally add:

public final Single<T> switchIfEmpty(SingleSource<? extends T> other)

So you could simplify your chain to:

cache.switchIfEmpty(api)

This should be preferred way to such case if you have latest version of RxJava. Just be aware the method is annotated with @Experimental so it might be changed again in the future.

Colligan answered 11/7, 2018 at 5:29 Comment(0)
C
0

Maybe i's not a comprehensive answer but i try to answer to your specific use case:

i think the purpose of your combining task is to fetch data from cache, if the result is empty you want to call remote api:

    final Single<List<Integer>> single = api.call();
    Maybe<List<Integer>> maybe = disk.call();


    Single <List<Integer>> composedSingle = maybe.flatMapSingle(new Function<List<Integer>, SingleSource<?>>() {
        @Override
        public SingleSource<?> apply(List<Integer> integers) throws Exception {
            if(integers.isEmpty()) return single;
            else return Single.just(integers);
        }
    });

I've not tested it, but i think could be a possible solution (not sure if the best).

Centralization answered 27/7, 2017 at 9:47 Comment(3)
if maybe simply completes without emission, flatMapSingle will throw exception.Trembles
yes, you're right. So i should remove the proposed solution and the answer at all?Centralization
There is another problem in proposed code: empty list is not the best way to indicated that there are no such item in cache, because it doesn't allow to cache correctly empty response from server.Tibbitts

© 2022 - 2024 — McMap. All rights reserved.