Android RecyclerView dynamically load more items when scroll to end with bottom ProgressBar

    Load more data when scrolling to bottom of list view/table view is one the most popular design style which available in a lot of application, for example: Facebook, Google+,... By my previous post, you've learn the way to do this trick with ListView. As you are already known, RecyclerView is a new component introduced in Android Lollipop, this component increases performances respect to ListView. Moreover, respect to ListView, RecyclerView is much more customizable.
    Today, with this post, I would like to talk about making an endless RecyclerView and when data is loading, it will show a ProgressBar at the bottom.

Adding dependencies to project

    The first work after creating a new Android Studio project is adding RecyclerView denpendency to your app level build.gradle. In this sample project, I set each RecyclerView item as a CardView, so you must add it's dependency, too:
dependencies {
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.android.support:recyclerview-v7:25.1.0'
    compile 'com.android.support:cardview-v7:25.1.0'
}

Create layout files

    Now we must provide some necessary XML files to define layouts. Because of we will set a ProgressBar at the bottom of RecyclerView when data is loading, it will have two item types. The normal item that to show info of user and loading item that place at bottom to show progress bar.
    Layout for the normal item:
item_recycler_view_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card_view:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:selectableItemBackground"
        android:padding="10dp">

        <TextView
            android:id="@+id/txt_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/txt_email"
            android:textColor="@android:color/black"
            android:textSize="12sp" />
    </RelativeLayout>
</android.support.v7.widget.CardView>
    Layout for the loading item:
item_loading.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>
    And you must put a RecyclerView object to the main activity:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

Create a POJO class

    We've just create all necessary layouts for this project, now please move to programmatically code. Firstly, provide a POJO class (model object) simply like this:
Contact.java
package info.devexchanges.endlessrecyclerview;

public class Contact {
    private String email;
    private String phone;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

Define an interface for callbacks

    Now, make an interface named OnLoadMoreListener with an abstract method onLoadMore() which will be invoked when we scroll the RecyclerView to end:
OnLoadMoreListener.java
package info.devexchanges.endlessrecyclerview;

public interface OnLoadMoreListener {
    void onLoadMore();
}

Configuration in RecyclerView adapter

    In this RecyclerView adapter (I named as ContactAdapter), we have 2 item types then must create two ViewHolder like below:
// "Loading item" ViewHolder
private class LoadingViewHolder extends RecyclerView.ViewHolder {
        public ProgressBar progressBar;

        public LoadingViewHolder(View view) {
            super(view);
            progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
        }
    }

// "Normal item" ViewHolder
private class UserViewHolder extends RecyclerView.ViewHolder {
        public TextView phone;
        public TextView email;

        public UserViewHolder(View view) {
            super(view);
            phone = (TextView) view.findViewById(R.id.txt_phone);
            email = (TextView) view.findViewById(R.id.txt_email);
        }
    }
In this adapter, declare two constants that is delegate for two item type of RecyclerView:
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
Provide an OnLoadMoreListener variable and set an "add method":
private OnLoadMoreListener onLoadMoreListener;
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
        this.onLoadMoreListener = mOnLoadMoreListener;
    }
In the constructor of this adapter class, we handle scroll event of the RecyclerView here. This is the most important step, firstly, you must get LayoutManager of RecyclerView, detecting scroll to bottom in onScroll():
    private boolean isLoading;
    private Activity activity;
    private List<Contact> contacts;
    private int visibleThreshold = 5;
    private int lastVisibleItem, totalItemCount;

    public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) {
        this.contacts = contacts;
        this.activity = activity;

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    isLoading = true;
                }
            }
        });
    }
Add some necessary methods to complete ContactAdapter:
    @Override
    public int getItemViewType(int position) {
        return contacts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(activity).inflate(R.layout.item_recycler_view_row, parent, false);
            return new UserViewHolder(view);
        } else if (viewType == VIEW_TYPE_LOADING) {
            View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false);
            return new LoadingViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof UserViewHolder) {
            Contact contact = contacts.get(position);
            UserViewHolder userViewHolder = (UserViewHolder) holder;
            userViewHolder.phone.setText(contact.getEmail());
            userViewHolder.email.setText(contact.getPhone());
        } else if (holder instanceof LoadingViewHolder) {
            LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
            loadingViewHolder.progressBar.setIndeterminate(true);
        }
    }

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

    public void setLoaded() {
        isLoading = false;
    }

Activity programmatically code

    In onCreate() method of activity, we must call setOnLoadMoreListener() and get new data inside onLoadMore(). This is full code of the activity:
MainActivity.java
package info.devexchanges.endlessrecyclerview;

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

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private List<Contact> contacts;
    private ContactAdapter contactAdapter;
    private Random random;

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

        contacts = new ArrayList<>();
        random = new Random();

        //set dummy data
        for (int i = 0; i < 10; i++) {
            Contact contact = new Contact();
            contact.setPhone(phoneNumberGenerating());
            contact.setEmail("DevExchanges" + i + "@gmail.com");
            contacts.add(contact);
        }

        //find view by id and attaching adapter for the RecyclerView
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        contactAdapter = new ContactAdapter(recyclerView, contacts, this);
        recyclerView.setAdapter(contactAdapter);

        //set load more listener for the RecyclerView adapter
        contactAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                if (contacts.size() <= 20) {
                    contacts.add(null);
                    contactAdapter.notifyItemInserted(contacts.size() - 1);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            contacts.remove(contacts.size() - 1);
                            contactAdapter.notifyItemRemoved(contacts.size());

                            //Generating more data
                            int index = contacts.size();
                            int end = index + 10;
                            for (int i = index; i < end; i++) {
                                Contact contact = new Contact();
                                contact.setPhone(phoneNumberGenerating());
                                contact.setEmail("DevExchanges" + i + "@gmail.com");
                                contacts.add(contact);
                            }
                            contactAdapter.notifyDataSetChanged();
                            contactAdapter.setLoaded();
                        }
                    }, 5000);
                } else {
                    Toast.makeText(MainActivity.this, "Loading data completed", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private String phoneNumberGenerating() {
        int low = 100000000;
        int high = 999999999;
        int randomNumber = random.nextInt(high - low) + low;

        return "0" + randomNumber;
    }
}

Running application

    And this is output for our code:

Conclusions

    Over here, I've just present the way to put a progress bar at the bottom of RecyclerView and loading more data when scroll to end. This design is very popular in mobile application development, so I hope this post is helpful with your own work. Further, you can visit my previous post to learn solution with ListView or read related tutorial posts on other site like:

Share


Previous post
« Prev Post
Next post
Next Post »