Baker
Baker

Reputation: 28080

RxJava - two Observable sources, combine output only on certain values

Question:

Which operator or transform should I be using to combine two data streams in smallest amount of code and in the most efficient way?

ViewPager, RxBinding, Event stream

With an Android ViewPager, I'm observing events (using RxBinding)

1) OnPageSelected (page currently visible)

Observable<Integer> pageSelectObs = RxViewPager.pageSelections(mPlaceImageViewPager);

and

2) OnPageScrollStateChanged (swipe starting = 1, in motion = 2, complete = 0)

Observable<Integer> scrollStateObs = RxViewPager.pageScrollStateChanges(mPlaceImageViewPager);

The stream of Integers looks like this:

I/System.out: Page: 0 ScrollState: 1
I/System.out: Page: 0 ScrollState: 2
I/System.out: Page: 1 ScrollState: 2
I/System.out: Page: 1 ScrollState: 0
I/System.out: Page: 1 ScrollState: 1
I/System.out: Page: 1 ScrollState: 2
I/System.out: Page: 2 ScrollState: 2
I/System.out: Page: 2 ScrollState: 0

I'm only interested when:

Current Code

This is how I'm currently observing:

Disposable d = ObservableCombineLatest.combineLatest(pageSelectObs, scrollStateObs, new BiFunction<Integer, Integer, Integer>() {
    @Override
    public Integer apply(@NonNull Integer pageSelected, @NonNull Integer scrollState) throws Exception {
        AUtils.logSystem(TAG, "Page: %s ScrollState: %s", pageSelected, scrollState);

        if (adapter.isLastVisibleItem(pageSelected) && adapter.hasHiddenItemsRight() && scrollState == 0) {
            return 1;
        }

        if (adapter.isFirstVisibleItem(pageSelected) && adapter.hasHiddenItemsLeft() && scrollState == 0) {
            return -1;
        }
        return 0;
    }
}).subscribe(new Consumer<Integer>() {
    @Override
    public void accept(@NonNull Integer doAction) throws Exception {
        if (doAction == -1) {
            AUtils.logSystem(TAG, "shift LEFT");
            adapter.shiftLeft();
        }
        if (doAction == 1) {
            AUtils.logSystem(TAG, "shift RIGHT");
            adapter.shiftRight();
        }
    }
});

Is there a simpler way of doing the above?

Upvotes: 0

Views: 837

Answers (2)

Lamorak
Lamorak

Reputation: 11137

Since your conditions are quite simple, you can express them with a simple filter() operator.

Observable<Integer> scrollStateObs = RxViewPager.pageScrollStateChanges(mPlaceImageViewPager)
        .filter(scrollState -> scrollState == ViewPager.SCROLL_STATE_IDLE);

In order to only react on scrollState changes, you can use withLatestFrom() operator

Disposable d = pageSelectObs.withLatestFrom(scrollStateObs, (pageSelected, scrollState) -> pageSelected)
        .filter(pageSelected -> adapter.isLastVisibleItem(pageSelected));
        .subscribe(pageSelected -> {
            if (adapter.hasHiddenItemsRight()) {
                adapter.shiftRight();
            } else if (adapter.hasHiddenItemsLeft()) {
                adapter.shiftRight();
            }
        });

Upvotes: 1

Sergej Isbrecht
Sergej Isbrecht

Reputation: 4012

Well 'the most efficient way' depends on your requirment and how you define most efficient. Is it time, is it resources?

I took your code and added a rate-limited-window of 50 msec, that bursty events would not call onNext too often.

In the MAP-opreator you would add some matching to the enum, because -1 and 1 are not representative values.

@Test
void name() throws Exception {
    Observable<Integer> pageSelectObs = Observable.just(0, 0, 1, 1, 1, 1, 2, 2);
    Observable<Integer> scrollStateObs = Observable.just(1, 2, 2, 0, 1, 2, 2, 0);

    // Test-Obs
    Observable<ShiftOperation> filter = Observable.combineLatest(pageSelectObs, scrollStateObs, Combined::new)
            .window(50, TimeUnit.MILLISECONDS)
            .flatMap(combinedObservable -> combinedObservable.filter(combined -> combined.scrollState == 0)
                    .takeLast(1))
            .map(combined -> {
                // do mapping here

                return ShiftOperation.SHIFT_LEFT; // do your adapter... check here and decide which operation you want to return.
            });

    filter.test()
            .await()
            .assertValueCount(1);
}

Datastructures:

class Combined {
    final int pageState;
    final int scrollState;

    Combined(int pageState, int scrollState) {
        this.pageState = pageState;
        this.scrollState = scrollState;
    }
}

enum ShiftOperation {
    SHIFT_LEFT,
    SHIFT_RIGHT
}

Upvotes: 0

Related Questions