Hello, everyone.
The Paging Library helps you load large sets of data
gradually and gracefully, reducing network usage
and system resources.
You told us that the paging to API was a good start.
But you wanted more.
So we launched Paging 3, a complete rewrite of the library
based on Kotlin coroutines and Flow,
adding the features you asked for,
like better error handling, easier transformations of your data,
like map or filter,
and support for common features like list separators,
headers, and footers.
We also focus on making it easier to integrate in your app.
If you're still developing in Java, we've got you covered.
Paging has support for RxJava, live data, and listenable futures.
Here's why I like the Paging 3 library.
When you load data in pages,
there's a lot of things you need to do.
You need to keep track of the keys to retrieve the next or previous page.
You have to request the correct next page
when the user scrolls at the end of the loaded data.
And you have to prevent applicator requests.
Well, Paging does all of this for you.
So there is a lot to like.
It also tracks loading state and allows you to display it
in a recycle view list item or elsewhere on your UI
and provides easier retry functionality for failed loads.
Refreshing your list is also only one method call away.
Paging makes it easy to transform your data before displaying it.
It offers common operations like map or filter,
no matter whether you're using flow like data or RxJava.
Adding separators with your data is also greatly simplified.
We also made several Paging 3 components backwards compatible with Paging 2.
So, if you're already using paging in your app,
you can migrate incrementally.
Now, let's take a look at the practical example.
An app that displays all the good dogs.
We get the dogs from a GoodDoggos API
that supports index-based pagination.
Let's go over the page and components we need to implement
and see how they fit in your architecture.
The following examples will be in Kotlin.
But in our doggos, we have examples in Java as well.
The Paging Library integrates directly in the recommended Android app architecture
in each layer of your app.
In your ### , and more precisely, in your repository,
we will work with a paging source or remote mediator.
Then, in the view model, using pager,
you will create the flow of paging data,
which, in the UI layer, you'll bind to a paging data adapter
to populate a recycler.
Let's start with the data source.
If you are loading data from a single source like network,
local database, or file, implement the paging source.
If you're using route, it implements the paging source for you.
If you're loading data from a layer source
like a network data source with a local database cache,
implement a remote mediator to retrieve and save remote data
and the paging source for the local database cache.
A paging source defines the source of paging data
and how to retrieve data from that single source.
Implement load to retrieve the page data from your data source.
Since load is a suspend function, you can call other suspend functions here,
such as a network or a database call.
You need to return the loaded data
together with information about next and previous keys,
or error if the load failed.
When your next and previous keys are given by the network response,
pass those in.
If you're using indices, for example,
compute those yourself.
Based on the keys you've set, and the direction of loading,
the paging library will set the value of the key field
in the load params.
So, when you build a network call, it's just provided.
Often, you want to have a layered data source,
like loading from both network and database.
They work great together.
You get to have fresh data from the network
with all the benefits of a local cache,
like offline support, fast resume, and decreased network traffic.
Implementing this by yourself without paging is not easy.
We often saw this kind of architecture,
where a repository would rely on a network connected state
to decide whether to get the data from the network
or from the database.
But there are several problems with this approach.
Just using a connected flag oversimplifies things,
as individual requests can succeed or fail.
And you're not using the local data, even if it's present,
but always requesting data from the network.
Instead, a better solution is to always read the database
as the source of truth for the UI.
And only request more from the network on the first load
or when the database is exhausted.
Paging 3 was built to support this exact architecture.
So here's what we need to do.
First, create a PagingSource that reads from the database.
Here, I'm using Room.
It supports this out of the box.
All we need to do is return our PagingSource
from our doggo query.
To request data from the network and save it,
Paging offers the remote mediator class.
The load method will be triggered whenever you need to load more data.
So here, we request data from the network
and save it to the database.
Then, you'll have to inform Paging whether there is more data to load or not.
For example, if we get no more doggos back from the service,
we know we've reached the end of the list.
Similarly, we'll have to tell Paging
whether an error occurred when retrieving the data.
That's all.
Now that we defined that data source, we need to configure paging to use it.
The primary entry point for Paging is the pager.
We construct the pager based on a paging configuration
and the function that creates the data source.
If you're using a remote mediator
and the database is your source of truth,
make sure you pass them both as such.
Paging config allows you to set several parameters.
But page size is required
and represents the number of items loaded at once from the PagingSource.
The pager gives you a stream of Paging data objects,
which contain the loaded data.
It allows us to define the data stream type.
The pager can return flow for Kotlin coroutines,
flowable, or observable, for RxJava2, or LiveData.
Now that we have our flow,
we need to connect it to our recycler view.
For this, implement a Paging data adapter.
This wouldn't look any different to implementing a regular list adapter.
But it provides the functionality needed to support Paging data objects.
Then, in the coding scope of your activity or fragment,
simply collect the flow and submit it to the adapter.
For example, if you're implementing this in an activity,
you'll launch a new coroutine in the lifecycle scope,
collect the latest emissions of the flow,
and submit the paging data to the adapter.
So, this is how Paging components integrate in your app architecture.
But often, we want to perform some transformations to the data
before displaying it.
Transforming Paging data streams
is very similar to the way you would work with any other data stream.
For example, let's say that we only want to display playful doggos.
To do this, we need to map the flow
and then, filter the paging data.
Now, let's say that our list is ordered by breed.
And you want to separate the dogs by breed name.
When adding list separators, we transform the Paging data
to insert separator objects into the list.
When using separators,
you will need to implement your own type that models the new separator items.
So, first, map from doggo to UiModel Doggo.
Next, to modify your Paging data to add separators,
use the insert separator strength formation.
You have to give a callback for every pair of items
where you can decide whether to insert a separator between them or not.
So here, I'm checking if the breed is different.
If so, I create a separator.
If not, I return.
Let's say that we want to add on a header or footer to our list.
First thing we have to do is expand the UiModel
to support the new view types,
and then, transform the Paging data to insert the header and the footer items.
But what if in your footer, you actually want to display the load state?
Maybe, a progress spinner while the list is loading
or a Retry button for error.
Because these are not elements separate from the actual list content,
we don't need to modify the list to add them.
To make it easier to work with a load state object,
Paging offers a new recycle view adapter type, load state adapter.
Implement the load state adapter
and update the adapter in your UI to also add the footer item,
the header item, or both.
So, we've seen the different types of transformations
Paging Data offers.
But transforming data might be expensive,
especially if you have a lot of items in memory.
You want to make sure that you're not executing transformations
on every configuration change when you start collecting the flow,
but rather just once.
To help with this, Paging 3 offers the cachedIn method.
The cachedIn should be done
after all the transformations have been performed.
If you're using Kotlin coroutines,
cache the data in the view model scope.
If you're using RxJava,
you'll have to use the view model scope for caching the data.
If you're using live data, you can either cache the data
in the view model scope
or in the context of a lifecycle object.
Okay, let's recap.
Paging 3 makes it easier to work with large sets of data,
whether loading from network, a database, in memory cache,
or some combination of these sources.
The library is both on coroutines and flow,
making it easy to call suspend functions
and work with streams of data.
It has built-in APIs to transform data,
add separators, headers, and footers,
displaying the loading state and retry and follow requests.
Finally, it's compatible with Paging 2.
So it's easy to migrate.
As Paging 3 is still in Alpha,
we need your help to make it better.
To get started, find out more about Paging in our documentation,
or check out the Code Lab and sample.
Then let us know how we can improve the library
by creating issues on the issue tracker.
We hope the Paging Library helps you gradually and gracefully
log all the good doggos.
Thanks for watching.