Skip to content

Instantly share code, notes, and snippets.

@erseno
Last active April 21, 2020 07:55
Show Gist options
  • Save erseno/c26b6ff2a6cc483b8bbf to your computer and use it in GitHub Desktop.
Save erseno/c26b6ff2a6cc483b8bbf to your computer and use it in GitHub Desktop.
A base adapter for RecyclerView to handle the loading of more data once a scroll threshold has been reached. Also shows a progress bar as a footer view to show that the next page is loading. If the next page fails to load, a retry button is displayed along with an error message so that the page can be retried.
/**
* Created by Ersen on 12/18/2015.
*
* This is used by the PagedRecyclerViewAdapter as it takes in Items of Pageable.
* This is so that multiple types can be added to the data set such as the the actual data to be displayed along with footer progress items or retry failed page etc.
*/
public interface Pageable {
}
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* Created by Ersen on 12/18/2015.
* <p/>
* This is an adapter which will handle the following:
* 1. Call loadMore once a scroll threshold has been reach and passing the current page
* 2. Show progress bar as a footer to show that the next page is loading
* 3. If the next page fails to load (connection or non 200) show a retry footer view like Google Play Store
* <p/>
* delegate means we let the child class handle the view holder creation / binding in its own class
*/
public abstract class PagedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM_VIEW_TYPE_DELEGATE = 0;
private static final int ITEM_VIEW_TYPE_PROGRESS_FOOTER = 1;
private static final int ITEM_VIEW_TYPE_RETRY_PAGE_FOOTER = 2;
private int mCurrentPage = 1; //Assumes start as page 1
private ArrayList<Pageable> mItems;
private OnLoadMoreListener mOnLoadMoreListener;
private RecyclerView mRecyclerView;
private boolean mHasReachedEnd; //Managed by you, if the next page of results
public PagedRecyclerViewAdapter(RecyclerView recyclerView, ArrayList<Pageable> items, @NonNull OnLoadMoreListener onLoadMoreListener) {
this.mItems = items;
this.mOnLoadMoreListener = onLoadMoreListener;
this.mRecyclerView = recyclerView;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
if (viewType == ITEM_VIEW_TYPE_PROGRESS_FOOTER) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_list_load_more, parent, false);
viewHolder = new ProgressBarViewHolder(view);
} else if (viewType == ITEM_VIEW_TYPE_RETRY_PAGE_FOOTER) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_list_load_more_retry, parent, false);
viewHolder = new RetryNextPageViewHolder(view);
} else {
viewHolder = delegateOnCreateViewHolder(parent, viewType);
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
int viewType = getItemViewType(position);
if (viewType != ITEM_VIEW_TYPE_PROGRESS_FOOTER && viewType != ITEM_VIEW_TYPE_RETRY_PAGE_FOOTER) {
delegateOnBindViewHolder(holder, position);
//I post it in its queue just in case the recycler view is still setting up its layout.
mRecyclerView.post(new Runnable() {
@Override
public void run() {
//If we are binding to a view which is not a footer view and its the second to last item and hasReachedEnd is false, call onLoadMore
if (!mHasReachedEnd && position >= getItemCount() - 1) {
addProgressItem(); //Add a progress view
mOnLoadMoreListener.onLoadMore(mCurrentPage); //Notify we are loading more
}
}
});
}
//Do nothing because there is nothing to bind with progress or retry
}
@Override
public int getItemCount() {
return mItems.size();
}
@Override
public int getItemViewType(int position) {
Pageable pageable = mItems.get(position);
if (pageable instanceof ProgressFooter) {
return ITEM_VIEW_TYPE_PROGRESS_FOOTER;
} else if (pageable instanceof RetryPage) {
return ITEM_VIEW_TYPE_RETRY_PAGE_FOOTER;
} else {
return ITEM_VIEW_TYPE_DELEGATE;
}
}
public final void addProgressItem() {
mItems.add(new ProgressFooter());
notifyItemInserted(mItems.size() - 1);
}
public final void addRetryPageItem() {
removeAnyFooterItem();
Pageable pageable = mItems.get(mItems.size() - 1);
if (!(pageable instanceof RetryPage)) {
mItems.add(new RetryPage());
notifyItemInserted(mItems.size() - 1);
}
}
public final void removeAnyFooterItem() {
if (mItems != null && !mItems.isEmpty()) {
Pageable pageable = mItems.get(mItems.size() - 1);
if (pageable instanceof ProgressFooter || pageable instanceof RetryPage) {
mItems.remove(mItems.size() - 1);
notifyItemRemoved(mItems.size());
}
}
}
public final int getCurrentPage() {
return mCurrentPage;
}
public final void addItems(ArrayList<Pageable> items) {
int positionStart = mItems.size() + 1;
mItems.addAll(items);
notifyItemRangeInserted(positionStart, items.size());
}
public final ArrayList<Pageable> getDataSet() {
return mItems;
}
/**
* Call to increment the current page, note that page incrementing is not done automatically for situations where the request fails
*/
public final void incrementPage() {
mCurrentPage++;
}
public final void setHasReachedEnd() {
mHasReachedEnd = true;
}
public final void reset(){
mHasReachedEnd = false;
mCurrentPage = 1;
mItems.clear();
notifyDataSetChanged();
}
public interface OnLoadMoreListener {
void onLoadMore(int page);
}
public abstract RecyclerView.ViewHolder delegateOnCreateViewHolder(ViewGroup parent, int viewType);
public abstract void delegateOnBindViewHolder(RecyclerView.ViewHolder holder, int position);
public static class ProgressFooter implements Pageable {
}
public static class RetryPage implements Pageable {
}
public static class ProgressBarViewHolder extends RecyclerView.ViewHolder {
public ProgressBarViewHolder(View itemView) {
super(itemView);
}
}
public class RetryNextPageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
View mRetry;
public RetryNextPageViewHolder(View itemView) {
super(itemView);
mRetry = itemView.findViewById(R.id.retry);
mRetry.setOnClickListener(this);
}
@Override
public void onClick(View view) {
removeAnyFooterItem();
mOnLoadMoreListener.onLoadMore(mCurrentPage);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment