The Paging library is another library added to the Architecture Components. The library helps efficiently manage the loading and display of a large data set in the RecyclerView. Android paging library makes it easy to load data gradually and gracefully within our app’s RecyclerView.
Advantages of Paging Library
To use paging you have to know about it members:
PagedList
. A PagedList
is a special kind of List
for showing paged data in Android. PagedList object is passed to RecyclerView by creating LiveData of pages.DataSource
(and Data Source Factory) + Repository. DataSource is used for loading data by PagedList. You can create DataSource by extending one of the three data source classes such as PageKeyedDataSource
, ItemKeyedDataSource
, or PositionalDataSource
. You need to implement loadInitial
and loadAfter
methods. Using parameters in loadInitial
and loadAfter
methods, you can prepare query and load data.PagedListAdapter
calls loadAround
on PagedList
object to make PagedList
load data for next pages as user scrolls the items in recycler view. PagedListAdapter
listens for data changes and displays the changes in recycler view.Pagination library needs DataSource
to give us what we need. DataSource
is the class where you tell your application about how much data you want to load in your application. There’re three choices ItemKeyedDataSource
, PageKeyedDataSource
and PositionalDataSource
.
PageKeyedDataSource
if your feeds page has some feature of next or previous post. For example, you can see the next post by clicking the next button in any social media platform.ItemKeyedDataSource
if you want the data of an item with the help of the previous item. For example, while commenting to a post, you need the id of the last comment to get the content of the next comment.PositionalDataSource
if you want to fetch data only from a specific location then you can use PositionalDataSource
. For example, out of 1000 users, if you want to fetch the data of the users from 300 to 400 then you can do this by using PositionalDataSource
.Implementation
First, let’s add the Pagination library to our project. I’ve used version 2.1.1:
implementation "androidx.paging:paging-runtime:${paging}"
In following example we'll load data from Firestore and show in PagedListAdapter
.
Let’s create a model class
data class Movie(val title: String, val year: Int)
Create a Data Source
The Paging Library requires that the data used in the RecyclerView, regardless of whether they are pulled from the network, database, or memory, be defined as data sources.
In data source, we have to override 3 methods.
loadInitial
loads the first page of your data that use to initialize RecyclerViewloadBefore
used when to scroll uploadAfter
used when to scroll downclass MoviesDataSource: ItemKeyedDataSource<Movie, Movie>() { val rep = MovieRepository() override fun loadInitial(params: LoadInitialParams<Movie>, callback: LoadInitialCallback<Movie>) { rep.getMovies(null, params.requestedLoadSize, callback) } override fun loadAfter(params: LoadParams<Movie>, callback: LoadCallback<Movie>) { rep.getMovies(params.key, params.requestedLoadSize, callback) } override fun loadBefore(params: LoadParams<Movie>, callback: LoadCallback<Movie>) {} override fun getKey(item: Movie): Movie { return item } } class MovieRepository { val db = FirebaseFirestore.getInstance() fun getMovies(movie: Movie?, size: Int, callback: ItemKeyedDataSource.LoadCallback<Movie>) { val query = db.collection("databases/movies").orderBy("title", Query.Direction.DESCENDING); movie?.let { query.startAfter(movie) } query.limit(size.toLong()).get().addOnSuccessListener { if (it != null) { val items = it.toObjects(Movie::class.java) if (items.isEmpty()) return@addOnSuccessListener callback.onResult(items) } } } }
Using Data Source we need DataSource.Factory which extends DataSource.Factory. Basically it just a factory for DataSources.
val dataSourceFactory = object : DataSource.Factory<Movie, Movie>() { override fun create(): DataSource<Movie, Movie> { return MoviesDataSource() } }
Create a PageList
A PageList
takes in 4 important parameters:
setEnablePlaceholders
. Enabling placeholders mean there is a placeholder that is visible to the user till the data is fully loaded. So for instance, if we have 20 items that are needed to be loaded and each item contains an image, when we scroll through the screen, we can see placeholders instead of the image since it is not fully loaded.setInitialLoadSizeHint
. The number of items to load initially.setPageSize()
. The number of items to load in the PagedList.setPrefetchDistance
. The number of preloads that occur. For instance, if we set this to 10, it will fetch the first 10 pages initially when the screen loads.val config = PagedList.Config.Builder() .setEnablePlaceholders(false) .setPrefetchDistance(6) .setPageSize(12) .build()
Set up PagedListAdapter
Now we have data ready to load in page by page fashion. We need to create an adapter that accepts this data.
class MoviePagedAdapter : PagedListAdapter<Movie, MoviePagedAdapter.ViewHolder>(COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviePagedAdapter.ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.movie_item, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: MoviePagedAdapter.ViewHolder, position: Int) { val movie = getItem(position) movie?.let { holder.bindTo(movie) } } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bindTo(item: Movie) { with(itemView) { tvName.setText(item.title); } } } companion object { private val COMPARATOR = object : DiffUtil.ItemCallback<Movie>() { override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean = oldItem.userId == newItem.userId override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean = oldItem == newItem } } }
Gather all components in MainActivity
class MainActivity : AppCompatActivity() { val movieAdapter = MoviePagedAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dataSourceFactory = object : DataSource.Factory<Movie, Movie>() { override fun create(): DataSource<Movie, Movie> { return MoviesDataSource() } } val config = PagedList.Config.Builder() .setEnablePlaceholders(false) .setPrefetchDistance(6) .setPageSize(12) .build() val pagedListLiveData = LivePagedListBuilder(dataSourceFactory, config).build() pagedListLiveData.observe(this, Observer { movieAdapter.submitList(it) }) rvItems.adapter = movieAdapter rvItems.layoutManager = GridLayoutManager(this, 2) } }
Useful links