While working with Firebase Realtime Database, A common challenge that everyone face is how to create Firebase Realtime Database Pagination for a large amount of data stored in a node? While working with the RESTful APIs it is often very easy to add pagination support by using query parameters in the path but it is little tricky in Firebase Realtime Database case.

Why Pagination is important anyway?

When showing data from the internet through an API it is important that we request information in batches so that it consumes less bandwidth of the user and put less load on the server as well. Pagination is a concept in which we request data in smaller sets that are reasonable depending on the device type and internet connection.

For example, if we are building a social network then it does not make sense to bring all posts from the 100s of friends, first of all, it will consume a lot of network of the user and secondly, the information will be shown very late, both of which are bad for User Experience. Instead what we can do is that show latest 10 posts when the app is launched and bring next 10 when the user scrolls the news feed and reaches the 7th post. This way the user will see the posts quickly, it will save his bandwidth, and it will also put less burden on the server.

Enough Talking, Let’s get into the actual Steps!

I am going to create a very simple Android App that will show paginate through 100 items stored in a node in the Google Firebase Realtime Database. I assume that you know already about What is Firebase, Firebase Realtime Database, and RecyclerView.

For this tutorial, I am going to use the “users” node in which list of users is stored as “users/uid/” and under each uid, I have the corresponding user information which has the following JSON:

Firebase Realtime Database Pagination

Now you need to create Android Studio Project and add the Google Firebase Related Dependencies as usual. I am gonna skip those to keep the article concise and to the point.

Open the layout of your MainActivity.java and add RecyclerView to it.

<android.support.v7.widget.RecyclerView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rv"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

Now in the onCreate method of your MainActivity.java file, you need to the usual stuff to find the RecyclerView and setting it up which is shown below (so far so good):

mRV = findViewById(R.id.rv);
final LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
mRV.setLayoutManager(mLayoutManager);

Next, we need to create the corresponding POJO for our Firebase Realtime Database that we can want to paginate through.

public class UserModel {

    private String email;
    private String name;
    private String profileImageLink;
    private String provider;
    private String uid;

    public UserModel() {
    }

    public UserModel(String email, String name, String profileImageLink, 
           String provider, String uid) {
        this.email = email;
        this.name = name;
        this.profileImageLink = profileImageLink;
        this.provider = provider;
        this.uid = uid;
    }
}

(Note that I have removed getters and setters from the POJO to keep the post short)

Now we need to create list item layout for our RecyclerView adapter that will show each item, I have made it as below:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/user_iv"
            android:layout_width="56dp"
            android:layout_height="56dp" />

        <TextView
            android:id="@+id/user_name_iv"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:paddingLeft="16dp"
            tools:text="Shajeel Afzal" />
    </LinearLayout>

    <TextView
        android:id="@+id/email"
        style="@style/TextAppearance.AppCompat.Small"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        tools:text="email" />

    <TextView
        android:id="@+id/provider"
        style="@style/TextAppearance.AppCompat.Small"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        tools:text="google.com" />

</LinearLayout>

Now let’s create ViewHolder class for our RecyclerView Adapter that we are going to use with our RecyclerViewAdapter.

public class UserRVViewHolder extends RecyclerView.ViewHolder {

    private ImageView mUserIV;
    private TextView mUserNameTV;
    private TextView mEmailTV;
    private TextView mProviderTV;

    public UserRVViewHolder(View itemView) {
        super(itemView);
        findViews(itemView);
    }

    private void findViews(View view) {
        mUserIV = view.findViewById(R.id.user_iv);
        mUserNameTV = view.findViewById(R.id.user_name_iv);
        mEmailTV = view.findViewById(R.id.email);
        mProviderTV = view.findViewById(R.id.provider);
    }

    public void setData(UserModel userModel) {
        mUserNameTV.setText(userModel.getName());
        mEmailTV.setText(userModel.getEmail());
        mProviderTV.setText(userModel.getProvider());

        Glide.with(mUserIV.getContext())
                .load(userModel.getProfileImageLink())
                .thumbnail(0.1f)
                .crossFade()
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(mUserIV);
    }

}

Next we need to create RecyclerView Adapter

public class UserRVAdapter extends RecyclerView.Adapter<UserRVViewHolder> {

    private List<UserModel> userModels;

    public UserRVAdapter() {
        this.userModels = new ArrayList<>();
    }

    @Override
    public UserRVViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new UserRVViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.user_list_item_layout, parent, false));
    }

    @Override
    public void onBindViewHolder(UserRVViewHolder holder, int position) {
        holder.setData(userModels.get(position));
    }

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

    public void addAll(List<UserModel> newUsers) {
        int initialSize = userModels.size();
        userModels.addAll(newUsers);
        notifyItemRangeInserted(initialSize, newUsers.size());
    }

    public String getLastItemId() {
        return userModels.get(userModels.size() - 1).getUid();
    }
}

Now we need to query the records from the Firebase Realtime Database, I have created a separate function for that which takes the nodeId and brings the specified number of records after that nodeId. The function is simple and looks like this:

private void getUsers(String nodeId) {
    Query query;

    if (nodeId == null)
        query = FirebaseDatabase.getInstance().getReference()
                .child(Consts.FIREBASE_DATABASE_LOCATION_USERS)
                .orderByKey()
                .limitToFirst(mPostsPerPage);
    else
        query = FirebaseDatabase.getInstance().getReference()
                .child(Consts.FIREBASE_DATABASE_LOCATION_USERS)
                .orderByKey()
                .startAt(nodeId)
                .limitToFirst(mPostsPerPage);

    query.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            UserModel user;
            List<UserModel> userModels = new ArrayList<>();
            for (DataSnapshot userSnapshot : dataSnapshot.getChildren()) {
                userModels.add(userSnapshot.getValue(UserModel.class));
            }

            mAdapter.addAll(userModels);
            mIsLoading = false;
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            mIsLoading = false;
        }
    });
}

Three new variables are important in the above function:

mPostsPerPage: It is the Integer constant defined in the MainActivity.java class which specifies how many records will be queried per page.

mIsLoading: It is a boolean defined in the MainActivity that is used to keep track of whether the Firebase Database query is in progress or not.

Consts.FIREBASE_DATABASE_LOCATION_USERS: It is the constant defined in the Consts.java class where simply the location of the “users” node is stored as shown below:

public class Consts {
    public static final String FIREBASE_DATABASE_LOCATION_USERS = "users";
}

And the, if/else condition in the getUsers method, is checking the argument nodeId, If it is null then it queries the first page and if the nodeId is specified then it queries records starting from the nodeId.

And finally, we need to make a logic inside for calling the getUsers function when the user is nearly reaching the bottom of the list. And for that, we can use OnScrollListener whose onScrolled function is called every time when the user scrolls the list. The code for that is shown below:

mRV.addOnScrollListener(new RecyclerView.OnScrollListener() {

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

        mTotalItemCount = mLayoutManager.getItemCount();
        mLastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();

        if (!mIsLoading && mTotalItemCount <= (mLastVisibleItemPosition 
                  + mPostsPerPage)) {
            getUsers(mAdapter.getLastItemId());
            mIsLoading = true;
        }
    }
});

The if condition above is important, It is checking that mIsLoading is a false and the total number of visible items in the list are less than or equal to the last visible item position and posts per pages combined which makes sure that the getUsers method is called only once there are only 20 items in the list or user reaches the bottom of the list – (minus) posts per page constant value.

So you have seen that how simple it is to add Firebase Realtime Database Pagination support. Please let me know if this article was helpful for you.

You can see the complete code for this example on GitHub here.

Firebase Realtime Database Pagination

Don’t hesitate to ask in the comments section below if you have any doubts.

Happy Firebasing! 😊