Welcome to read Paging 3.0 of MAD Skills series In this article, I will introduce Paging 3.0 and focus on how to integrate it into the data layer of your application. If you prefer a video about this content, please here view.
Why use Paging 3.0?
Presenting a column of data to the user is one of the most common UI patterns. When you need to load a large amount of data, you can obtain/display data asynchronously in blocks to improve application performance. This pattern is so common, if there are dependent libraries that can provide abstractions that facilitate the realization of this pattern, it will bring great convenience to developers. This is the use case that Paging 3.0 strives to solve. As an additional benefit, it also allows your application to support an unlimited data collection; and if your application loads data over the network, it also provides convenience for supporting local caching.
If you are using Paging 2.0, then Paging 3.0 also provides a series of improvements to the features included in its predecessor:
- Priority support for Kotlin coroutines and Flow.
- Supports asynchronous loading via RxJava Single or Guava ListenableFuture primitives.
- Provides built-in loading status and error signals for responsive UI design, including retry and refresh functions.
- Improve the warehouse layer, including support for cancellation and simplified data source interface.
- Improve the presentation layer, list separator, custom page transition, and loading status header and footer.
For more information, please refer to the migration document Paging 2.0 to Paging 3.0.
insert data
In the architecture scheme of your application, Paging 3.0 is most suitable as a way to obtain data from the data layer and transmit the data in the UI layer ViewModel
In Paging 3.0, we PagingSource
, which defines how to get and refresh data PagingConfig
PagingSource
Map
, both of which need to define two generic types: the type of the paged Key and the type of the loaded data. For example, getting the PagingSource
Repo
project from the Github API-based page can be defined as:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
class GithubPagingSource(
…
) : PagingSource<Int, Repo>()
△ PagingSource statement
A fully functional PagingSource needs to implement two abstract methods:
load()
getRefreshKey()
load method
load()
method, as its name suggests, is called by the Paging library to asynchronously load the data to be displayed. This method will be called upon initial loading or in response to the user sliding to the boundary. The load method will pass in a LoadParams object, you can use it to determine how to trigger the call of the load method. This object contains information about the load operation, including:
- Key of the page to be loaded: If this is the first time the load method is called (initial load),
LoadParams.key
will benull
. In this case, you must define the initial page Key. - Load size: The number of items to be loaded in the request.
The return type of the load method is LoadResult
. it can be:
LoadResult.Page
: Targeting successfully loaded.LoadResult.Error
: Failed to load.
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
val apiQuery = query + IN_QUALIFIER
return try {
val response = service.searchRepos(apiQuery, position, params.loadSize)
val repos = response.items
val nextKey = if (repos.isEmpty()) {
null
} else {
// 初始加载大小为 3 * NETWORK_PAGE_SIZE
// 要保证我们在第二次加载时不会去请求重复的项目。
position + (params.loadSize / NETWORK_PAGE_SIZE)
}
LoadResult.Page(
data = repos,
prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
nextKey = nextKey
)
} catch (exception: IOException) {
LoadResult.Error(exception)
} catch (exception: HttpException) {
LoadResult.Error(exception)
}
}
△ load method implementation
Note that by default, the initial load size is three times the page size. This ensures that when the list is loaded for the first time, even if the user scrolls slightly, enough data can be seen, so as to avoid triggering too many network requests. This is also something that needs to be considered when calculating the next Key PagingSource
getRefreshKey method
The refresh key is used for the PagingSource.load()
method (the first call is the initial load, and the initial key provided for Pager is used). Whenever the Paging library wants to load new data to replace the current list (for example, pull-down refresh or database update, configuration changes, process termination, etc., the data becomes invalid), a refresh operation will occur. In general, the follow-up call to refresh want to reload to PagingState.anchorPosition
centric data, and PagingState.anchorPosition
represents the index position recently visited.
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
// 刷新 Key 用于在初始加载的数据失效后下一个 PagingSource 的加载。
override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
// 我们需要获取与最新访问索引最接近页面的前一个 Key(如果上一个 Key 为空,则为下一个 Key)
// anchorPosition 即为最近访问的索引
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
△ getRefreshKey method implementation
Pager Object
After defining PagingSource
, we can now create Pager
. Pager
class is responsible for incrementally pulling data sets PagingSource
according to the request of the UI. Since Pager
needs to access PagingSource
, it is usually created in the data layer that PagingSource
Another class required to construct Pager
PagingConfig
, which defines the Pager
obtains data. In addition to the required pageSize
parameters, PagingConfig
also exposes many optional parameters, you can use them to fine-tune the behavior of Pager
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
private const val NETWORK_PAGE_SIZE = 30
class GithubRepository(private val service: GithubService) {
fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
Log.d("GithubRepository", "New query: $query")
return Pager(
config = PagingConfig(
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = { GithubPagingSource(service, query) }
).flow
}
}
△ Create Pager
A brief description of the parameters used in the code for PagingConfig
above is as follows:
pageSize
: The number of items toPagingSource
each time.enablePlaceholders
: Do you needPagingData
to return null for data that has not been loaded.
Usually we want pageSize
be large enough (at least enough to fill the visible area of the interface, but preferably 2 to 3 times this amount), so that Pager
does not have to be scrolled by the user in order to display enough content on the screen Get data over and over again during operation.
Get your data
Pager
type produced is PagingData
, this type provides access behind PagingSource
different windows. When the user scrolls through the list, PagingData
will continue PagingSource
to provide content. If PagingSource
fails, Pager
will issue a new PagingData
to ensure that the paged items are synchronized with the content displayed in the UI. It may be helpful for your understanding to PagingData
PagingSource
at a certain time node.
Since PagingSource
is PagingSource
fails, the Paging library provides a variety of ways to use PagingData
in the form of streams:
- Kotlin Flow through
Pager.flow
- LiveData through
Pager.liveData
- RxJava Flowable through
Pager.flowable
- RxJava Observable through
Pager.observable
PagingData
can be manipulated and converted ViewModel
before displaying the paging item to the UI.
followed by
Following the above steps, we have integrated Paging 3.0 into the data layer of your application! How to consume PagingData in the UI and populate our warehouse list, please pay attention to our follow-up articles.
Welcome to Click here to submit feedback to us, or share your favorite content or problems found. Your feedback is very important to us, thank you for your support!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。