1

I need to check that an infinite observable (events from a device) emits ne specific event lets call it "Started" which is then followed by another one, "Finished". However in between these two events, any number of different events can be received and they must be ignored. The result of this should be a Completable.complete() which is successful when the "Started" event was followed by the "Finished" event before a set timeout.

I have a working solution for this problem, however it looks ugly and too complex and I think there probably is a more elegant/simple solution. My current code looks like this, I have generalized my code so it is easier to understand, basically in this example I check that after the Flowable emits the number "5" before a timeout of 10 seconds the number "8" is received.:

    Flowable<Long> events = Flowable.interval(1, TimeUnit.SECONDS, testScheduler)
            .publish().autoConnect(1);

    return events
            .filter(number -> number == 5)
            .firstElement()
            .concatMapCompletable(number -> {
                if (number == 5) {
                    return events
                            .filter(number2 -> number2 == 8)
                            .firstElement()
                            .concatMapCompletable(number2 -> {
                                if (number2 == 8) {
                                    return Completable.complete();
                                } else {
                                    return Completable.error(new Exception("Number 3 expected, got " + number2));
                                }
                            });
                } else {
                    return Completable.error(new Exception("Number 2 expected, got " + number));
                }
            })
            .timeout(10, TimeUnit.SECONDS, Completable.error(new Exception("Timeout!")));

EDIT: I have found a cleaner version, however it seems weird since I am using the .filter operator to then Complete on the first element received, I post it here below for reference:

    Flowable<Long> events = Flowable.interval(1, TimeUnit.SECONDS, testScheduler)
            .publish().autoConnect(1);

    TestObserver testObserver = events
            .filter(number -> number == 5)
            .firstElement()
            .concatMapCompletable(number ->
                    events
                            .filter(number2 -> number2 == 8)
                            .firstElement()
                            .concatMapCompletable(number2 ->
                                    Completable.complete()))
            .timeout(10, TimeUnit.SECONDS, Completable.error(new Exception("Timeout!")))
            .test();

UPDATE2: Version which I am much more happy about:

    Flowable<Long> events = Flowable.interval(1, TimeUnit.SECONDS, testScheduler)
            .publish().autoConnect(1);

    TestObserver testObserver = events
            .skipWhile(number -> number != 5)
            .firstElement()
            .flatMapCompletable(number -> Completable.fromObservable(events
                    .takeUntil(number2 -> number2 == 8)
                    .toObservable()
            ));
Trinket
  • 15
  • 3

1 Answers1

0

I'm not sure to understand what you want to do exactelly but you can use buffer or window operators like the following:

Flowable.just(1, 2, 3, 4, 5)
        .buffer(2, 1)
        .filter(e -> e.size() > 1)
        .flatMapCompletable(e -> {
            int first = e.get(0);
            int second = e.get(1);
            if (first == 2) {
                if (second == 3) {
                    return Completable.complete();
                } else {
                    return Completable.error(new Exception("..."));
                }
            }

            return Completable.fromObservable(Observable.just(e));
        })

UPDATE

Observable<Long> source = Observable.interval(1, TimeUnit.SECONDS)
        .share();

source
        .skipWhile(e -> e != 5)
        .flatMapCompletable(e -> Completable.fromObservable(source
                .takeUntil(x -> x == 8)
                .timeout(10, TimeUnit.SECONDS)))
        .subscribe();
bubbles
  • 2,597
  • 1
  • 15
  • 40
  • Hi, Sorry I will try to clarify a bit more my question, I have now seen I can improve it. The thing is that I am receiving events such as "Started" and then a "Finished" event, however in between I can receive any other event which must be ignored. The only thing is that after receiving an "Started" event the "Finished" event must be received before a timeout. Another issue here, is that I am dealing with an infinite Flowable, that's why I did my example with an interval operator. – Trinket Oct 20 '20 at 14:21
  • the timeout starts when you receive `"Started"` ? what you want to do with elements from your source stream, you want to ignore them all ? – bubbles Oct 20 '20 at 14:48
  • Yes, the timeout should begin when "Started" is received. Just noticed this is not happening in my current implementation. However, if this proves too difficult it could just directly start without waiting for the "Started" event. The elements of the source stream not matching "Started" or "Finished" can be safely ignored. – Trinket Oct 20 '20 at 15:11
  • another question, why you're using `Completable` ? – bubbles Oct 20 '20 at 15:14
  • Because the only result I am interested in is when this process emits the "Finished" event (of course after first emitting the "Started" one). So I don't need to emit a Maybe/Single since no information must be forwarded. – Trinket Oct 20 '20 at 15:20
  • you can check the update @Trinket, when you're using `Completable` you can not return values. But this is your choice – bubbles Oct 20 '20 at 15:43
  • Thank you very much! It has helped me a lot. However your solution doesn't seem to work since it never finishes. The first skipWhile never emits the onComplete event, and the takeUntil starts propagating this since it gets a point where the takeUntil receives events with number > 8, so it is infinite and never finishes. By adding a .firstElement() below the skipWhile and also fixing the .takeUntil(x -> x == 8) it does work! I also changed the .share() to what i have, .publish().autoconnect(1) so that the second time we use the observable, it receives the events starting from the number 5. – Trinket Oct 21 '20 at 12:51
  • You asked for an elegant solution, I've offered you what you want ;) It's up to you to adapt it to whatever you want. The question were not too clear, too – bubbles Oct 21 '20 at 13:01
  • 1
    Indeed you are right. I have updated my question with the code I ended up using. But your solution is the more general one and useful! Thank you very much for your help – Trinket Oct 21 '20 at 13:02
  • you're wlcm, I'm glad to help – bubbles Oct 21 '20 at 13:03