欢迎光临
我们一直在努力

Paging 3 与 RemoteMediator:列表分页工程

文章目录

  • 第 1 章 全量加载的代价
  • 第 2 章 PagingSource
  • 第 3 章 Pager 与 ViewModel
  • 第 4 章 UI:Compose 与 RecyclerView
  • 第 5 章 RemoteMediator
  • 第 6 章 工程策略
      • 踩坑
  • 第 7 章 治理清单
  • 面试速查 · 追问链
      • 追问链 #1:PagingSource vs RemoteMediator? 🔥
      • 追问链 #2:refresh 键? 🔥
      • 追问链 #3:与 Flow 列表区别? ⭐
      • 追问链 #4:RxJava Paging2? 💡
  • 完整链路一句通
  • 相关推荐

第 1 章 全量加载的代价

首屏等全量 JSON、内存暴涨、旋转丢失页码——Paging 3 用增量加载 + 占位解决。

PagingSource(单页) → Pager → Flow<PagingData<T>> → UI collect
RemoteMediator(可选)→ Room 作缓存


第 2 章 PagingSource

class ArticlePagingSource(
private val api: ArticleApi,
) : PagingSource<Int, Article>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
val page = params.key ?: 0
return try {
val items = api.getArticles(page, params.loadSize).map { it.toDomain() }
LoadResult.Page(
data = items,
prevKey = if (page == 0) null else page 1,
nextKey = if (items.isEmpty()) null else page + 1,
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, Article>): Int? =
state.anchorPosition?.let { pos ->
state.closestPageToPosition(pos)?.prevKey?.plus(1)
?: state.closestPageToPosition(pos)?.nextKey?.minus(1)
}
}


第 3 章 Pager 与 ViewModel

class ArticleViewModel(api: ArticleApi) : ViewModel() {
val articles: Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
pagingSourceFactory = { ArticlePagingSource(api) },
).flow.cachedIn(viewModelScope)
}

cachedIn(viewModelScope) 旋转后保留已加载页。


第 4 章 UI:Compose 与 RecyclerView

Compose:

val articles = viewModel.articles.collectAsLazyPagingItems()
LazyColumn {
items(count = articles.itemCount) { index ->
articles[index]?.let { ArticleRow(it) }
}
}

RecyclerView:PagingDataAdapter + submitData(lifecycle, pagingData)。


第 5 章 RemoteMediator

@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
private val api: ArticleApi,
private val db: ArticleDao,
) : RemoteMediator<Int, ArticleEntity>() {

override suspend fun load(
loadType: LoadType,
state: PagingState<Int, ArticleEntity>,
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> 0
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> state.lastItemOrNull()?.page?.plus(1) ?: 0
}
return try {
val remote = api.getArticles(page, state.config.pageSize)
db.withTransaction {
if (loadType == LoadType.REFRESH) db.clearAll()
db.insertAll(remote.map { it.toEntity(page) })
}
MediatorResult.Success(endOfPaginationReached = remote.isEmpty())
} catch (e: Exception) {
MediatorResult.Error(e)
}
}
}

Pager 的 remoteMediator + Room PagingSource 读本地。


第 6 章 工程策略

  • 筛选条件变化:flatMapLatest 重建 Pager。
  • 头部/分隔:用 insertHeaderItem 或 UI 层组合。
  • 测试:PagingSource 单测 load;Mediator 用 fake API + 内存 DB。

踩坑

  • 忘记 cachedIn → 旋转重复请求。
  • enablePlaceholders = true 但无 placeholder 实现 → 闪烁。

第 7 章 治理清单

检查项通过标准
加载 无手写页码全局变量
缓存 离线需求有 RemoteMediator + Room
生命周期 cachedIn(viewModelScope)
错误 LoadResult.Error / MediatorResult.Error 映射 UI
刷新 下拉刷新走 PagingDataAdapter.refresh() 或 LazyPagingItems.refresh()

面试速查 · 追问链

追问链 #1:PagingSource vs RemoteMediator? 🔥

参考回答:Source 管单数据源分页;Mediator 协调网络写库+本地 Source 读库,做离线。

追问链 #2:refresh 键? 🔥

参考回答:getRefreshKey 根据 PagingState 锚点算恢复页,避免刷新后跳顶错乱。

追问链 #3:与 Flow 列表区别? ⭐

参考回答:全量 Flow 适合小列表/库观察;大列表分页用 Paging 省内存与网络。

追问链 #4:RxJava Paging2? 💡

参考回答:遗留;新工程 Paging 3 + 协程 Flow。


完整链路一句通

PagingSource/Mediator 产页 → Pager.flow.cachedIn → ViewModel 暴露 Flow → LazyColumn/Adapter 增量渲染。


请添加图片描述

相关推荐

  • Kotlin 作用域函数:let/apply 怎么选

  • Kotlin 语法与空安全:从零的第一课

赞(0)
未经允许不得转载:171主机测评 » Paging 3 与 RemoteMediator:列表分页工程
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址