- Pagination in Android with Paging 3, Retrofit and kotlin Flow
- Non library approach
- Android Jetpack Paging 3 and Flow
- Introduction to Paging 3.0 in the MAD Skills Series
- Why Paging 3.0?
- Dropping it in
- The load method
- The getRefreshKey method
- The Pager object
- Getting your data
- What’s next?
- Android: Definitive guide to Paging 3
- Advantages
- Architecture
- Repository
- ViewModel
- Implementation
- Setup
- Data Source
- Repository
- ViewModel
- Adapter
- Activity/Fragment
- Display the loading and error state in the footer
- Empty state
- Load and error state for the Activity/Fragment
- Conclusion
Pagination in Android with Paging 3, Retrofit and kotlin Flow
Haven’t you asked how does Facebook, Instagram, Twitter, Forbes, etc… let you “scroll infinitely” without reach a “end” of information in their apps? Wouldn’t you like to implement something like that?
This “endless scrolling” feature is commonly called “Pagination” and it’s nothing new. In a brief resume, pagination help you to load chunks of data associated with a “page” index.
Let’s assume you have 200 items to display in your RecyclerView. What you would do normally is just execute your request, get the response items and submit your list to your adapter. We already know that RecyclerView by it’s own is optimized for “recycle our views”. But do we really need to get those 200 items immediately? What if our user never reach the top 100, or top 50? The rest of the non displayed items are still keep on memory.
What pagination does (in conjunction with our API) is that it let us establish a page number, and how many items per page can we load. In that way, we can request for the next page of items only when we reach the bottom of our RecyclerView.
Non library approach
Before paging 3, you could have implemented Pagination by adding some listeners to your RecyclerView and those listeners would be triggered when you reach the bottom of your list. There are some good samples about it, here is a video with a vey detailed explanation (it’s on Indi, but it is understandable anyways).
A real PRODUCTION ready example is on the Plaid app. Look at their InfinteScrollListener class.
Android Jetpack Paging 3 and Flow
Today you gonna learn how to implement Pagination by using paging 3 from the android jetpack libraries. For my surprise the codelab of Paging 3 was one of the most easiest I have ever done. Florina Muntenescu did a great job with each step of the codelab, go check it out and give it a try. If you want to go straight to the sample code, check this pull request and see step by step how I implement paging 3 to this project.
Источник
Introduction to Paging 3.0 in the MAD Skills Series
Welcome to the Paging 3.0 MAD Skills series! In this article I’ll be introducing Paging 3.0 and highlighting how to integrate it into the data layer of your application. Want to watch and read? Check out the video for this episode here!
Why Paging 3.0?
Showing lists of data to a user is one of the most common UI patterns. When working with large datasets, you can improve app performance and reduce memory usage by fetching/displaying data asynchronously in chunks. This is a common enough pattern that having a library provide an abstraction that facilitates this is a huge boon. These are use cases that Paging 3.0 solves with aplomb. As an added bonus, it also allows your app to support infinite datasets, and if you’re pulling down network data, it offers avenues for local caching as well.
If you’re currently using Paging 2.0, Paging 3.0 offers a bunch of improvements over its predecessor including:
- First-class support for Kotlin coroutines and Flow.
- Support for async loading using RxJava Single or Guava ListenableFuture primitives.
- Built-in load state and error signals for responsive UI design, including retry and refresh functionality.
- Improvements to the repository layer, including cancellation support and a simplified data source interface.
- Improvements to the presentation layer, list separators, custom page transforms, and loading state headers and footers.
Be sure to check out the Paging 2.0 to Paging 3.0 migration guide for more information.
Dropping it in
In the scheme of your app’s architecture, Paging 3.0 is best suited as a means of fetching data from your data layer, and ferrying it for transformation and presentation in the UI layers via the ViewModel . Access to your data layer in Paging 3.0 is through a type called the PagingSource , a class that defines how to fetch and refresh data around boundaries defined by a PagingConfig .
A PagingSource , similar to a Map , is defined by two generic types: that of its paging key, and that of the data loaded. For example, the declaration of a PagingSource that fetched Repo items from the page based Github API would be defined as:
The PagingSource needs to implement 2 abstract methods to be fully functional:
The load method
The load() method, much like its name implies, is called by the Paging library to asynchronously fetch data to be displayed. This is either for the initial load, or in response to the user scrolling around. The trigger for the call to load can be ascertained from the LoadParams object passed as an argument to the method. It keeps information related to the load operation including the following:
- Key of the page to be loaded: If this is the first time that load is called (initial load), the LoadParams.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 return type of load is a LoadResult . It can either be:
- LoadResult.Page : for successful loads.
- LoadResult.Error : for when errors arise.
Note that by default, the initial load size is 3 * 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 scrolls a bit. This is also something to consider when computing the next key in the PagingSource implementation.
The getRefreshKey method
The refresh key is used for subsequent refresh calls to PagingSource.load() (the first call is initial load which uses the initial key provided to the Pager). A refresh happens whenever the Paging library wants to load new data to replace the current list, e.g., on swipe to refresh or on invalidation due to database updates, config changes, process death, etc. Typically, subsequent refresh calls will want to restart loading data centered around PagingState.anchorPosition which represents the most recently accessed index.
The Pager object
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. Since the Pager requires access to the PagingSource , it is typically created in the data layer where the PagingSource is defined.
The second thing needed to construct a Pager is the PagingConfig which defines parameters that govern how the Pager will fetch data. It exposes quite a few optional arguments that let you fine tune the Pager’s behavior, however the pageSize argument is required.
A brief description of the arguments utilized in the construction of the PagingConfig above follows:
- pageSize : The number of items to be loaded at once from the PagingSource .
- enablePlaceholders : Whether the PagingData returns null for items that haven’t 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 2–3x 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.
Getting your data
The type produced from the Pager is PagingData , a type that provides a distinct window into its backing PagingSource . As a user scrolls through the list, the paging data will keep fetching from the PagingSource to provide content. Should the PagingSource be invalidated, a new PagingData will be emitted to make sure the items being paginated through are in sync with what is displayed in the UI. It may help to think of a PagingData as a snapshot of the PagingSource at an instance in time.
Since a PagingSource is a snapshot that changes when the PagingSource is invalidated, 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
This stream of PagingData is what the ViewModel can then opt to transform before presenting the paged items to the UI.
What’s next?
With the above, we’ve integrated Paging 3.0 into the data layer of the app! Stay tuned to see how to consume PagingData in the UI and populate our list of repos!
Источник
Android: Definitive guide to Paging 3
Paging 3 is a library from the Android Jetpack that helps you load and display pages of data from a larger amount of data from local or remote data source. This approach allows your app to use efficiently the bandwidth and system resources once the user may not see all the data loaded at once.
Advantages
- In-memory cache.
- Makes your RecyclerView’s Adapter to automatically request new data when the user scrolls toward the end.
- Flow and LiveData support.
- Error handling, refresh and retry capabilities.
Architecture
The Paging library integrates directly into the recommended Android app architecture. The library’s components operate in three layers of the app:
Repository
In the Repository layer there is the PagingSource . The PagingSource defines a source of data and how to retrieve data from it. Also, can load from remote or local data sources.
In addiction, in the Repository layer there is the RemoteMediator . The RemoteMediator handles paging from a layered data source.
ViewModel
The Pager component provides a public API for constructing instances of PagingData that are exposed to the View, based on a PagingSource and a PagingConfig configuration.
In the View layer there is the PagingDataAdapter , a RecyclerView adapter that handle the paginated data.
Implementation
For our implementation of the Paging 3, we are going to use the Github API https://api.github.com/users/google/repos which can be used the parameters page and per_page to handle pagination like this: https://api.github.com/users/google/repos?page=1&per_page=20.
This will be the final result:
To keep it simples we’ll model our Repo class as simple as possible to keep all the focus on the Paging library. This will be our Repo class:
Exit fullscreen mode
The GithubApi that will be used to retrieve data is:
Exit fullscreen mode
Setup
Add the following to your app’s build.gradle.
Exit fullscreen mode
Data Source
Let’s start building our PagingSource which will load more and more data once the scroll toward the end.
Exit fullscreen mode
The Key and Value parameters from PagingSource are the type of key which define what data to load (Int to represent a page) and the type of data loaded by this PagingSource, respectively.
This GithubRepoPagingSource receives a GithubAPI where the data is retrieved and a username to fetch the repos.
The load() function will be called by the Paging to fetch more data to be displayed to the user and can have this implementation:
Exit fullscreen mode
The refresh key is used for subsequent refresh calls to PagingSource.load() (the first call is initial load which uses initialKey provided by Pager ). A refresh happens whenever the Paging library wants to load new data to replace the current list, e.g., on swipe to refresh or on invalidation due to database updates, config changes, process death, etc. Typically, subsequent refresh calls will want to restart loading data centered around PagingState.anchorPosition which represents the most recently accessed index.
Exit fullscreen mode
Repository
Now that we already implemented our data source, let’s build the Repository.
Exit fullscreen mode
Here we have a searchRepos method that returns a Flow
> . In the Pager object we define the GithubRepoPagingSource created previously and a PagingConfig with the pageSize parameter.
ViewModel
In the ViewModel we are going to expose the Flow
> . Here we are avoiding to request the same username if it was previously requested. Also, we are caching the content of the Flow
Exit fullscreen mode
Adapter
Now we are in the View component, let’s start with the Adapter and make it works with Paging.
Exit fullscreen mode
The implementation of the Adapter is pretty much the same as a usual RecyclerView.Adater , however now we need to extend from PagingDataAdapter and pass a COMPARATOR to its constructor.
Activity/Fragment
We need to launch a new coroutine to search for the repos. We’ll do that in the lifecyclerScope .
We also want to ensure that whenever the user searches for a new username, the previous query is cancelled. To do this, our Activity/Fragment can hold a reference to a new Job that will be cancelled every time we search for a new username.
Exit fullscreen mode
At this point your app is ready and working as expected, however there are more customization that we could do, let’s continue and see them!
Display the loading and error state in the footer
For a better experience, we can display a loading state when the list is fetching new data or display an error when something wrong happens.
For this purpose we need to create a new xml file, a new Adapter and ViewHolder, let’s see them below:
Exit fullscreen mode
Exit fullscreen mode
Now that we have our load state adapter done, we need to link to our list.
Exit fullscreen mode
Empty state
What if we try to load some data but there is no data (list is 0)?
For this purpose we need to be notified when the load state is changed, we’ll use tee addLoadStateListener .
Exit fullscreen mode
Load and error state for the Activity/Fragment
For the first request, until now there is no feedback what is happening in the screen, also if happens some problem we won’t know because there is no error handling.
Update your Activity/Fragment and add a progress bar and a retry button. Don’t forget to set the retry click:
Exit fullscreen mode
Let’s update the previous addLoadStateListener and update the visibility of the progress bar and retry button.
Exit fullscreen mode
This is everything we need to start using Paging 3 in our app!
Conclusion
Paging 3 is a pagination library for Android that is build into the recommended Android app architecture. It uses Flow or LiveData for the communication between the layers.
Also, it handles the load, error and empty state in a simple way, giving us more flexibility for that.
Источник