Skip to content

Instantly share code, notes, and snippets.

@polbins
Last active January 9, 2019 10:02
Show Gist options
  • Save polbins/b6830d392e5c687c8e77 to your computer and use it in GitHub Desktop.
Save polbins/b6830d392e5c687c8e77 to your computer and use it in GitHub Desktop.
Android Endless Scroll using RecyclerView

Endless Scroll Listener for Recycler Views

Endless Scrolling for Paginated APIs as shown in : Google Design - Progress Activity Behavior which has the following components:

  1. Endless Scroll Listener for Recycler Views
  • concrete class implements loadMore(pageNumber) to signal the API to load more data
  1. List Adapter which shows:
    • Empty View when given an empty Item List (TextView)
    • Progress View when we are currently loading (ProgressBar)
    • addAll(...) to append data to the end of the list
    • setIsAppending(boolean) for signaling the start/end of data loading

Resources:

public class EndlessListActivity<D> extends Activity {
private EndlessRecyclerOnScrollListener mEndlessScrollListener;
private EndlessListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RecyclerView listView = (RecyclerView) findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mEndlessScrollListener = new EndlessRecyclerOnScrollListener(mLinearLayoutManager) {
@Override
public void onLoadMore(int current_page) {
// try to get adapter from the recycler view, and cast it appropriately
EndlessListAdapter adapter = (EndlessListAdapter) mListView.getAdapter();
if (adapter != null) {
// signal adapter that loading attempt has started
adapter.setIsAppending(true);
adapter.notifyItemInserted(adapter.getItemCount());
// Call your API Here
loadMore(current_page);
}
}
};
listView.setLayoutManager(linearLayoutManager);
listview.addOnScrollListener(mEndlessScrollListener);
// Do your RecyclerView stuff here
mAdapter = new EndlessListAdapter(...);
}
// When your Data from the API is loaded,
// set the Total Entries on your Endless Scroll Listener
public void onDataLoaded(int serverTotalEntries) {
mEndlessScrollListener.setTotalEntries(serverTotalEntries);
}
// After finishing loading of new data,
// add it to your adapter
public void onLoadMore(List<D> dataList) {
mAdapter.addAll(dataList);
mAdapter.setIsAppending(false);
}
}
public abstract class EndlessListAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private boolean mIsAppending = false;
protected static final int VIEW_TYPE_PROGRESS = 333;
public EndlessListAdapter(List<D> dataList) {
super(dataList);
}
public boolean isAppending() {
return mIsAppending;
}
public void setIsAppending(boolean isAppending) {
mIsAppending = isAppending;
}
@Override
public int getItemCount() {
return isAppending() ?
super.getItemCount() + 1 : super.getItemCount();
}
@Override
public int getItemViewType(int position) {
return (isAppending() && position >= super.getItemCount()) ?
VIEW_TYPE_PROGRESS : super.getItemViewType(position);
}
@Override
public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder vh;
if (viewType == VIEW_TYPE_PROGRESS) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_progress_bar, parent, false);
vh = new ProgressViewHolder(v);
} else {
vh = super.onCreateViewHolder(parent, viewType);
}
return (VH) vh;
}
@Override
public final void onBindViewHolder(VH holder, int position) {
if (holder instanceof ProgressViewHolder) {
// do nothing
} else {
super.onBindViewHolder(holder, position);
}
}
public static class ProgressViewHolder extends RecyclerView.ViewHolder {
public ProgressViewHolder(View v) {
super(v);
}
}
}
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private boolean mLoading = false; // True if we are still waiting for the last set of data to load
private int previousItemCount = 0; // The total number of items in the dataset after the last load
private int mTotalEntries; // The total number of entries in the server
private int current_page = 1; // Always start at Page 1
private LinearLayoutManager mLinearLayoutManager;
public EndlessRecyclerOnScrollListener(LinearLayoutManager linearLayoutManager) {
mLinearLayoutManager = linearLayoutManager;
}
// Concrete classes should implement the Loading of more data entries
public abstract void onLoadMore(int current_page);
public void setTotalEntries(int totalEntries) {
mTotalEntries = totalEntries;
}
// when you're RecyclerView supports refreshing, also refresh the count
public void refresh() {
current_page = 1;
previousItemCount = 0;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = recyclerView.getChildCount();
int totalItemCount = mLinearLayoutManager.getItemCount();
int firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
if (mLoading) {
int diffCurrentFromPrevious = totalItemCount - previousItemCount;
// check if current total is greater than previous (diff should be greater than 1, for considering placeholder)
// and if current total is equal to the total in server
if ((diffCurrentFromPrevious > 1) ||
totalItemCount >= mTotalEntries) {
mLoading = false;
previousItemCount = totalItemCount;
}
} else {
if (totalItemCount >= mTotalEntries) {
// do nothing, we've reached the end of the list
} else {
// check if the we've reached the end of the list,
// and if the total items is less than the total items in the server
if ((firstVisibleItem + visibleItemCount) >= totalItemCount &&
totalItemCount < mTotalEntries) {
onLoadMore(++current_page);
mLoading = true;
previousItemCount = totalItemCount;
}
}
}
}
}
public abstract class ListAdapter<D, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
protected List<D> mDataList;
protected static final int VIEW_TYPE_NORMAL = 0;
protected static final int VIEW_TYPE_EMPTY = 111;
public ListAdapter(List<D> dataList) {
mDataList = dataList;
}
@Override
public int getItemCount() {
return mDataList.isEmpty() ? 1 : mDataList.size();
}
@Override
public int getItemViewType(int position) {
if (mDataList.isEmpty()) {
return VIEW_TYPE_EMPTY;
} else {
return VIEW_TYPE_NORMAL;
}
}
public void clear() {
int size = mDataList.size();
mDataList.clear();
notifyItemRangeRemoved(0, size);
}
public void addAll(List<D> data) {
mDataList.addAll(data);
notifyDataSetChanged();
}
protected D getItemAt(int position) {
return mDataList.get(position);
}
protected D replaceItemAt(int position, D newItem) {
D item = mDataList.set(position, newItem);
notifyItemChanged(position);
return item;
}
protected void addItem(int index, D newItem) {
mDataList.add(index, newItem);
notifyItemInserted(index);
}
protected void addItem(D newItem) {
mDataList.add(newItem);
notifyItemInserted(mDataList.size() - 1);
}
protected D removeItem(int index) {
if (mDataList.size() > 0) {
D item = mDataList.remove(index);
notifyItemRemoved(index);
return item;
}
return null;
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder vh;
if (viewType == VIEW_TYPE_EMPTY) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_empty_view, parent, false);
vh = new EmptyViewHolder(v);
} else {
vh = createNormalViewHolder(parent, viewType);
}
return (VH) vh;
}
@Override
public void onBindViewHolder(VH holder, int position) {
if (holder instanceof EmptyViewHolder) {
// do nothing
} else {
// Prevent binding of child view holder when List is NULL/Empty
if (mDataList != null && !mDataList.isEmpty()) {
bindNormalViewHolder(holder, position);
}
}
}
protected abstract VH createNormalViewHolder(ViewGroup parent, int viewType);
protected abstract void bindNormalViewHolder(VH holder, int position);
public static class EmptyViewHolder extends RecyclerView.ViewHolder {
public EmptyViewHolder(View v) {
super(v);
}
}
}
@guidedways
Copy link

Did you forget to extend EndlessListAdapter from ListAdapter? The EndlessListAdapter is taking in a List dataList - D is not defined.

@ibraizQazi
Copy link

I'm getting that abstract methods cannot be accessed and stuff...
@guidedways did you solve it? if not and did it some other way then help me out...

@ibraizQazi
Copy link

I tried extending it from ListAdapter and then overriding the abstract methods but endlessrecycleronscrolllistener didn't invoke the onLoadMore() method...help required

@aistomin
Copy link

Does that make sense to publish the code which you didn't try yourself? This code obviously can not be compiled because of typos, missing methods etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment