Android endless scrolling with RecyclerView

This tutorial will illustrate how to implement infinite scrolling in Android, where the data should be load automatically as the user scrolls down


Baraa Abuzaid
@baraaabuzaid
Android endless scrolling with RecyclerView

One of the most commonly used component in Android is the RecyclerView, there is almost no app that doesn’t contain some kind of list that needs to be displayed. Meanwhile, presenting an endless List (Infinite List) could be quite tricky. And in this context, there is two no! no! you would like to avoid when displaying online data on a list.

First, you don’t want to make a huge request call to the server, making your user wait infinitely. Second, you don’t want to clutter your UI with Next and previous button and make your App look like a web page from the 90’s.

In such a scenario implementing an endless scroll might be the ideal choice. Basically, before the user reaches the end of our recyclerView or a predefined threshold, we want to make a request call to the server and load new contents.

So initially we make a class the extends RecyclerView.OnScrollListener with abstract method onLoadMore() that would be overridden later to implement the request call to the server or to the database. Onscrolled() is a core method and will be called when the user scrolls our RecycledView, perhaps multiple time per second.
The method reset() is for resetting the member variable we are about to define.

public abstract class EndlessScrollEventListener extends RecyclerView.OnScrollListener {

    private LinearLayoutManager mLinearLayoutManager;

    public EndlessScrollEventListener(LinearLayoutManager linearLayoutManager) {
        mLinearLayoutManager = linearLayoutManager;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
    }

    public void reset(){};

    public abstract void onLoadMore(int pageNum);

}

 

So the member variable of our EndlessScrollEventListener is as below

/** is number of items that we could have after our
     *  current scroll position before we start loading
        more items */
    private int visibleThreshold = 5;
    /** to keep track of the page that we would like to
     * retrieve from a server our database
     * */
    private int currentPage = 0;
    /** total number of items that we retrieve lastly*/
    private int previousTotalItemCount = 0;
    /** indicating whether we are loading new dataset or not*/
    private boolean loading = true;
    /** the initial index of the page that'll start from */
    private int startingPageIndex = 0;

    /******* variables we could get from linearLayoutManager *******/

    /** the total number of items that we currently have on our recyclerview and we
     * get it from linearLayoutManager */
    private int totalItemCount;
    
    /** the position of last visible item in our view currently
     * get it from linearLayoutManager */
    private int lastVisibleItemPosition;

 

Moreover, let’s discuss how we could put all these together to get our intended result.
Basically, we have three major cases that we should address in our method  onScrolled().

Firstly,  if (totalItemCount < previousTotalItemCount)
that’s mean we should invalidate our list and rest back to our initial state.

Secondly, if we are loading we go and check whether
(totalItemCount > previousTotalItemCount) if true that means we finished loading a new dataset, then we:
– set loading to false
– set previousTotalItemCount to our new totalItemCount

The third case, we finished loading and
(lastVisibleItemPosition + visibleThreshold) > totalItemCount) that’s mean we have to load new contents as we reached the closest point to the bottom of our list.

 

Now, putting these cases together should be like


    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        totalItemCount = mLinearLayoutManager.getItemCount();
        lastVisibleItemPosition = mLinearLayoutManager.findLastVisibleItemPosition();

        // first case
        if (totalItemCount < previousTotalItemCount) {
            this.currentPage = this.startingPageIndex;
            this.previousTotalItemCount = totalItemCount;
            if (totalItemCount == 0) { this.loading = true; }
        }

        // second case
        if (loading && (totalItemCount > previousTotalItemCount)) {
            loading = false;
            previousTotalItemCount = totalItemCount;
        }

        // third case
        if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
            currentPage++;
            onLoadMore(currentPage, recyclerView);
            loading = true;
        }

    }

So putting it all together should be as below,

import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

public abstract class EndlessScrollEventListener extends RecyclerView.OnScrollListener {

    private LinearLayoutManager mLinearLayoutManager;


    /** is number of items that we could have after our
     *  current scroll position before we start loading
        more items */
    private int visibleThreshold = 5;
    /** to keep track of the page that we would like to
     * retrieve from a server our database
     * */
    private int currentPage = 0;
    /** total number of items that we retrieve lastly*/
    private int previousTotalItemCount = 0;
    /** indicating whether we are loading new dataset or not*/
    private boolean loading = true;
    /** the initial index of the page that'll start from */
    private int startingPageIndex = 0;

    /******* variables we could get from linearLayoutManager *******/

    /** the total number of items that we currently have on our recyclerview and we
     * get it from linearLayoutManager */
    private int totalItemCount;

    /** the position of last visible item in our view currently
     * get it from linearLayoutManager */
    private int lastVisibleItemPosition;

    public EndlessScrollEventListener(LinearLayoutManager linearLayoutManager) {
        mLinearLayoutManager = linearLayoutManager;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        totalItemCount = mLinearLayoutManager.getItemCount();
        lastVisibleItemPosition = mLinearLayoutManager.findLastVisibleItemPosition();

        // first case
        if (totalItemCount < previousTotalItemCount) {
            this.currentPage = this.startingPageIndex;
            this.previousTotalItemCount = totalItemCount;
            if (totalItemCount == 0) { this.loading = true; }
        }

        // second case
        if (loading && (totalItemCount > previousTotalItemCount)) {
            loading = false;
            previousTotalItemCount = totalItemCount;
        }

        // third case
        if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
            currentPage++;
            onLoadMore(currentPage, recyclerView);
            loading = true;
        }

    }

    // should be called if we do filter(search) to our list
    public void reset(){
        this.currentPage = this.startingPageIndex;
        this.previousTotalItemCount = 0;
        this.loading = true;
    }

    // Define the place where we load the dataset
    public abstract void onLoadMore(int pageNum, RecyclerView recyclerView);

}

 

In short, you’ll need to make a basic RecyclerView Adapter, and your activity or fragment should be like

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private MyRecyclerAdapter mAdapter;
    private List<String> mList = new ArrayList<>();
    private EndlessScrollEventListener endlessScrollEventListener;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        recyclerView = findViewById(R.id.recyclerView);
        mAdapter = new MyRecyclerAdapter(this,mList);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setAdapter(mAdapter);
        recyclerView.setLayoutManager(layoutManager);
        
        endlessScrollEventListener = new EndlessScrollEventListener(layoutManager) {
            @Override
            public void onLoadMore(int pageNum, RecyclerView recyclerView) {
                /*Todo: add your request call to load more data from server or database here */
            }
        };

        recyclerView.addOnScrollListener(endlessScrollEventListener);
        
    }
}

 

 

 

Feature Photo by NordWood 

Show Comments (2)

Comments

  • Aung Khant Htoo

    Thank you for very clear way of implementing paging state. I think we should add if(dy < 0) return at the top of onScrolled()

    • Article Author
    • Reply
    • Baraa Abuzaid

      you are welcome Aung,

      • Article Author
      • Reply

Related Articles

Android Core

Moving Map Under Marker Like UBER: Maps SDK for Android

Uber is a nice and features rich App, here in this tutorial we’ll implement a feature on it that is very useful. In particular, moving the map under the marker which is UBER...

Posted on by Baraa Abuzaid
Software Design And Architecture

Why you should use MVP clean architecture in your next android project

Tech industry is moving at a staggering pace. Gone the good old days when a release cycle takes year or more. And we enter a new era. The era of Apps and “rapid release cycle”....

Posted on by Baraa Abuzaid