Paging 3.0 简介 | MAD Skills
△ MAD Skills 系列之 Paging 第一集
为什么使用 Paging 3.0?
优先支持 Kotlin 协程和 Flow。 支持通过 RxJava Single 或 Guava ListenableFuture 原语进行异步加载。 为响应式 UI 设计提供了内建的加载状态和错误信号,包括重试和刷新功能。 改进仓库层,包含对于可取消的支持及简化数据源接口。 改进表现层、列表分隔符、自定义页面转换以及加载状态头、脚标。
如需获取更多内容信息,请查阅 Paging 2.0 到 Paging 3.0 的迁移文档:
置入数据
在您应用的架构方案中,Paging 3.0 最适合作为从数据层获取数据并通过 ViewModel 在 UI 层传输数据来对其进行转换和呈现的一种方式。在 Paging 3.0 中,我们通过名为 PagingSource 的类型访问您的数据层,该类型定义了如何围绕 PagingConfig 所定义的范围获取和刷新数据。
PagingSource 和 Map 类似,都需要定义两个泛型类型: 分页的 Key 的类型和加载的数据的类型。举例来说,从基于 Github API 的页面获取 Repo 项目的 PagingSource 的声明,可以定义为:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
class GithubPagingSource(
…
) : PagingSource<Int, Repo>()
功能完整的 PagingSource 需要实现两个抽象方法:
load() getRefreshKey()
load 方法
load() 方法正如其名,是由 Paging 库所调用的,用于异步加载要显示的数据的方法。这一方法会在初始加载或者响应用户滑动至边界时调用。load 方法会传入一个 LoadParams 对象,您可以通过它来确定如何触发 load 方法的调用。此对象中包含了有关 load 操作的信息,包括:
将要加载的页面的 Key: 如果这是 load 方法第一次被调用 (初始加载),LoadParams.key 将会是 null。在这种情况下,您必须定义初始页面 Key。
加载大小: 请求所要加载的项目的数量。
load 方法的返回类型是 LoadResult。它可以是:
LoadResult.Page: 针对加载成功。
LoadResult.Error: 针对加载失败。
/* 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)
}
}
注意,默认情况下,初始加载大小为分页大小的三倍。这样可以保证在列表第一次加载时,即使用户稍作滚动,也能看到足够的数据,从而避免触发太多网络请求。这也是在 PagingSource 实现中计算下一个 Key 时所需要考虑的事情。
getRefreshKey 方法
/* 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 方法实现
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
}
}
pageSize: 每次要从 PagingSource 加载项目的数量。 enablePlaceholders: 是否需要 PagingData 为尚未加载的数据返回 null。
获取您的数据
Kotlin Flow 通过 Pager.flow LiveData 通过 Pager.liveData RxJava Flowable 通过 Pager.flowable RxJava Observable 通过 Pager.observable
后续
推荐阅读