Recyclerview Progress Bar Pagination (RPBP)


RPBP:   SOLD       




Hi dev, have you ever noticed that in the Instagram app there is a circle progress bar when the recyclerview content is scrolled up.


This is a very cool feature and need to be added to your app, if your app is using recyclerview which loads data from server. For better UX, then user needs to be informed that data loading process from server is in progress.

Honestly, the overlay progress dialog with the text  "Loading.. or Please Wait.."  is an old school method that is already outdated. The Android Developer community has found new and much cooler solutions to overcome this problem, including implementing a circle progress bar, snackbar, etc. as an indication to the user that something is happening in the background.

This provides a richer and more responsive User experience. Who doesn't want that?!

How to implement pagination in Android Recyclerview

The steps below are best practices as per the realities of the Android app development industry.

  1. Added recyclerview and dependency libraries.
  2. Setting up recyclerview in Activity
  3. Create two layout files: One for the data items and one for the progress indicators.
  4. Create an adapter for recyclerview
  5. Create a listener for the recyclerview adapter
  6. Set recyclerview adapter and listener

Add recyclerview and design library dependency

First of all, you need to add recyclerview and design library dependencies as mentioned below:

implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:design:27.1.1'

Make sure they have the same version.. otherwise it may cause some errors at runtime. Then sync the gradle files and wait for it to finish.

Set up an empty recyclerview in Activity

In the activity/fragment where you want to display the recyclerview, open the layout file and add a recyclerview. My layout looks like this:

<?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="match_parent">

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

</LinearLayout>

Now open the java file and prepare the recyclerview by adding LinearLayoutManger as its layout manager. Here is my MainActivity.java.

package com.ayusch.blogexamples.view;

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

import com.ayusch.blogexamples.R;
import com.ayusch.blogexamples.adapter.CustomAdapter;
import com.ayusch.blogexamples.listeners.InfiniteScrollListener;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity{

    RecyclerView recyclerView;
    ArrayList<Integer> data = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = findViewById(R.id.recyclerview);

        LinearLayoutManager manager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(manager);
       
    }

}

Create two layout files: One for the data items and one for the progress bar

The way it works is, the progress bar you see is added to the recyclerview as a regular android recyclerview entry. It is just like any other entry of your data. The only difference is, when the data is loaded from the server, the item is removed from the android recyclerview and the recyclerview is updated.

Following is the layout of row_item.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">

    <TextView
        android:id="@+id/number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="8dp"
        android:text="1"
        android:textSize="28sp" />
</LinearLayout>

I will display the numbers in the middle of the items. Your rows can be much more complex than this, but that is not the purpose of this tutorial.

This is my row_progress.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:gravity="center"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

Create a custom adapter for recyclerview

Now here comes the meaty part!! This is where your android development skills will be tested and your knowledge of recyclerviews and recyclerview adapters in android development will come in handy.

Now we need to create 3 ViewHolders

  • A base view holder
  • A view holder for our data items
  • A view holder for progressbar

Create an inner class inside CustomAdapter named CustomViewHolder and extend from RecyclerView.ViewHolder. Implement its methods by clicking  ALT + Enter .

Create a second inner class inside CustomAdapter named DataViewHolder and extend it from CustomViewHolder. Implement all its methods as well.

Finally, create a third inner class inside CustomAdapter called ProgressViewHolder and extend it from CustomViewHolder. Implement all its methods as well.

package com.ayusch.blogexamples.adapter;

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 com.ayusch.blogexamples.R;

import java.util.ArrayList;

public class CustomAdapter extends RecyclerView.Adapter{

  
    class DataViewHolder extends CustomViewHolder {
        public DataViewHolder(View itemView) {
            super(itemView);
            
        }

    }

    class ProgressViewHolder extends CustomViewHolder {

        public ProgressViewHolder(View itemView) {
            super(itemView);
        }
    }

    class CustomViewHolder extends RecyclerView.ViewHolder {

        public CustomViewHolder(View itemView) {
            super(itemView);
        }
    }

}

Remember I told you not to make public modifier to  CustomAdapter, we will do that now. Add a public type  CustomViewHolder when you extend from RecyclerView.Adapter and implement the following method.

package com.ayusch.blogexamples.adapter;

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 com.ayusch.blogexamples.R;

import java.util.ArrayList;

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.CustomViewHolder> {

    private ArrayList<Integer> dataList;

    public CustomAdapter(ArrayList<Integer> dataList) {
        this.dataList = dataList;
    }

    @NonNull
    @Override
    public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        

    }

    @Override
    public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
        
    }


    @Override
    public int getItemCount() {
        return dataList.size();
    }


    class DataViewHolder extends CustomViewHolder {
        public DataViewHolder(View itemView) {
            super(itemView);
        }

    }

    class ProgressViewHolder extends CustomViewHolder {
        public ProgressViewHolder(View itemView) {
            super(itemView);
        }
    }

    class CustomViewHolder extends RecyclerView.ViewHolder {

        public CustomViewHolder(View itemView) {
            super(itemView);
        }
    }

}

I have added a constructor that will take an ArrayList of data as its parameter.

We need to override one more method here:  getItemViewType() Press CTRL Enter and select Override Methods and select getItemViewType() Now here comes the trick!! We need to trick the adapter into thinking that a data item is being added to the recyclerview, when we want to show a progress bar. But how do we differentiate between real and fake data? Well, the fake item we are going to add, will be a null object. So, in the method  getItemViewType(), we can return the view type based on whether the item is fake (null) or real data. The ViewType we are returning here is obtained in the method  onCreateViewHolder() (see the last parameter).

We need to return a view holder here based on whether we should display a data row or a progress bar. So, first in the method  getItemViewType(), implement a null check on your data item and return a specific value based on it. It will look something like this:

@Override
public int getItemViewType(int position) {
    if (dataList.get(position) != null)
        return VIEW_TYPE_ITEM;
    else
        return VIEW_TYPE_LOADING;
}

IEW_TYPE_ITEM dan VIEW_TYPE_LOADING is an integer constant field in the CustomAdapter class. Now, in the CustomAdapter create method, we need to return a view holder based on the view type. Here is how your onCreateViewHolder method should look:

public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View root = null;
    if (viewType == VIEW_TYPE_ITEM) {
        root = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item, parent, false);
        return new DataViewHolder(root);
    } else {
        root = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_progress, parent, false);
        return new ProgressViewHolder(root);
    }
}

Now we need to bind the data to the layout in the onBindViewHolder method.

@Override
public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
    if (holder instanceof DataViewHolder) {
        ((DataViewHolder) holder).tvNumber.setText(dataList.get(position) + "");
    }else{
           //Do whatever you want. Or nothing !!
    }
}

We will add 3 utility methods to the adapter.

  • To add fake data to show the progress bar
  • To delete fake data
  • To add the actual data received after loading is complete.

Add a named method  addNullData() to the custom adapter:

public void addNullData() {
    dataList.add(null);
    notifyItemInserted(dataList.size() - 1);
}

Now add the method removeNullData()

public void removeNull() {
    dataList.remove(dataList.size() - 1);
    notifyItemRemoved(dataList.size());
}

Now to add new data that we receive from the server, we add a method named  addData (ArrayList dataList).

public void addData(ArrayList<Integer> integersList) {
    dataList.addAll(integersList);
    notifyDataSetChanged();
}

And  CustomAdapter our class is finally over!!

Create infinite scroll listener to recyclerview

We will add  InfiniteScrollListener to recyclerview. This will trigger a method  loadMore() inside our activity when all available data has been displayed on the screen.

Create a new class named  InfiniteScrollListener as shown below:

package com.ayusch.blogexamples.listeners;

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

public class InfiniteScrollListener extends RecyclerView.OnScrollListener {

    private final static int VISIBLE_THRESHOLD = 2;
    private LinearLayoutManager linearLayoutManager;
    private boolean loading; // LOAD MORE Progress dialog
    private OnLoadMoreListener listener;
    private boolean pauseListening = false;


    private boolean END_OF_FEED_ADDED = false;
    private int NUM_LOAD_ITEMS = 10;

    public InfiniteScrollListener(LinearLayoutManager linearLayoutManager, OnLoadMoreListener listener) {
        this.linearLayoutManager = linearLayoutManager;
        this.listener = listener;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (dx == 0 && dy == 0)
            return;
        int totalItemCount = linearLayoutManager.getItemCount();
        int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
        if (!loading && totalItemCount <= lastVisibleItem + VISIBLE_THRESHOLD && totalItemCount != 0 && !END_OF_FEED_ADDED && !pauseListening) {
            if (listener != null) {
                listener.onLoadMore();
            }
            loading = true;
        }
    }

    public void setLoaded() {
        loading = false;
    }

    public interface OnLoadMoreListener {
        void onLoadMore();
    }

    public void addEndOfRequests() {
        this.END_OF_FEED_ADDED = true;
    }

    public void pauseScrollListener(boolean pauseListening) {
        this.pauseListening = pauseListening;
    }
}

Set recyclerview adapter and scroll listener

Finally, it's time to set up our adapter and scroll listener. Change the code as below:

public class MainActivity extends AppCompatActivity implements InfiniteScrollListener.OnLoadMoreListener {

    RecyclerView recyclerView;
    ArrayList<Integer> data = new ArrayList<>();
    InfiniteScrollListener infiniteScrollListener;
    CustomAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = findViewById(R.id.recyclerview);

        LinearLayoutManager manager = new LinearLayoutManager(this);
        infiniteScrollListener = new InfiniteScrollListener(manager, this);
        infiniteScrollListener.setLoaded();

        recyclerView.setLayoutManager(manager);
        recyclerView.addOnScrollListener(infiniteScrollListener);
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        for (int i = 0; i < 10; i++) {
            data.add(i);
        }

        adapter = new CustomAdapter(data);
        recyclerView.setAdapter(adapter);

    }

    @Override
    public void onLoadMore() {
        adapter.addNullData();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                adapter.removeNull();
                ArrayList<Integer> newData = new ArrayList<>();
                for (int i = 0; i < 10; i++) {
                    newData.add(i);
                }
                adapter.addData(newData);
                infiniteScrollListener.setLoaded();
            }
        }, 2000);
    }


}

In  onLoadMethod, you will make a network call or whatever to fetch the next batch of data and add it to the recyclerview. For the sake of this example, I added dummy data and gave it a two second delay to mimic a network request.

This is what the final MainActivity.java looks like:

public class MainActivity extends AppCompatActivity implements InfiniteScrollListener.OnLoadMoreListener {

    RecyclerView recyclerView;
    ArrayList<Integer> data = new ArrayList<>();
    InfiniteScrollListener infiniteScrollListener;
    CustomAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = findViewById(R.id.recyclerview);

        LinearLayoutManager manager = new LinearLayoutManager(this);
        infiniteScrollListener = new InfiniteScrollListener(manager, this);
        infiniteScrollListener.setLoaded();

        recyclerView.setLayoutManager(manager);
        recyclerView.addOnScrollListener(infiniteScrollListener);
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        for (int i = 0; i < 10; i++) {
            data.add(i);
        }

        adapter = new CustomAdapter(data);
        recyclerView.setAdapter(adapter);

    }

    @Override
    public void onLoadMore() {
        adapter.addNullData();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                adapter.removeNull();
                ArrayList<Integer> newData = new ArrayList<>();
                for (int i = 0; i < 10; i++) {
                    newData.add(i);
                }
                adapter.addData(newData);
                infiniteScrollListener.setLoaded();
            }
        }, 2000);
    }


}

Okay, done!


Post a Comment

Previous Next

نموذج الاتصال