欢送回到 MAD Skills 系列 课程之 Paging 3.0!在上一篇 Paging 3.0 简介 的文章中,咱们探讨了 Paging 库,理解了如何将它融入到利用架构中,并将其整合进了利用的数据层。咱们应用了 PagingSource
来为咱们的利用获取并应用数据,以及用 PagingConfig
来创立可能提供 Flow<PagingData>
给 UI 生产的 Pager
对象。在本文中我将介绍如何在您的 UI 中理论应用 Flow<PagingData>
。
为 UI 筹备 PagingData
利用现有的 ViewModel
裸露了可能提供渲染 UI 所需信息的 UiState
数据类,它蕴含一个 searchResult
字段,用于将搜寻后果缓存在内存中,可在配置变更后提供数据。
data class UiState(
val query: String,
val searchResult: RepoSearchResult
)
sealed class RepoSearchResult {data class Success(val data: List<Repo>) : RepoSearchResult()
data class Error(val error: Exception) : RepoSearchResult()}
△ 初始 UiState 定义
当初接入 Paging 3.0,咱们移除了 UiState
中的 searchResult
,并抉择在 UiState
之外独自暴露出一个 PagingData<Repo>
的 Flow
来代替它。这个新的 Flow
性能与 searchResult
雷同: 提供一个让 UI 渲染的我的项目列表。
ViewModel
中增加了一个公有的 "searchRepo()"
办法,它调用 Repository
来提供 Pager
中的 PagingData Flow
。咱们能够调用该办法来创立基于用户输出搜索词的 Flow<PagingData<Repo>>
。咱们还在生成的 PagingData Flow
上应用了 cachedIn
操作符,使其可能通过 ViewModelScope
疾速复用。
class SearchRepositoriesViewModel(
private val repository: GithubRepository,
…
) : ViewModel() {
…
private fun searchRepo(queryString: String): Flow<PagingData<Repo>> =
repository.getSearchResultStream(queryString)
}
△ 为仓库集成 PagingData Flow
裸露一个独立于其它 Flow 的 PagingData Flow 这一点十分重要。因为 PagingData
本身是一个可变类型,它外部保护了本人的数据流并且会随着工夫的变动而更新。
随着组成 UiState
字段的 Flow
全副被定义,咱们能够将其组合成 UiState
的 StateFlow
,并和 PagingData
的 Flow
一起裸露进去给 UI 生产。实现这些之后,当初咱们能够开始在 UI 中生产咱们的 Flow
了。
class SearchRepositoriesViewModel(…) : ViewModel() {
val state: StateFlow<UiState>
val pagingDataFlow: Flow<PagingData<Repo>>
init {
…
pagingDataFlow = searches
.flatMapLatest {searchRepo(queryString = it.query) }
.cachedIn(viewModelScope)
state = combine(...)
}
}
△ 裸露 PagingData Flow 给 UI 留神 cachedIn 运算符的应用
在 UI 中生产 PagingData
首先咱们要做的就是将 RecyclerView Adapter
从 ListAdapter 切换到 PagingDataAdapter
。PagingDataAdapter
是为比拟 PagingData
的差别并聚合更新而优化的 RecyclerView Adapter
,用以确保后盾数据集的变动可能尽可能高效地传递。
// 之前
// class ReposAdapter : ListAdapter<Repo, RepoViewHolder>(REPO_COMPARATOR) {
// …
// }
// 之后
class ReposAdapter : PagingDataAdapter<Repo, RepoViewHolder>(REPO_COMPARATOR) {…}
view raw
△ 从 ListAdapter 切换到 PagingDataAdapter
接下来,咱们开始从 PagingData Flow
中收集数据,咱们能够这样应用 submitData
挂起函数将它的发射绑定到 PagingDataAdapter
。
private fun ActivitySearchRepositoriesBinding.bindList(
…
pagingData: Flow<PagingData<Repo>>,
) {
…
lifecycleScope.launch {pagingData.collectLatest(repoAdapter::submitData)
}
}
△ 应用 PagingDataAdapter 生产 PagingData 留神 colletLatest 的应用
此外,为了用户体验着想,咱们心愿确保当用户搜寻新内容时,将回到 列表的顶部 以展现第一条搜寻后果。咱们冀望在 咱们加载实现并已将数据展现到 UI 时做到这一点。咱们通过利用 PagingDataAdapter
裸露的 loadStateFlow
和 UiState
中的 "hasNotScrolledForCurrentSearch"
字段来跟踪用户是否手动滚动列表。联合这两者能够创立一个标记让咱们晓得是否应该触发主动滚动。
因为 loadStateFlow
提供的加载状态与 UI 显示的内容同步,咱们能够有把握地在每次 loadStateFlow
告诉咱们新的查问处于 NotLoading
状态时滚动到列表顶部。
private fun ActivitySearchRepositoriesBinding.bindList(
repoAdapter: ReposAdapter,
uiState: StateFlow<UiState>,
pagingData: Flow<PagingData<Repo>>,
…
) {
…
val notLoading = repoAdapter.loadStateFlow
// 仅当 PagingSource 的 refresh (LoadState 类型) 产生扭转时发射
.distinctUntilChangedBy {it.source.refresh}
// 仅响应 refresh 实现,也就是 NotLoading。.map {it.source.refresh is LoadState.NotLoading}
val hasNotScrolledForCurrentSearch = uiState
.map {it.hasNotScrolledForCurrentSearch}
.distinctUntilChanged()
val shouldScrollToTop = combine(
notLoading,
hasNotScrolledForCurrentSearch,
Boolean::and
)
.distinctUntilChanged()
lifecycleScope.launch {
shouldScrollToTop.collect { shouldScroll ->
if (shouldScroll) list.scrollToPosition(0)
}
}
}
△ 实现有新查问时主动滚动到顶部
增加头部和尾部
Paging 库的另一个长处是在 LoadStateAdapter
的帮忙下,可能在页面的顶部或底部显示进度指示器。RecyclerView.Adapter
的这一实现可能在 Pager
加载数据时主动对其进行告诉,使其能够依据须要在列表顶部或底部插入我的项目。
而它的精华是您甚至不须要扭转现有的 PagingDataAdapter
。withLoadStateHeaderAndFooter
扩大函数能够很不便地应用头部和尾部包裹您已有的 PagingDataAdapter
。
private fun ActivitySearchRepositoriesBinding.bindState(
uiState: StateFlow<UiState>,
pagingData: Flow<PagingData<Repo>>,
uiActions: (UiAction) -> Unit
) {val repoAdapter = ReposAdapter()
list.adapter = repoAdapter.withLoadStateHeaderAndFooter(header = ReposLoadStateAdapter { repoAdapter.retry() },
footer = ReposLoadStateAdapter {repoAdapter.retry() }
)
}
△ 头部和尾部
withLoadStateHeaderAndFooter
函数的参数中为头部和尾部都定义了 LoadStateAdapter
。这些 LoadStateAdapter
相应地托管了本身的 ViewHolder
,这些 ViewHolder
与最新的加载状态绑定,因而很容易定义视图行为。咱们还能够传入参数实现当呈现谬误时重试加载,我将会在下一篇文章中具体介绍。
后续
咱们曾经将 PagingData 绑定到了 UI 上!来疾速回顾一下:
- 应用 PagingDataAdapter 将咱们的 Paging 集成到 UI 上
- 应用 PagingDataAdapter 裸露的 LoadStateFlow 来保障仅当 Pager 完结加载时滚动到列表的顶部
- 应用 withLoadStateHeaderAndFooter() 实现当获取数据时将加载栏增加到 UI 上
感谢您的浏览!敬请关注下一篇文章,咱们将探讨用 Paging 实现以数据库作为繁多起源,并具体探讨 LoadStateFlow
!
欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!