package com.kickstarter.libs;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import com.jakewharton.rxbinding.support.v7.widget.RxRecyclerView;
import rx.Subscription;
import rx.functions.Action0;
public final class RecyclerViewPaginator {
private final @NonNull RecyclerView recyclerView;
private final @NonNull Action0 nextPage;
private Subscription subscription;
private static final int DIRECTION_DOWN = 1;
public RecyclerViewPaginator(final @NonNull RecyclerView recyclerView, final @NonNull Action0 nextPage) {
this.recyclerView = recyclerView;
this.nextPage = nextPage;
start();
}
/**
* Begin listening to the recycler view scroll events to determine
* when pagination should happen.
*/
public void start() {
stop();
subscription = RxRecyclerView.scrollEvents(recyclerView)
.filter(__ -> recyclerView.canScrollVertically(DIRECTION_DOWN))
.map(__ -> recyclerView.getLayoutManager())
.ofType(LinearLayoutManager.class)
.map(this::displayedItemFromLinearLayout)
.filter(item -> item.second != 0)
.filter(this::visibleItemIsCloseToBottom)
// NB: We think this operation is suffering from back pressure problems due to the volume of scroll events:
// https://rink.hockeyapp.net/manage/apps/239008/crash_reasons/88318986
// If it continues to happen we can also try `debounce`.
.onBackpressureDrop()
.distinctUntilChanged()
.subscribe(__ -> nextPage.call());
}
/**
* Stop listening to recycler view scroll events and discard the
* associated resources. This should be done when the object that
* created `this` is released.
*/
public void stop() {
if (subscription != null) {
subscription.unsubscribe();
subscription = null;
}
}
/**
* Returns a (visibleItem, totalItemCount) pair given a linear layout manager.
*/
private @NonNull Pair<Integer, Integer> displayedItemFromLinearLayout(final @NonNull LinearLayoutManager manager) {
return new Pair<>(manager.findLastVisibleItemPosition(), manager.getItemCount());
}
private boolean visibleItemIsCloseToBottom(final @NonNull Pair<Integer, Integer> visibleItemOfTotal) {
return visibleItemOfTotal.first == visibleItemOfTotal.second - 1;
}
}