7
votes

I am looking for what is the recommended practice in rxjava2 to handle a case where one flowable leads to conditional behaviors.

More concretely, I have a Maybe<String> for which I want to Update the String on the database if the String exists or, if it doesn't exists I want to create a new String and save it on the database.

I thought of the below but obviously it is not what I am looking for:

Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar"))).subscribe(result -> 
System.out.println("save to database "+result));
source.subscribe(result -> System.out.println("update result "+result));

The above obviously produces

save to database foo
update result foo

I tried also the below which gives the expected result but still feel it's... weird.

Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar")).doOnSuccess(result -> 
System.out.println("save to database "+result))).subscribe();
source.doOnSuccess(result -> System.out.println("update result "+result)).subscribe();

How can I have an action for when the result exists and when it doesn't exists? How is that use case supposed to be handled in rxjava2?

Update 01

I tried the below and it looks cleaner than what I came up with above. Note sure it is recommended rxjava2 practice however...

Maybe.just(new String("foo"))
     .map(value -> Optional.of(value))
     .defaultIfEmpty(Optional.empty())
     .subscribe(result -> {
         if(result.isPresent()) {
             System.out.println("update result "+result);
         }
         else {
             System.out.println("save to database "+"bar");
         }
     });
4
You are subscribing twice on the source Observable. Means you will execute it twice. In both code blocks you are showing. - masp
@masp you are correct, this is not my prefered way of doing things. How would you recommend solving my issue where I want to take action on Empty and Non-Empty results? - simon
The first 3 lines of your first block look fine, actually. But, they only cover the case where you source emits something. So, if you create another case where your source emits nothing( simply completes), then you will see the second Observable being executed. Also, personally I would use doOn* methods only to log. - masp
I added Update 01 in the post, sounds like a cleaner solution but would like to have your feedback. - simon

4 Answers

1
votes

You have the isEmpty() operator that will return you Boolean if the Maybe source is empty or not, and then you can flatMap it and write a if else statement depending on that Boolean

1
votes

This is a common pattern in our code as well, though in our case the choices are themselves async. You can't get quite the right semantic by simply composing flatMapX and switchIfEmpty (in either order), so I am curious why this isn't part of the API.

Here's what we're doing for now (this example for when the 2 options are both Completables, we have similar things for the other types as well):

public static <T> Completable flatMapCompletable(Maybe<T> target, 
                                                 @ClosureParams(FirstParam.FirstGenericType.class) 
                                                         Closure<? extends CompletableSource> completableSupplier, 
                                                 Supplier<CompletableSource> emptySupplier) {
    Maybe<T> result = target.cache();
    return result.isEmpty().flatMapCompletable(empty -> {
       if (empty) {
           return emptySupplier.get();
       } else {
           return result.flatMapCompletable(completableSupplier::call);
       }
    });
}

We're using Groovy, so we package these up as extension methods. I'm not thrilled with the need to use cache() so I'm wondering if there is a better alternative. From looking at the code, an operator which basically combines flatMapX and switch looks like it wouldn't be too hard (but I feel like I'm missing something).

0
votes

Try something like this. checkDB can return a Maybe or Single or whatever which emits either an optional or a wrapper Object.

checkDB(String)
            .flatMap(s -> {
                if (s.isPresent()) {
                    return updateDB(s.get());
                } else {
                    return insertDB("new String");
                }
            })
0
votes

There is an solution using the flatMap call with 3 params

fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
        return getById(message.id) // returns Maybe
            .flatMap(
                Function {
                    update(message) // onSuccess update call returns Single
                },
                Function {
                    Single.error(it) // onError
                },
                Callable {
                    add(message) // onComplete add call returns Single
                }
            )
    }
}

Or shorter version

fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
        return getById(message.id) // returns Maybe
            .flatMap(
                {
                    update(message) // onSuccess update call returns Single
                },
                {
                    Single.error(it) // onError
                },
                {
                    add(message) // onComplete add call returns Single
                }
            )
    }
}