Site icon AndroidVille

Paginated Recyclerview with progress bar in Android

Have you noticed that in Instagram’s android app, when you scroll to the bottom of your feed, there is that gray colored circular loading bar?

It is a really cool and necessary feature to add to your apps if your apps make use of a recyclerview which loads data in parts from the server. The user has to be notified that a network call is in progress and he has to wait till the data loads.

Frankly speaking, the old overlay progress dialogs with “Loading… or Please Wait…” texts are no longer the best solutions for this. The  android development community has come up with newer much cooler solutions to this problem by implementing circular progress bars, snackbars etc. to give the user the indication that something is going on in the background.

And they look really good!! Have a look at this progress bar at the bottom of Instagram feed.

 

Its way better than the old-fashioned dialog boxes. They don’t freeze the functionality of the app. The user can still interact with the app, like the post, comment, share etc. The buttons still remain clickable.

And this provides for a richer and more responsive User Experience. Who doesn’t want that.

So, in this android development tutorial, I am going to show you, how you can create such a functionality for you android app. Its really easy and you should definitely be using it.

 

How to implement pagination in android Recyclerview

We’ll be following aforementioned sequence of steps:

 

Add recyclerview and design library dependency

First of all, you need to add the 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 as the support library otherwise it may lead to some runtime crashes. Then sync the gradle file and wait for it to complete.

Once gradle build is finished, move on to the next step.

 

Setup empty recyclerview in the activity

In the activity/fragment where you want to show the recyclerview, go to the layout file and add a recylcerview. 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 go over to the java file and setup the recyclerview by adding a LinearLayoutManger as it’s 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 item and one for the progress bar

Now we need to create two layout files:

  1. For the data item
  2. For the progressbar item

How this works is, the progress bar that you see is added to recyclerview as a normal android recyclerview entry. It is not different from all your other data entries. The only difference is, when the data is loaded from the server, the item is removed from the android recyclerview and recyclerview is updated.

Here is my row_item.xml (data row) layout:

<?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 be displaying numbers in the center of items. Your row can be much more complex than this, but this is not the aim of this android development tutorial.

 

Here is my row_progress.xml (progressbar) layout:

<?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 is the meaty part!!

Here is when your android development skills would be put to test and your knowledge of recyclerviews and recyclerview adapters in android development would come in handy.

Create a class CustomAdapter.java (You can name it anything you want) and extend it from RecyclerView.Adapter. It might ask you to override the methods but don’t just yet.

Now we need to create 3 ViewHolders:

  1. A base view holder
  2. A view holder for our data item
  3. A view holder for progressbar

Create an inner class inside CustomAdapter named CustomViewHolder and extend it from RecyclerView.ViewHolder. Implement it’s 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 named ProgressViewHolder and extend it from CustomViewHolder. Implement all its methods as well.

This is how everything must look like for now:

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);
        }
    }

}

 

** You can name your viewholders anything you want. This is just the convention that I follow**

 

Remember I told you not to provide a generic type to CustomAdapter, well we’ll do it now. Add the generic type of “CustomViewHolder” when you extend from RecyclerView.Adapter and implement the following methods:

onCreateViewHolder, onBindViewHolder, getItemCount

It should look something like this:

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’ve added a constructor which would 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 this is where the trick lies!! We have to fool the adapter into thinking that a data item is being added to the recyclerview, when we want to show a progress bar. But how to differentiate between the real data item and a fake one?

Well, the fake item we will be adding, will be a null object. And so, in our getItemViewType() method, we can return a view type based on wether the item is a fake(null) or real data.

The viewType we return here is obtained in the onCreateViewHolder() method (see the last parameter). We have to return a view holder here based on whether we have to show the data row or progress bar.

So, firstly in getItemViewType() method, apply a null check on your data item and return a specific value based on it. This is how it would look like:

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

VIEW_TYPE_ITEM and VIEW_TYPE_LOADING are integer constant fields in the CustomAdapter class.

Now, in the on create method of our CustomAdapter, we have to return a view holder based on view type. This is how your onCreateViewHolder method would look like:

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 our 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 are going to add 3 utility methods to our adapter.

  1. To add a fake data to show progress bar
  2. To remove the fake data
  3. To append actual data received after loading is complete

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

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

Now add “removeNullData()” method:

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

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

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

And our CustomAdapter class is finally complete!!

 

Create an infinite scroll listener to recyclerview

We will add an InfiniteScrollListener to our recyclerview. This will trigger a loadMore() method inside our activity when all the data available has been shown 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 the recyclerview adapter and scroll listener

Finally, it’s the time to set our adapter and scroll listener. Modify 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 the onLoadMethod you’ll do your network call or whatever to fetch the next set of data and add it to the recyclerview.

For the sake of this example, I am adding a dummy data and giving a two second delay to emulate a network request.

We’re all set to go. Here’s how 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);
    }


}

 

Run the app and see it in action!!

 

Like what you read ? Don’t forget to share this post on FacebookWhatsapp and LinkedIn.

You can follow me on LinkedInQuoraTwitter and Instagram where I answer questions related to Mobile Development, especially Android and Flutter.

Exit mobile version