[MUSIC PLAYING]
TJ DAHUNSI: Welcome back.
In the last episode, we integrated a pager
into our ViewModel and used it to populate the UI using
a Paging data adapter.
We also took considerations for adding
indicators for load states and retrying if there is an error.
In this video, we're dialing things up a notch.
Till now, we've been pulling our data directly
from the network, which only works
in the best of circumstances.
We may sometimes be on a slow internet connection,
or even have lost internet connection entirely.
Even if our connection is good, we certainly
don't want our apps to be a data hog, as refreshing data
every time you navigate to a screen is wasteful.
The solution to these issues is to have
a local cache we pull from and refresh only when necessary.
Updates to the cache should always
hit the cache first, and then be propagated back
into the ViewModel.
This way, the local cache is the single source of truth.
Conveniently for us, the Paging library
has this covered with a little help from the Room library.
Let's get into it.
Since the data source we'll be paging through
is going to be from our local database instead of the API
directly, the first thing we want to do
is update our paging source.
The good news is, we barely have to do much.
The little help from Room I mentioned earlier?
It turns out it's a bit more than that.
Getting a PagingSource from a Room DAO
is as simple as adding a definition for it on the DAO.
In the GitHub repository, we can now
update the construction of the pager
to use the new paging source.
Now, that's all well and good, but we're missing something.
How does the local database ever get populated?
Enter the RemoteMediator.
It's the class responsible for fetching more data
from the network when the PagingSource runs out of items
to load from the database.
Let's see how this works.
The key thing to note about the RemoteMediator
is that it is a callback.
The result from the RemoteMediator
is never returned to the UI as is.
It's just the way paging notifies us as the developer
that its PagingSource has run out of data.
It is our job to update the database until paging
there is new data in the database.
The first abstract method is the initialize method.
It is the first call made to the RemoteMediator
before any loading has begun.
And it returns an initialized action.
The initialized action is either LAUNCH_INITIAL_REFRESH,
which will cause the load method to be called
with a refresh load type, or SKIP_INITIAL_REFRESH,
which would cause the RemoteMediator
not to refresh unless the UI specifically requests it.
In our case, since repository starts may update often,
we return LAUNCH_INITIAL_REFRESH.
Next is the load method.
The load method is called out boundaries defined
by the loadType and the paging state,
where the loadType may either be a refresh, append, or prepend.
It is responsible for fetching the data,
persisting it to disk, and informing
the results, which can either be an error or success.
If it's an error, the load states reflects it
and the load may be retried.
If it's successful, however, the paging
needs to be notified if more data can be fetched or not.
Since the load method is a suspending function that
returns the result, it is important
that the UI is able to accurately reflect
the status of the work being done.
In the last video, we touched briefly
on the withLoadStateHeader and footer extension,
and saw how we can use it to display
loading header and footers.
A closer look at the name of the extension
reveals a type, the load state.
Let's go over this type some more.
Since paging is a series of asynchronous events,
it's important that the UI reflects the current state
of the data being fetched.
In paging, loading status of the pager
is represented with the CombinedLoadStates type.
Like its name implies, this class
is a combination of other types that
convey loading information.
These other types are one, the LoadState,
a sealed class that fully describes the loading status,
which can be Loading, NotLoading, or Error.
And then also there is LoadStates,
a data class containing LoadStates for append, prepend,
and refresh.
Typically, the prepend and append LoadStates
are used to react to extra data fetches,
while the refresh LoadState is used to react to initial loads,
refreshes and retries.
Since the paging may be loading from a PagingSource
or RemoteMediator, the CombinedLoadStates data class
has two load state fields, one for the PagingSource
and the other for the RemoteMediator.
As a convenience, CombinedLoadStates also
has refresh, append, and prepend fields similar to the fields
you find in LoadStates, which will reflect
the LoadState of the RemoteMediator
or the PagingSource, depending on your paging configuration
and a few other semantics.
Be sure to check out the docs on the behavior of the fields
in different scenarios.
Using this information to update your UI
is as easy as collecting from the LoadState flow exposed
by the paging adapter.
In the case of our app, we can use it to display a loading
spinner on first load.
We start collecting from the flow
and use the combined LoadStates to refresh fields
to show a progress bar if the pager isn't loading
and the existing list is empty.
We use the Refresh field because we only
want to show a launch progress bar when we launch the app
the first time or because we explicitly trigger refresh.
We can also check if any of the loading states have errored out
and notify the user.
Now, if we run the app, we should see a progress bar
till the paging source has been populated.
That's it for this episode.
To recap, we paged from the database
the single source of truth.
Used a RemoteMediator to feed the database PagingSource,
updated the UI with progress bars based on the LoadStates
from the paging adapter's LoadState flow.
We'll be wrapping up the series in the next video,
so stay tuned and see you soon.