Tutorial

Android RecyclerView Load More, Endless Scrolling

Published on August 3, 2022
author

Anupam Chugh

Android RecyclerView Load More, Endless Scrolling

In this tutorial, we’ll be discussing and implementing Endless Scrolling or Infinite Scroll on RecyclerView in our Android Application. The infinite scrolling in which the next set of rows are fetched from the DB/Server while showing a loading icon is commonly seen in many applications such as Facebook, Twitter. It’s recommended to go through this tutorial before proceeding ahead.

Android RecyclerView Load More

In order to show Loading icon at the bottom of RecyclerView while the next set of items are fetched, we need to use Multiple View Types in our RecyclerView Adapter. How is this implemented? Typically in a simple RecyclerView, we load elements to the adapter from a Data Structure. In order to show the loading icon view at the bottom of the RecyclerView, we need to first add a NULL element to the end of the Data Structure. Why NULL? In order to differentiate that element from the rest of the elements and show a different view type row. After adding a null, we notify the adapter the of the new element and fetch the next set of elements. Once the next set of elements is obtained, we remove the NULL element and add the next set to the bottom of the Data Structure. Following diagram demonstrates what actually happens in the RecyclerView and its Adapter. android recyclerview loadmore flow

In order to detect that the user has scrolled to the end of the RecyclerView, we need to implement OnScrollListener() on the RecyclerView.

Enough Talk. Let’s code. In the following section, we’ll demonstrate Endless Scrolling on RecyclerView by populating a List of Strings and loading the next set of List after a delay using Handlers.

Project Structure

android recyclerview load more project structure

Code

The code for the activity_main.xml layout is given below:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

The layout for the rows of the RecyclerView is defined in item_row.xml file as shown below:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardElevation="8dp"
    app:cardUseCompatPadding="true">


    <TextView
        android:id="@+id/tvItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="Item X" />

</android.support.v7.widget.CardView>

The layout for the loading view is defined in the item_loading.xml file as shown below:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:indeterminate="true"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        />

</LinearLayout>

The code for the RecyclerViewAdapter.java class is given below:

package com.journaldev.androidrecyclerviewloadmore;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final int VIEW_TYPE_ITEM = 0;
    private final int VIEW_TYPE_LOADING = 1;

    public List<String> mItemList;


    public RecyclerViewAdapter(List<String> itemList) {

        mItemList = itemList;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row, parent, false);
            return new ItemViewHolder(view);
        } else {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false);
            return new LoadingViewHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {

        if (viewHolder instanceof ItemViewHolder) {

            populateItemRows((ItemViewHolder) viewHolder, position);
        } else if (viewHolder instanceof LoadingViewHolder) {
            showLoadingView((LoadingViewHolder) viewHolder, position);
        }

    }

    @Override
    public int getItemCount() {
        return mItemList == null ? 0 : mItemList.size();
    }

    /**
     * The following method decides the type of ViewHolder to display in the RecyclerView
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        return mItemList.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }


    private class ItemViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        public ItemViewHolder(@NonNull View itemView) {
            super(itemView);

            tvItem = itemView.findViewById(R.id.tvItem);
        }
    }

    private class LoadingViewHolder extends RecyclerView.ViewHolder {

        ProgressBar progressBar;

        public LoadingViewHolder(@NonNull View itemView) {
            super(itemView);
            progressBar = itemView.findViewById(R.id.progressBar);
        }
    }

    private void showLoadingView(LoadingViewHolder viewHolder, int position) {
        //ProgressBar would be displayed

    }

    private void populateItemRows(ItemViewHolder viewHolder, int position) {

        String item = mItemList.get(position);
        viewHolder.tvItem.setText(item);

    }


}

getItemViewType is where we check each element of the List. If the element is NULL we set the view type as 1 else 0. Based on the View type we instantiate the ViewHolder in the onCreateViewHolder. Inside the onBindViewHolder we check the type of ViewHolder instance and populate the row accordingly. Let’s look at the MainActivity.java class where we instantiate the above Adapter.

package com.journaldev.androidrecyclerviewloadmore;

import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    RecyclerViewAdapter recyclerViewAdapter;
    ArrayList<String> rowsArrayList = new ArrayList<>();

    boolean isLoading = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recyclerView);
        populateData();
        initAdapter();
        initScrollListener();


    }

    private void populateData() {
        int i = 0;
        while (i < 10) {
            rowsArrayList.add("Item " + i);
            i++;
        }
    }

    private void initAdapter() {

        recyclerViewAdapter = new RecyclerViewAdapter(rowsArrayList);
        recyclerView.setAdapter(recyclerViewAdapter);
    }

    private void initScrollListener() {
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

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

                LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

                if (!isLoading) {
                    if (linearLayoutManager != null && linearLayoutManager.findLastCompletelyVisibleItemPosition() == rowsArrayList.size() - 1) {
                        //bottom of list!
                        loadMore();
                        isLoading = true;
                    }
                }
            }
        });


    }

    private void loadMore() {
        rowsArrayList.add(null);
        recyclerViewAdapter.notifyItemInserted(rowsArrayList.size() - 1);


        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                rowsArrayList.remove(rowsArrayList.size() - 1);
                int scrollPosition = rowsArrayList.size();
                recyclerViewAdapter.notifyItemRemoved(scrollPosition);
                int currentSize = scrollPosition;
                int nextLimit = currentSize + 10;

                while (currentSize - 1 < nextLimit) {
                    rowsArrayList.add("Item " + currentSize);
                    currentSize++;
                }

                recyclerViewAdapter.notifyDataSetChanged();
                isLoading = false;
            }
        }, 2000);


    }
}

initScrollListener is the most important method in the above code. It’s there where we check the scrolled state of the RecyclerView and if the bottom-most item is visible we show the loading view and populate the next list. The output of the above application in action is given below: That brings an end to this tutorial. You can download the project from the link below:

AndroidRecyclerViewLoadMore

Github Project Link

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Anupam Chugh

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
JournalDev
DigitalOcean Employee
DigitalOcean Employee badge
November 2, 2020

Thanks a lot, it’s working and great decision.

- Eugene

    JournalDev
    DigitalOcean Employee
    DigitalOcean Employee badge
    October 21, 2020

    Simple and easy to go through.

    - devraj singh

      JournalDev
      DigitalOcean Employee
      DigitalOcean Employee badge
      October 2, 2020

      HI, I am using volley, and when requesting data, I am consulting 5 items, but the recycler only shows 4, why is this error?

      - Paul

        JournalDev
        DigitalOcean Employee
        DigitalOcean Employee badge
        July 31, 2020

        why the linearLayoutManager.findLastCompletelyVisibleItemPosition() equals with -1 ?

        - mahdi

          JournalDev
          DigitalOcean Employee
          DigitalOcean Employee badge
          April 18, 2020

          How to pupulateData() from Model Arraylist ?

          - Redigo

            JournalDev
            DigitalOcean Employee
            DigitalOcean Employee badge
            November 6, 2019

            I stopped reading at this line: <android.support.v7.widget.RecyclerView android:id=“@+id/recyclerView” android:layout_width=“match_parent” android:layout_height=“wrap_content” <- will trigger lot of drawings

            - duna

              JournalDev
              DigitalOcean Employee
              DigitalOcean Employee badge
              October 6, 2019

              nice example. It gives me an error saying “cannot call this method in a scroll callback” but it is fixed by calling recyclerView.post() method. Just a note.

              - Numan

                JournalDev
                DigitalOcean Employee
                DigitalOcean Employee badge
                March 15, 2019

                What if I use grid layout manager and I want the load more to Cover full length?

                - Carlos

                  JournalDev
                  DigitalOcean Employee
                  DigitalOcean Employee badge
                  March 12, 2019

                  All Code is Good it worked also…But use for loop instead of while loop because app is getting struck when we break the while loop with condition and in my real time also after some time app is freezed… Just use for loop instead of while in whole code then it will be a excellent tutorial example…

                  - Krishna Kamesh

                    JournalDev
                    DigitalOcean Employee
                    DigitalOcean Employee badge
                    January 22, 2019

                    Can you make this article in kotlin version?

                    - Ade Fajar

                      Try DigitalOcean for free

                      Click below to sign up and get $200 of credit to try our products over 60 days!

                      Sign up

                      Join the Tech Talk
                      Success! Thank you! Please check your email for further details.

                      Please complete your information!

                      Featured on Community

                      Get our biweekly newsletter

                      Sign up for Infrastructure as a Newsletter.

                      Hollie's Hub for Good

                      Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

                      Become a contributor

                      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

                      Welcome to the developer cloud

                      DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

                      Learn more