AutoLoadingRecyclerView
and AutoLoadingRecyclerViewAdapter
. That is, just like this, this solution cannot be inserted into already written code. We'll have to work a little. And this, of course, somewhat ties hands in the future.AutoLoadingRecyclerView
you must explicitly call the methods setLimit
, setLoadingObservable
, startLoading
. And this is in addition to the standard methods for RecyclerView
, such as setAdapter
, setLayoutManager
and others. Also in my head you need to keep that the startLoading
method must be called last. Yes, all these methods are marked with comments, how and in what order they should be called, but this is not very intuitive, and you can easily get confused.AutoLoadingRecyclerView
. A brief summary of it is as follows:PublishSubject
with a RecyclerView.OnScrollListener
, and which accordingly "emit" certain elements when an event occurs (when the user has twisted to a certain position).Subscriber
that listens on the above-mentioned PublishSubject
, and when an item arrives to it from the PublishSubject
, it unsubscribes from it and calls the special Observable
responsible for uploading new items.Observable
that loads new items, updates the list, and then reconnects Subscriber
to the PublishSubject
to PublishSubject
for the scrolling list.PublishSubject
, which is generally recommended for use in exceptional situations and which somewhat breaks the whole concept of RxJava. As a result, we obtain several “crutch reactivity”.PublishSubject
, and for the place of it we will create Observable
, which will “emit” when a given condition occurs, that is, when the user scrolls to a certain position.Observable
(for simplicity, we will call it scrollObservable
) will be as follows: private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); }
RecyclerView recyclerView
is our search list :)int limit
- the number of loaded items at a time. I added this parameter here for the convenience of determining the "position X", after which the Observable
begins to "emit". Position is determined by this expression: int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2);
int emptyListCount
is a more interesting parameter. Remember, I said that in the previous version, after the most recent initialization, you need to call the startLoading
method for the initial load. So now, if the list is empty and it is not scrolled, then scrollObservable
will automatically “emit” the first element, which is the starting point for the start of pagination: if (recyclerView.getAdapter().getItemCount() == 0) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); }
emptyListCount
parameter helps emptyListCount
. int emptyListCount = 1; if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); }
scrollObservable
"emit" number equal to the number of items in the list. This number is also a shift (or “offset”). subscriber.onNext(recyclerView.getAdapter().getItemCount());
scrollObservable
starts to "emit" elements en masse. We need only one “emit” with the changed “offset”. Therefore, we add the distinctUntilChanged()
operator, which cuts off all repeating elements. getScrollObservable(recyclerView, limit, emptyListCount) .distinctUntilChanged();
getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged();
PagingListener
interface, implementing which, the developer sets the Observable
, which is responsible for loading data: public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
Observable
accomplished using the switchMap
operator. Also remember that it is desirable to load data in a non-UI stream. getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(pagingListener::onNextPage);
Observable
already in the fragment or activation, where the developer decides how to deal with newly uploaded data. Or they are immediately in the list, or filtered, and only then the list. The great thing is that we can easily upgrade the Observable
way we want. In this, of course, RxJava is wonderful, and the Subject
that was in the last article is not an assistant.retry(long count)
operator retry()
I avoid the retry()
operator because of the possibility of a hangup if the error is not short-lived). Then: getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(pagingListener::onNextPage) .retry(3);
retry(long count)
in case of an error re-subscribes Subscriber
to the Observable
, and we again "listen" to the scrolling list. And the list has reached the end, so a second request does not occur. It is treated only by “twitching” the list to make scrolling work. But this, of course, is not correct. int startNumberOfRetryAttempt = 0; getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)) private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); }
retryCount
parameter retryCount
set by the developer. This is the maximum number of repeated requests in case of an error. That is, this is not the maximum number of attempts for all requests, but the maximum - only for a specific request.getPagingObservable
method?Observable<List> observable onErrorResumeNext , Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
to the Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Observable<List> observable onErrorResumeNext
, Observable
. . retryCount
:
if (numberOfAttemptToRetry < retryCount) {
, :
int attemptToRetryInc = numberOfAttemptToRetry + 1;
, , listener.onNextPage(offset)
:
return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount);
, Observable
:
return Observable.empty();
PaginationTool
.
PaginationTool /** * @author e.matsyuk */ public class PaginationTool { // for first start of items loading then on RecyclerView there are not items and no scrolling private static final int EMPTY_LIST_ITEMS_COUNT = 0; // default limit for requests private static final int DEFAULT_LIMIT = 50; // default max attempts to retry loading request private static final int MAX_ATTEMPTS_TO_RETRY_LOADING = 3; public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener) { return paging(recyclerView, pagingListener, DEFAULT_LIMIT, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit) { return paging(recyclerView, pagingListener, limit, EMPTY_LIST_ITEMS_COUNT, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount) { return paging(recyclerView, pagingListener, limit, emptyListCount, MAX_ATTEMPTS_TO_RETRY_LOADING); } public static <T> Observable<List<T>> paging(RecyclerView recyclerView, PagingListener<T> pagingListener, int limit, int emptyListCount, int retryCount) { if (recyclerView == null) { throw new PagingException("null recyclerView"); } if (recyclerView.getAdapter() == null) { throw new PagingException("null recyclerView adapter"); } if (limit <= 0) { throw new PagingException("limit must be greater then 0"); } if (emptyListCount < 0) { throw new PagingException("emptyListCount must be not less then 0"); } if (retryCount < 0) { throw new PagingException("retryCount must be not less then 0"); } int startNumberOfRetryAttempt = 0; return getScrollObservable(recyclerView, limit, emptyListCount) .subscribeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .observeOn(Schedulers.from(BackgroundExecutor.getSafeBackgroundExecutor())) .switchMap(offset -> getPagingObservable(pagingListener, pagingListener.onNextPage(offset), startNumberOfRetryAttempt, offset, retryCount)); } private static Observable<Integer> getScrollObservable(RecyclerView recyclerView, int limit, int emptyListCount) { return Observable.create(subscriber -> { final RecyclerView.OnScrollListener sl = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!subscriber.isUnsubscribed()) { int position = getLastVisibleItemPosition(recyclerView); int updatePosition = recyclerView.getAdapter().getItemCount() - 1 - (limit / 2); if (position >= updatePosition) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } } } }; recyclerView.addOnScrollListener(sl); subscriber.add(Subscriptions.create(() -> recyclerView.removeOnScrollListener(sl))); if (recyclerView.getAdapter().getItemCount() == emptyListCount) { subscriber.onNext(recyclerView.getAdapter().getItemCount()); } }); } private static int getLastVisibleItemPosition(RecyclerView recyclerView) { Class recyclerViewLMClass = recyclerView.getLayoutManager().getClass(); if (recyclerViewLMClass == LinearLayoutManager.class || LinearLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager(); return linearLayoutManager.findLastVisibleItemPosition(); } else if (recyclerViewLMClass == StaggeredGridLayoutManager.class || StaggeredGridLayoutManager.class.isAssignableFrom(recyclerViewLMClass)) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager)recyclerView.getLayoutManager(); int[] into = staggeredGridLayoutManager.findLastVisibleItemPositions(null); List<Integer> intoList = new ArrayList<>(); for (int i : into) { intoList.add(i); } return Collections.max(intoList); } throw new PagingException("Unknown LayoutManager class: " + recyclerViewLMClass.toString()); } private static <T> Observable<List<T>> getPagingObservable(PagingListener<T> listener, Observable<List<T>> observable, int numberOfAttemptToRetry, int offset, int retryCount) { return observable.onErrorResumeNext(throwable -> { // retry to load new data portion if error occurred if (numberOfAttemptToRetry < retryCount) { int attemptToRetryInc = numberOfAttemptToRetry + 1; return getPagingObservable(listener, listener.onNextPage(offset), attemptToRetryInc, offset, retryCount); } else { return Observable.empty(); } }); } }
PagingException /** * @author e.matsyuk */ public class PagingException extends RuntimeException { public PagingException(String detailMessage) { super(detailMessage); } }
PagingListener /** * @author e.matsyuk */ public interface PagingListener<T> { Observable<List<T>> onNextPage(int offset); }
PaginationFragment /** * A placeholder fragment containing a simple view. */ public class PaginationFragment extends Fragment { private final static int LIMIT = 50; private PagingRecyclerViewAdapter recyclerViewAdapter; private Subscription pagingSubscription; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fmt_pagination, container, false); setRetainInstance(true); init(rootView, savedInstanceState); return rootView; } @Override public void onResume() { super.onResume(); } private void init(View view, Bundle savedInstanceState) { RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.RecyclerView); GridLayoutManager recyclerViewLayoutManager = new GridLayoutManager(getActivity(), 1); recyclerViewLayoutManager.supportsPredictiveItemAnimations(); // init adapter for the first time if (savedInstanceState == null) { recyclerViewAdapter = new PagingRecyclerViewAdapter(); recyclerViewAdapter.setHasStableIds(true); } recyclerView.setLayoutManager(recyclerViewLayoutManager); recyclerView.setAdapter(recyclerViewAdapter); // if all items was loaded we don't need Pagination if (recyclerViewAdapter.isAllItemsLoaded()) { return; } // RecyclerView pagination pagingSubscription = PaginationTool .paging(recyclerView, offset -> EmulateResponseManager.getInstance().getEmulateResponse(offset, LIMIT), LIMIT) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<List<Item>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<Item> items) { recyclerViewAdapter.addNewItems(items); recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.getItemCount() - items.size()); } }); } @Override public void onDestroyView() { if (pagingSubscription != null && !pagingSubscription.isUnsubscribed()) { pagingSubscription.unsubscribe(); } super.onDestroyView(); } }
PagingRecyclerViewAdapter /** * @author e.matsyuk */ public class PagingRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int MAIN_VIEW = 0; private List<Item> listElements = new ArrayList<>(); // after reorientation test this member // or one extra request will be sent after each reorientation private boolean allItemsLoaded; static class MainViewHolder extends RecyclerView.ViewHolder { TextView textView; public MainViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.text); } } public void addNewItems(List<Item> items) { if (items.size() == 0) { allItemsLoaded = true; return; } listElements.addAll(items); } public boolean isAllItemsLoaded() { return allItemsLoaded; } @Override public long getItemId(int position) { return getItem(position).getId(); } public Item getItem(int position) { return listElements.get(position); } @Override public int getItemCount() { return listElements.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == MAIN_VIEW) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false); return new MainViewHolder(v); } return null; } @Override public int getItemViewType(int position) { return MAIN_VIEW; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (getItemViewType(position)) { case MAIN_VIEW: onBindTextHolder(holder, position); break; } } private void onBindTextHolder(RecyclerView.ViewHolder holder, int position) { MainViewHolder mainHolder = (MainViewHolder) holder; mainHolder.textView.setText(getItem(position).getItemStr()); } }
GitHub .
! , , , .
Source: https://habr.com/ru/post/271875/
All Articles