Welcome back to the Paging 3.0 MAD Skills series! In the previous article, we went over the Paging library, saw how it fits into the app’s architecture, and we integrated it into the data layer of the app. We did this using a
PagingSource to fetch our data, and then used it, along with a
PagingConfig to create a
Pager object that provided a
Flow<PagingData> for UI consumption. In this article I’ll be covering how to actually consume the
Flow<PagingData> in your UI.
Preparing PagingData for the UI
The app currently has the
ViewModel exposing the information needed to render the UI in the
UiState data class, which contains a
searchResult field; an in-memory cache for result searches that survives configuration changes.
With Paging 3.0, we drop the
searchResult val from the
UiState, opting instead to replace it with a
PagingData<Repo> exposed separately from the
UiState. This new
Flow will serve the same purpose as the
searchResult: provide a list of items to be rendered by the UI.
A private method “
searchRepo()” is added to the
ViewModel, which calls the
Repository to provide a
Flow from the
Pager. We can then call this method to create our
Flow<PagingData<Repo>> based on the search terms the user enters. We also make use of the
cachedIn operator on the resulting
Flow which caches it for quicker reuse using the
It’s important to expose the
<strong class="hy kk">PagingData</strong>
<strong class="hy kk">Flow</strong> independent of other
<strong class="hy kk">Flows</strong>. This is because the
PagingData itself is a mutable type, and maintains its own internal stream of data that updates over time.
Flows that comprise the fields of the
UiState fully defined, we can combine them into a
UiState, which can then be exposed to, and consumed by the UI alongside the
PagingData. With the above, we are now ready to start consuming our
Flows in the UI.
Consuming PagingData in the UI
The first thing we do is switch the
Adapter from a
ListAdapter to a
PagingDataAdapter is a
Adapter optimized for diffing and aggregating updates from
PagingData to make sure changes in the backing data set are propagated as efficiently as possible.
Next, we start to collect from the
Flow, so we can bind its emissions to the
PagingDataAdapter using the
submitData suspending function.
Also, as a user experience perk, we want to make sure that when the user searches for something new, they are taken to the top of the list to show the first search results. We want to do this when we’re confident when we’ve finished loading and have presented data in the UI. We achieve this by taking advantage of the
loadStateFlow exposed by the
PagingDataAdapter and the “
hasNotScrolledForCurrentSearch’’ field in the
UiState used for tracking if the user has manually scrolled the list themselves. The combination of these two creates a flag to let us know if we can trigger an auto scroll.
Since the load states provided by the
loadStateFlow are synchronous with what is displayed in the UI, we can confidently scroll to the top of the list once the load state flow notifies us we are not loading for each new query.
Adding Headers and Footers
Another advantage of the Paging Library is the ability to display progress indicators either at the top or bottom of the list with the help of the
LoadStateAdapter. This implementation of a
RecyclerView.Adapter is automatically notified of changes in the
Pager as it loads data which enables it to insert items at the top or bottom of the list as needed.
The best part is you don’t even need to change your existing
withLoadStateHeaderAndFooter extension conveniently wraps your existing
PagingDataAdapter with both the header and footer!
The arguments of the
withLoadStateHeaderAndFooter function take definitions of
LoadStateAdapters for both the header and footer. The
LoadStateAdapter’s in turn host their own
ViewHolders, which are bound with the latest load state making it easy to define the behavior of the views. We can even pass arguments to let us retry loading in case there’s an error, which I’ll cover more in the next article.
With that we’ve bound our paging data to the UI! For a quick recap, we:
- Integrated our Paging in the UI layer using the PagingDataAdapter
- Used the LoadStateFlow exposed by the PagingDataAdapter to guarantee we only auto scroll to the top of the list when the Pager is done loading
- Used the withLoadStateHeaderAndFooter() to add progress bars to UI when fetching data
Thanks for reading along! Stay tuned, and see you in the next one where we’ll be looking at Paging from the database as a single source of truth, and taking a closer look at the