Hi folks, after android Jetpack we have many new things to learn. So here is Android Paging Library Tutorial for you. Android Paging Library is an important component of Android Jetpack.
Update: Jetpack Paging 3.0 has been released, check the updated Jetpack Paging 3.0 Tutorial.
Many app displays a large set of data to the users, for example consider the Amazon App, it shows you the list of products, and the app has too many products, but it do not loads all the product at once, it shows you some products, as soon as you reaches the last item of the product list it loads more products. This is called paging.Â
Table of Contents
Android Paging Library Tutorial – Video
If you don’t like reading text and want a visual experience, then here is a complete step by step video series for understanding Android Paging Library.
Why use Paging?
Assume you have more than 1000 items for your list that you are fetching from a backend server. Here are the cons if you are fetching everything at once.
Disadvantages of not using Paging
- User do not see all the items at once, but you are fetching everything at once, so it will consume more bandwidth uselessly.
- Creating a large List at once uses more system resources resulting in a lagging app and bad user experience.
Advantages of using Paging
- You will only load a small chunk from your large data set, it will consume less bandwidth.
- The app will use less resources resulting in a smooth app and nice user experience.
Android Paging Library
Android paging library is a component of android jetpack. Remember it is not available by default so we need to add it. It helps us to load data gradually and gracefully in our application’s RecyclerView.
In this Android Paging Library Tutorial I will not tell you about the theoretical things about the library. Because to know this thing you can visit the official documentation. Instead of discussing the theoretical bla bla bla we will learn implementing the library in our application, as the official documentation might confuse you.
Pre-Requisites
Before moving ahead, you should go through these tutorials first, as we are going to use these things.
- Using Retrofit in Android: Complete Retrofit Playlist from Scratch.
We will use the Retrofit Library to fetch the data from backend API. The above tutorial discusses about Retrofit and Building API from scratch. So you can check the sereis. - RecyclerView: RecyclerView Tutorial.
We will load the items in a RecyclerView after fetching it from server. - Android ViewModel: Android ViewModel Tutorial.
This is another component from Android Jetpack. It helps us to store UI related data in a more efficient way.
Backend API
The important thing is the Backend API. Though you can also load data from SQLite database using Paging Library, but often the app needs to fetch data from our Backend API. You can go through this complete retrofit tutorial series to learn building an API using PHP and MySQL.
In this tutorial we will not build our own API, but I am going to use a readymade API of StackOverflow. Below is the API link.
1 2 3 |
https://api.stackexchange.com/2.2/answers?page=1&pagesize=50&site=stackoverflow |
In the above API URL we are passing the below parameters.
page: The number of page that we want to fetch.
pagesize: The total number of items that we want in the page.
site:Â The site from which we want to fetch the data.
The above URL will give the following response.Â
The data is coming from StackOverflow, and it has a very very large data set, so you will get may be infinite number of pages.
Now our task is to fetch from the page 1 and load the next page as soon as the user reaches the end of the list. And to do this we will use the Android Paging Library.
Android Paging Library Tutorial
Now let’s get into the real code friends.
Creating a new project
- As always we will create a new Android Studio project. I have created an Android Project named Paging Library Tutorial.
- Now before moving ahead we will add the required dependencies.
Adding Dependencies
- Go to app level build.gradle file and add the following dependencies.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
dependencies { def paging_version = "1.0.0" def view_model_version = "1.1.0" def support_version = "27.1.0" def glide_version = "4.3.1" implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "com.android.support:appcompat-v7:$support_version" implementation 'com.android.support.constraint:constraint-layout:1.1.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' //adding retrofit implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' //adding view model implementation "android.arch.lifecycle:extensions:$view_model_version" implementation "android.arch.lifecycle:viewmodel:$view_model_version" //adding paging implementation "android.arch.paging:runtime:$paging_version" //adding recyclerview and cardview implementation "com.android.support:cardview-v7:$support_version" implementation "com.android.support:recyclerview-v7:$support_version" //adding glide implementation "com.github.bumptech.glide:glide:$glide_version" annotationProcessor "com.github.bumptech.glide:compiler:$glide_version" } |
- After adding all the above required dependencies sync your project and you are good to go. Here we have added a lot of dependencies these are:
Retrofit and Gson: For fetching and parsing JSON from URL.
ViewModel: Android architecture component for storing data.
Paging: The paging library.
RecyclerView and CardView: For building the List.
Glide: For loading image from URL.
Creating Model Class
Now we will create a model class in our project. We need this class to parse the JSON response automatically. We have a really complex nested JSON in the response. So we need many classes to bind the response into respective java class automatically.
- Create a file named StackApiResponse.java and write the following code in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package net.simplifiedcoding.androidpagingexample; import java.util.List; class Owner { public int reputation; public long user_id; public String user_type; public String profile_image; public String display_name; public String link; } class Item { public Owner owner; public boolean is_accepted; public int score; public long last_activity_date; public long creation_date; public long answer_id; public long question_id; } public class StackApiResponse { public List<Item> items; public boolean has_more; public int quota_max; public int quota_remaining; } |
- The above code is pretty simple, I am not using getters and setters just to make it concise and small. The variable names are matching with the JSON keys so the Gson will map the data accordingly.
- The above file have only one public class (and actually we can have only one public class in a file). The class named StackApiResponse contains the following json.
- Inside the class we are mapping the above JSON object, that is why we have only 4 properties inside StackApiResponse.
- Then first item inside StackApiResponse is items which contains an array of items, that is why I have a List<Item>, then has_more is boolean and quota_max and quota_remaining.
- Now inside item we have the following JSON.
- Inside item we have another object named owner, that is why I have created another class named Owner, and I have declared an object of type Owner inside the Item class. I hope you got how we define model class for the specified JSON.
Creating Retrofit Singleton Class
- Each time when we want to fetch data from a new page, we need the Retrofit object. So creating a singleton instance of Retrofit is a good idea.
- For the singleton instance I will create a new class named RetrofitClient.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package net.simplifiedcoding.androidpagingexample; import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitClient { private static final String BASE_URL = "https://api.stackexchange.com/2.2/"; private static RetrofitClient mInstance; private Retrofit retrofit; private RetrofitClient() { retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); } public static synchronized RetrofitClient getInstance() { if (mInstance == null) { mInstance = new RetrofitClient(); } return mInstance; } public Api getApi() { return retrofit.create(Api.class); } } |
- The above code is pretty simple, if you are having trouble understanding it, then you should first follow the Retrofit Tutorial.
Creating API
- Now we will create the API call interface.
- Create an interface named Api and write the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package net.simplifiedcoding.androidpagingexample; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; public interface Api { @GET("answers") Call<StackApiResponse> getAnswers(@Query("page") int page, @Query("pagesize") int pagesize, @Query("site") String site); } |
Creating RecyclerView
As I already told you that, we will display the list in the RecyclerView. So let’s build the RecyclerView.
- Come inside activity_main.xml and define your RecyclerView here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.constraint.ConstraintLayout> |
- Now for the RecyclerView create one more layout resource file named recyclerview_users.xml and write the following xml code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?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="wrap_content"> <ImageView android:id="@+id/imageView" android:layout_width="70dp" android:layout_height="70dp" /> <TextView android:id="@+id/textViewName" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="15dp" android:layout_toRightOf="@id/imageView" android:text="Belal Khan" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" android:textColor="@color/colorDefault" /> <TextView android:layout_width="match_parent" android:layout_height="1dp" android:layout_below="@id/imageView" android:background="@color/colorDefault" /> </RelativeLayout> |
Creating PagedListAdapter
We need to fetch the data page wise, so this time we will not use the RecyclerView.Adapter but we will use a PagedListAdapter.
What is PagedListAdapter?
It is a class that has the common behaviours needed for a PagedList for example Item Counting, Page CallBack etc. And don’t get too much confused, the only point here is that this is going to be the adapter for our RecyclerView.Â
- Create a class named ItemAdapter and write the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
package net.simplifiedcoding.androidpagingexample; import android.arch.paging.PagedListAdapter; import android.content.Context; import android.support.annotation.NonNull; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.Glide; public class ItemAdapter extends PagedListAdapter<Item, ItemAdapter.ItemViewHolder> { private Context mCtx; ItemAdapter(Context mCtx) { super(DIFF_CALLBACK); this.mCtx = mCtx; } @NonNull @Override public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(mCtx).inflate(R.layout.recyclerview_users, parent, false); return new ItemViewHolder(view); } @Override public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { Item item = getItem(position); if (item != null) { holder.textView.setText(item.owner.display_name); Glide.with(mCtx) .load(item.owner.profile_image) .into(holder.imageView); }else{ Toast.makeText(mCtx, "Item is null", Toast.LENGTH_LONG).show(); } } private static DiffUtil.ItemCallback<Item> DIFF_CALLBACK = new DiffUtil.ItemCallback<Item>() { @Override public boolean areItemsTheSame(Item oldItem, Item newItem) { return oldItem.question_id == newItem.question_id; } @Override public boolean areContentsTheSame(Item oldItem, Item newItem) { return oldItem.equals(newItem); } }; class ItemViewHolder extends RecyclerView.ViewHolder { TextView textView; ImageView imageView; public ItemViewHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.textViewName); imageView = itemView.findViewById(R.id.imageView); } } } |
- The adapter is almost same as RecyclerView.Adapter<>, the only change here is we have a DIFF_CALLBACK implementation that we are passing to the super(). This callback is used to differentiate two items in a List.
- For the PagedListAdapter<> we define the Item and the ViewHolder. Item is the item that you want to display, and we already have a class named Item that contains the data that we need to display.
Creating Item Data Source
Now here comes the very important thing, the data source of our item from where we will fetch the actual data. And you know that we are using the StackOverflow API.
For creating a Data Source we have many options, like ItemKeyedDataSource, PageKeyedDataSource, PositionalDataSource. For this example we are going to use PageKeyedDataSource, as in our API we need to pass the page number for each page that we want to fetch. So here the page number becomes the Key of our page.
- Create a class named ItemDataSource and write the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
package net.simplifiedcoding.androidpagingexample; import android.arch.paging.PageKeyedDataSource; import android.support.annotation.NonNull; import android.util.Log; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class ItemDataSource extends PageKeyedDataSource<Integer, Item> { //the size of a page that we want public static final int PAGE_SIZE = 50; //we will start from the first page which is 1 private static final int FIRST_PAGE = 1; //we need to fetch from stackoverflow private static final String SITE_NAME = "stackoverflow"; //this will be called once to load the initial data @Override public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull final LoadInitialCallback<Integer, Item> callback) { RetrofitClient.getInstance() .getApi().getAnswers(FIRST_PAGE, PAGE_SIZE, SITE_NAME) .enqueue(new Callback<StackApiResponse>() { @Override public void onResponse(Call<StackApiResponse> call, Response<StackApiResponse> response) { if (response.body() != null) { callback.onResult(response.body().items, null, FIRST_PAGE + 1); } } @Override public void onFailure(Call<StackApiResponse> call, Throwable t) { } }); } //this will load the previous page @Override public void loadBefore(@NonNull final LoadParams<Integer> params, @NonNull final LoadCallback<Integer, Item> callback) { RetrofitClient.getInstance() .getApi().getAnswers(params.key, PAGE_SIZE, SITE_NAME) .enqueue(new Callback<StackApiResponse>() { @Override public void onResponse(Call<StackApiResponse> call, Response<StackApiResponse> response) { //if the current page is greater than one //we are decrementing the page number //else there is no previous page Integer adjacentKey = (params.key > 1) ? params.key - 1 : null; if (response.body() != null) { //passing the loaded data //and the previous page key callback.onResult(response.body().items, adjacentKey); } } @Override public void onFailure(Call<StackApiResponse> call, Throwable t) { } }); } //this will load the next page @Override public void loadAfter(@NonNull final LoadParams<Integer> params, @NonNull final LoadCallback<Integer, Item> callback) { RetrofitClient.getInstance() .getApi() .getAnswers(params.key, PAGE_SIZE, SITE_NAME) .enqueue(new Callback<StackApiResponse>() { @Override public void onResponse(Call<StackApiResponse> call, Response<StackApiResponse> response) { if (response.body() != null) { //if the response has next page //incrementing the next page number Integer key = response.body().has_more ? params.key + 1 : null; //passing the loaded data and next page value callback.onResult(response.body().items, key); } } @Override public void onFailure(Call<StackApiResponse> call, Throwable t) { } }); } } |
You might find the above code a little tricky, but it is the most important part of our project. So let’s try to understand it.
- We extended PageKeyedDataSource<Integer, Item> in the above class. Integer here defines the page key, which is in our case a number or an integer. Every time we want a new page from the API we need to pass the page number that we want which is an integer. Item is the item that we will get from the API or that we want to get. We already have a class named Item.
- Then we defined the size of a page which is 50, the initial page number which is 1 and the sitename from where we want to fetch the data. You are free to change these values if you want.
- Then we have 3 overridden methods.
- loadInitials(): This method will load the initial data. Or you can say it will be called once to load the initial data, or first page according to this example.
- loadBefore(): This method will load the previous page.
- loadAfter(): This method will load the next page.
Creating Item Data Source Factory
We are going to use MutableLiveData<> to store our PageKeyedDataSource and for this we need a DataSource.Factory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package net.simplifiedcoding.androidpagingexample; import android.arch.lifecycle.MutableLiveData; import android.arch.paging.DataSource; import android.arch.paging.PageKeyedDataSource; public class ItemDataSourceFactory extends DataSource.Factory { //creating the mutable live data private MutableLiveData<PageKeyedDataSource<Integer, Item>> itemLiveDataSource = new MutableLiveData<>(); @Override public DataSource<Integer, Item> create() { //getting our data source object ItemDataSource itemDataSource = new ItemDataSource(); //posting the datasource to get the values itemLiveDataSource.postValue(itemDataSource); //returning the datasource return itemDataSource; } //getter for itemlivedatasource public MutableLiveData<PageKeyedDataSource<Integer, Item>> getItemLiveDataSource() { return itemLiveDataSource; } } |
Creating ViewModel
- Now create a class named ItemViewModel and write the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package net.simplifiedcoding.androidpagingexample; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import android.arch.paging.LivePagedListBuilder; import android.arch.paging.PageKeyedDataSource; import android.arch.paging.PagedList; public class ItemViewModel extends ViewModel { //creating livedata for PagedList and PagedKeyedDataSource LiveData<PagedList<Item>> itemPagedList; LiveData<PageKeyedDataSource<Integer, Item>> liveDataSource; //constructor public ItemViewModel() { //getting our data source factory ItemDataSourceFactory itemDataSourceFactory = new ItemDataSourceFactory(); //getting the live data source from data source factory liveDataSource = itemDataSourceFactory.getItemLiveDataSource(); //Getting PagedList config PagedList.Config pagedListConfig = (new PagedList.Config.Builder()) .setEnablePlaceholders(false) .setPageSize(ItemDataSource.PAGE_SIZE).build(); //Building the paged list itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory, pagedListConfig)) .build(); } } |
Now before moving ahead rebuild your project.Â
Displaying the Paged List
- Finally come inside MainActivity.java and write the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
package net.simplifiedcoding.androidpagingexample; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProviders; import android.arch.paging.PagedList; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.widget.Toast; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity { //getting recyclerview private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //setting up recyclerview recyclerView = findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setHasFixedSize(true); //getting our ItemViewModel ItemViewModel itemViewModel = ViewModelProviders.of(this).get(ItemViewModel.class); //creating the Adapter final ItemAdapter adapter = new ItemAdapter(this); //observing the itemPagedList from view model itemViewModel.itemPagedList.observe(this, new Observer<PagedList<Item>>() { @Override public void onChanged(@Nullable PagedList<Item> items) { //in case of any changes //submitting the items to adapter adapter.submitList(items); } }); //setting the adapter recyclerView.setAdapter(adapter); } } |
- Now you are done and you can try running your application.
- If everything is fine you will see the below output.
- As you can see it is working fine, and we have a smooth and huge list in our application.
Note: If you are not seeing any item, make sure the API URL is working correctly, by opening the URL in your browser, as sometimes stackoverflow blocks the continuous requests to their URLs.Â
Android Paging Library Tutorial Source Code
This tutorial is very big, and little complicated. So you have to follow all the steps carefully. But for some reason if you are not able to make it you can try with my source code as well.
From the below link you can download my source code on GitHub.
So that is all for this Android Paging Library Tutorial friends. I hope you found it helpful, so for a payback (just kidding) please share this tutorial with your friends learning android. And for any question feel free to comment. Thank You 🙂