作者 / Florina Muntenescu
Paging 3 亮点
Paging 3 的 API 对分页加载时可能需要实现的常见功能提供了支持:
跟踪获取前一页或后一页所需要的参数;
当用户滚动到现有数据的末尾时,自动请求正确的下一页;
确保不会同时触发多个请求;
跟踪加载状态,并支持您在 RecyclerView 的列表项或者界面中的其他地方展示它。为失败的加载提供简便的重试功能;
无论您是否使用 Flow、LiveData、RxJava Flowable 或 Observable,都可以对需要展示的列表使用 map 或 filter 这类常见的操作;
提供实现列表分隔符的简便方法;
简化了数据缓存,确保不会让您在每次配置更改时都执行数据转换。
在您的应用中使用 Paging 3
Paging 组件及其在应用架构的集成
定义数据源
数据源的定义取决于您从哪里加载数据。您仅需实现 PagingSource 或者 PagingSource 与 RemoteMediator 的组合:
如果您从单个源加载数据,例如网络、本地数据、文件等,实现 PagingSource 即可,如果您使用了 Room,从 2.3.0-alpha 开始,它将默认为您实现 Paging Source,请参见: Android 开发文档|使用 Room DAO 访问数据;
如果您从一个多层级数据源加载数据,就像带有本地数据库缓存的网络数据源那样。那么您需要实现 RemoteMediator 来合并两个数据源到一个本地数据库缓存的 PagingSource 中。
class DoggosRemotePagingSource(val backend: GoodDoggosService) : PagingSource<Int, Dog>() {override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Dog> {try {// 未定义时加载第 1 页val nextPageNumber = params.key ?: 1val response = backend.getDoggos(nextPageNumber)return LoadResult.Page(data = response.doggos,prevKey = null, // 仅向后翻页nextKey = response.nextPageNumber + 1)} catch (e: Exception) {// 在此块中处理错误return LoadResult.Error(exception)}}}
PagingData 与 Pager
分页数据的容器被称为 PagingData,每次刷新数据时,都会创建一个 PagingData 的实例。如果要创建 PagingData 数据流,您需要创建一个 Pager 实例,并提供一个 PagingConfig 配置对象和一个可以告诉 Pager 如何获取您实现的 PagerSource 的实例的函数,以供 Pager 使用。
val doggosPagingFlow = Pager(PagingConfig(pageSize = 10)) {DogRemotePagingSource(goodDoggosService)}.flow.cachedIn(viewModelScope)
PagingDataAdapter
class DogAdapter(diffCallback: DiffUtil.ItemCallback<Dog>) :PagingDataAdapter<Dog, DogViewHolder>(diffCallback) {override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): DogViewHolder {return DogViewHolder(parent)}override fun onBindViewHolder(holder: DogViewHolder, position: Int) {val item = getItem(position)if(item == null) {holder.bindPlaceholder()} else {holder.bind(item)}}}
val viewModel by viewModels<DoggosViewModel>()val pagingAdapter = DogAdapter(DogComparator)val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)recyclerView.adapter = pagingAdapterlifecycleScope.launch {viewModel.doggosPagingFlow.collectLatest { pagingData ->pagingAdapter.submitData(pagingData)}}
分页数据转换
展示一个过滤后的列表
doggosPagingFlow.map { pagingData ->pagingData.filter { dog -> dog.isPlayful }}
有分隔符的列表
pager.flow.map { pagingData: PagingData<Dog> ->pagingData.map { doggo ->// 将数据流中的项目转换为 UiModel.DogModel。UiModel.DogModel(doggo)}.insertSeparators<UiModel.DogModel, UiModel> { before: Dog, after: Dog ->return if(after == null) {// 我们到了列表的末尾null} else if (before == null || before.breed != after.breed) {// 上下品种不同,显示分隔符UiModel.SeparatorItem(after.breed)} else {// 无分隔符null}}}}.cachedIn(viewModelScope)
使用 RemoteMediator 进行高级分页操作
override suspend fun load(loadType: LoadType, state: PagingState<Int, Dog>): MediatorResult {val page = ... // 基于 loadType 和 state 进行计算try {val doggos = backend.getDoggos(page)doggosDatabase.doggosDao().insertAll(doggos)val endOfPaginationReached = emails.isEmpty()return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)} catch (exception: Exception) {return MediatorResult.Error(exception)}}
fun getDoggos(): PagingSource<Int, Dog>
val pagingSourceFactory = { database.doggosDao().getDoggos() }return Pager(config = PagingConfig(pageSize = NETWORK_PAGE_SIZE),remoteMediator = DoggosRemoteMediator(service, database),pagingSourceFactory = pagingSourceFactory).flow
Paging 3 仍然处于 alpha 版本,我们需要您帮助我们进一步优化!请参阅以下资源开始使用 Paging:
Android 开发文档|Paging 3 库概述
https://developer.android.google.cn/topic/libraries/architecture/paging/v3-overview
Codelab|Android Paging
https://codelabs.developers.google.com/codelabs/android-paging
代码示例|Paging With Network Sample
https://github.com/android/architecture-components-samples/tree/master/PagingWithNetworkSample
IssueTracker
https://issuetracker.google.com/issues?q=componentid:413106%20status:open
推荐阅读