0

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:

  • ScrollState == 0
  • end of ViewPager is reached

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?

Baker
  • 24,730
  • 11
  • 100
  • 106

2 Answers2

1

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();
            }
        });
Lamorak
  • 10,957
  • 9
  • 43
  • 57
  • Exactly what I was looking for. Thanks. 1st question: When filtering out scroll states except SCROLL_STATE_IDLE, this causes the ViewPager to cut short the SETTLING phase and snaps the next page into view (rather than smoothly settling in). Ideas why this happens with filtered streams but doesn't with my original, crude solution? – Baker Apr 26 '17 at 14:13
  • 2nd question: `withLatestFrom`, the BiFunction simply returns pageSelected and does nothing else, correct? (Using RxJava2 & I'm not using Lambda statements in my code. At times it's unclear to me what's hidden.) – Baker Apr 26 '17 at 14:17
  • 1. I don't really understand 2. Yes, but it doesn't really matter what you return there as it's not used – Lamorak Apr 26 '17 at 14:21
  • My original reason for watching for both events (OnPageSelected & OnPageScrollStateChanged) was to shiftRight/Left only when the ViewPager is IDLE after swipe/pageSelected and not during DRAGGING/SETTLING. Calling notifyDataSetChanged (in `shiftRight()`) would cancel the SETTLING event causing the viewpager page being swiped-in to suddenly snap into frame rather than smoothly settle. I notice the same effect now when `.filter(scrollState -> scrollState == ViewPager.SCROLL_STATE_IDLE)` – Baker Apr 26 '17 at 14:29
  • Thanks Lamorak. "Snapping" problem still happens. I think `withLatestFrom` prevents some scroll state events from emitting. `combineLatest` is OK though. Ideally `pageSelectObs` would only emit when `adapter.isLast/FirstVisibleItem` and `adapter.hasHiddenItemsRight/Left`. And in turn `scrollStateObs` would emit all states if `pageSelectObs` has emitted anything. I'm thinking And/Then/When might be the solution. Anyways, appreciate the help. – Baker Apr 27 '17 at 03:13
0

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
}
Sergej Isbrecht
  • 3,842
  • 18
  • 27