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! 😊

26 COMMENTS

  1. update ( nodId +1 ) to skip last id repeatation .. was nice tut ,

    note for others that Uid could be any id that is shown in adapter ,it is defined to get more result from the last user loaded item, it can also be saved as data as in this case shajeed did,

  2. but there is one more issue i m facing , when we start iterating data from starting node from up to bottom ,, node +1 makes sense it skips bacon it don’t find that and start from one more id but i m showing data in my project from bottom to top , so in that case i use Collections.reverse(example 10 postdate) and that will give me node of 10th item added in array list , but in that case its repeating i use endAt(nodded) so it will start iterating from bottom 20th node 20,19,18 like wise when it will reach to 10 th it will endAT(NODEID) but in that case i cants skip as like we do from up to bottom case .
    example for 5 items
    Up to bottom case :
    1,2,3,4,5
    startAt(5) don’t found started from 6

    bottom to up:

    15,16,17,18,19,20
    collection.reverese(data)
    20,19,18,17,16,15(userssize)
    endAt(15)

    Repeat bottom to up:

    10,11,12,13,14,15(repeat problem) as endAt(15) next iteration

  3. ok i found the way but little complicated ,,

    skips 1 item at the end of all iterations

    declared one string variable saved last node id , and removed from array list.

    for bottom to top :

    getUsers(nodeId2);

    .endAt(nodeId)

    Collections.reverse(commonModels);

    ——- > nodeId2=commonModels.get(commonModels.size()-1).getBasic().getBasic().getPushkey();
    commonModels.remove(commonModels.size()-1);

    multiViewTypeAdapter.addAll(commonModels);

    • Ashfaaaa, Sorry I could not write about it. Also, I postponed it because Google ended Admob Native Ads and recommends to go with Google Advanced Native Ads. And Advanced Native Ads are not working properly in The clockbyte:admobadapter library, I have opened an issue as well there. I’ll try to write about doing it without any third party library while using Firebase-UI very soon.

  4. It was a great post but there is a problem that i’m facing, I want to use FirebaseRecyclerAdapter’s getRef() method. I’ll use the reference obtained from getRef() to get the key using getKey() method.
    Is there any way in which I can integrate these methods in your implementation of adapter.

  5. am using firebase to develop an android app which work somewhat as instagram but i am confussed that what if i use firebase to upload and download images as insta.. then their will be more bandwidth use and then the charges of firebase will be more. .. i want to ask how much it may cost and is it good to make such app on firebase behalf of monthly charges??

  6. hello shajeel, your method for realtime database pagination is very very good and runs perfectly, thanks for that sir ! now i want to applying method for refreshing the list, but i can’t find any perfect method because any solution that i’ve tried always give me same results : the list update successful if only i refresh it after i ever click the item and make intent to another activity, can you please give me the right way for refreshing the list sir ?

    • Thanks for appreciation Agung! You can easily modify the code to add the refreshing feature. You can modify getUsers() method and add a check if nodeId is null then update the ArrayList in the Adapter. Let me know if you face the problem, I’ll update the repository with refresh feature.

  7. Hello, Thank you about your tutorial. Your tutorial so nice.
    But i got the problem. I can’t refresh and my data only show the older from newest. Can you help me?

LEAVE A REPLY

Please enter your comment!
Please enter your name here