[MUSIC PLAYING]
TJ DAHUNSI: Showing a list of data to a user
is one of the most common UI patterns.
When working with large data sets,
you can improve app performance and reduce memory usage
by fetching and displaying data asynchronously in chunks.
This also allows your app to support infinite data sets.
And if you're pulling down network data,
you probably want a local cache as well.
This gets complicated fast.
But Android Jetpack Paging 3.0 can help.
In this video, we'll be integrating the Paging library
into the data layer of an existing app, a GitHub
repository search app.
While doing that, you'll also learn
about the basic components of paging.
The app in its current form follows the Jetpack
architecture guidelines with a repository,
responsible for fetching the data we want to display.
A view model transforms the data from the repository
into a form consumable by the UI.
The UI displays the transform data from the API.
You may have noticed, however, there's quite a bit of code
required to transform the data from where
it's fetched to where it's consumed in the UI.
We have to keep track of lots of manual, mutable state
across a few layers of the app.
And this is a fairly generic requirement
that can be abstracted out for re-use.
And this is exactly where Paging 3.0 comes in.
The Jetpack Paging library provides
a convenient way of pulling ordered
data sets from your data layer into your business layer,
while offering transformations, caching,
and other niceties for easy consumption in your UI.
Let's get started.
The first thing we need to do is define
how we actually fetch data.
Currently, our app does this in the repository using the GitHub
service.
In the context of the Paging library,
this functionality is implemented with a type called
the PagingSource.
Specifically, the PagingSource defines
how to fetch and refresh data around existing boundaries
defined by existing data.
OK.
Let's implement it.
Since we're pulling data from the GitHub API,
an app name for this in the app is GitHubPagingSource.
To build it, we need to define the following--
the type of paging key.
In our case, the GitHub API uses 1-based index numbers
for pages, so the type is an int.
The type of data loaded--
in our case, we're loading repo items,
where the data I retrieved is coming from the GitHub service.
Our data source will be specific to a certain query.
So we need to make sure that we're also
passing the query information to the GitHub service.
With the shell of the GitHubPagingSource defined,
we can now implement the methods to enable us to load our data.
There are two main methods to provide implementations for,
load and getRefreshKey.
Let's start with the load method.
The load function will be called by the Paging library
to asynchronously fetch more data
to be displayed as the user scrolls around.
The load params object keeps information related to the load
operation, including the following,
the key of the first page to be loaded--
if this is the first time the load is called,
params.key will be null.
In this case, you will have to define the initial page key.
Load size, the requested number of items to load.
The load function returns the load result.
It can either be a LoadResult.Page
if the result was successful, or LoadResult.Error
in case of an error.
When constructing the load result page,
pass null for the next key or the previous key if the load
cannot be loaded in the corresponding direction.
For example, in our case, we could
consider that if the network response was successful
but the list was empty, we do not have
any more data to be loaded.
So in that case, the next key can be null.
Note that by default, the initial load size
is three times the page size.
This ensures that the first time the list
is loaded, the user will see enough items without triggering
too many network requests, if the user does not
scroll past what's loaded.
This is also something to consider
when computing the next key in the PagingSource
implementation.
Next, we need to implement getRefreshKey.
The refresh key is used for subsequent refresh calls
to PagingSource.load.
The first goal is the initial load,
which uses the initial key provided by the pager.
A refresh happens whenever the Paging library
wants to load new data to replace the current list,
for example on a swipe to refresh
on invalidation due to database updates, configuration changes,
process death, et cetera.
Typically, subsequent refresh calls
will want to restart loading data centered
around paging state.anchorPosition, which
represents the most recently accessed index.
With the PagingSource defined, we can now create a Pager.
A Pager is a class that is responsible for incrementally
pulling chunks of data from the PagingSource
as requested by the UI.
We'll be creating our Pager in our repository class
as it has reference to types we need to create a GitHub pager
source.
Creating the Pager is simple enough.
The first thing we need is a PagingConfig,
which defines basic parameters that govern
how the Pager will fetch data.
There are quite a few configuration options here.
But for this particular case, we're concerned with just two--
the pageSize, the number of items
to be loaded at once from the PagingSource;
and enablePlaceholders, whether the PagingData
returns null for items that have not been loaded yet.
We typically want the pageSize to be large enough,
at least enough to fill the visible area of the screen,
but preferably, two to three times that,
so the Pager doesn't have to keep fetching data
over and over again to show enough material on the screen
as the user scrolls.
With the Pager constructed, we can now get data from it.
The type produced from the Pager is the PagingData.
The Paging library offers multiple ways
of consuming PagingData as a stream,
including Kotlin Flow via Pager.flow;
LiveData via Pager.liveData; RxJava Flowable
via Pager.flowable; RxJava Observable
via Pager.observable.
Each emission of PagingData is a distinct window
into its backing PagingSource.
As a user scrolls through the list,
PagingData will keep fetching from the PagingSource
to provide content.
Should the PagingSource be invalidated,
a new PagingData will be admitted
to make sure the items being paginated through are in sync
with what is displayed in the UI.
It may help to think of PagingData
as a stream of events from the PagingSource
as an instance in time.
This stream of PagingData is what the basis layer can then
opt to transform before presenting it to the UI.
That's it for this episode.
So far, we've covered what a PagingSource is and implemented
one; defined a PagingConfig and used it to create a Pager;
exposed a stream of PagingData from the Pager.
Stay tuned to see how to consume PagingData in the UI
and populate our list of repos.
[MUSIC PLAYING]