3

Here is the chain: observableOne.flatMap(event -> observableTwo).subscribe()

I'd like to take events from observableOne the very first time it emits one, then ignore all other events from this Observable until after observableTwo has either emitted a value, completed or finished with an error. Worth mentioning that ultimately I'm interested in the events from the second Observable.

The context is like, there is a Button clicking on which an event is fired, this is ObservableOne. The fired event triggers ObservableTwo, let's say it's a chain of network operations. So I want to ignore all button clicks while network operations are being executed.

Eugene
  • 59,186
  • 91
  • 226
  • 333
  • It's usual to ignore previous network operations if the button is clicked multiple times - only taking the results of the network operation for the last button click. Why have you chosen to ignore button clicks until the current network operation is complete? – Enigmativity Sep 29 '16 at 10:04
  • You seem to be over complicating the problem. Why not disable your button after a click has been received and re-enable it after dealing with the results of the network operation? – JohnWowUs Sep 29 '16 at 10:39
  • It's just a part of a more complicated logic, there's a reason for it – Eugene Sep 29 '16 at 10:51

3 Answers3

1

You could use a Flowable instead of Observable, then you could use the backpressure to achieve the desired effect (drop events until observableTwo terminates) by setting the maxConcurrency of flatMap to 1:

observableOne
    .toFlowable(BackpressureStrategy.DROP)
    .flatMap(event -> observableTwo.toFlowable(BackpressureStrategy.ERROR), 1)
    .subscribe();

I posted a similar question here.

Turns out there's also an ObservableTransformer exactly for this purpose in RxJava2Extensions library by akarnokd. It can be used like this:

observableOne
    .compose(ObservableTransformers.flatMapDrop(event -> observableTwo))
    .subscribe();
arekolek
  • 9,128
  • 3
  • 58
  • 79
0

To control the request amounts made by flatMap use a special overload:

observableOne
  .doOnRequest(System.out::println)
  .flatMap(event -> observableTwo, 1)
  .flatMap(event -> observableThree, 1)
.subscribe()

If your sources observableOne, observableTwo, observableThree are synchronous this should not be necessary but for asynchronous sources this should do the job.

Dave Moten
  • 11,957
  • 2
  • 40
  • 47
0

[Edit2] the question changed so I adapt my answer + actually give a correct answer

I don't see other way of doing this than with a status flag:

AtomicBoolean progress = new AtomicBoolean();
observableOne
        .filter(event -> !progress.get())
        .flatMap(event ->
                observableTwo
                        .doOnSubscribe(() -> progress.set(true))
                        .doOnTerminate(() -> progress.set(false))
        )
        .subscribe();

If an error happens your subscription will be canceled and you will not receive any more event, even if the button is clicked again.

If this is not what you want you can:

  • resubscribe on the error callback

    private void bindRemoteCalls() {
        if (mySubscription != null) mySubscription.unsubscribe();
        AtomicBoolean progress = new AtomicBoolean();
        mySubscription = observableOne
            .filter(event -> !progress.get())
            .flatMap(event ->
                    observableTwo
                            .doOnSubscribe(() -> progress.set(true))
                            .doOnTerminate(() -> progress.set(false))
            )
            .flatMap(event -> observableTwo, 1)
            .subscribe(
                data -> handleResponse(data),
                error -> {
                    handleError(error);
                    bindRemoteCalls();
                }
            );
    }
    
  • use onErrorReturn() (combine with a doOnError() to actually do something about it)

    AtomicBoolean progress = new AtomicBoolean();
    observableOne
        .filter(event -> !progress.get())
        .flatMap(event ->
                observableTwo
                        .doOnSubscribe(() -> progress.set(true))
                        .doOnTerminate(() -> progress.set(false))
                        .doOnError(error -> handleError(error))
                        .onErrorReturn(null)
                        .filter(data -> data != null)
        )
        .subscribe(data -> handleResponse(data));
    

Remember using subscribeOn() / observeOn() with the right schedulers if you need to.

Please consider using switchMap() in place of flatMap() --> if the button is pressed again the previous call is canceled (unsubscribed) and then a new one is started. Or to say it in Rx terms: the observableTwo previous subscription is unsubscribed and a new subscription is formed.

if you disable your button on unsubscribe and enable it on termination you can easily obtain the same result by making the button unclickable.

Daniele Segato
  • 12,314
  • 6
  • 62
  • 88