为 UI 准备 PagingData
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()}
class SearchRepositoriesViewModel(private val repository: GithubRepository,…) : ViewModel() {…private fun searchRepo(queryString: String): Flow<PagingData<Repo>> =repository.getSearchResultStream(queryString)}
class SearchRepositoriesViewModel(…: ViewModel() {val state: StateFlow<UiState>val pagingDataFlow: Flow<PagingData<Repo>>init {…pagingDataFlow = searches{ searchRepo(queryString = it.query) }.cachedIn(viewModelScope)state = combine(...)}}
在 UI 中消费 PagingData
// 之前// 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 的使用
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() })}
后续
使用 PagingDataAdapter 将我们的 Paging 集成到 UI 上
使用 PagingDataAdapter 暴露的 LoadStateFlow 来保证仅当 Pager 结束加载时滚动到列表的顶部
使用 withLoadStateHeaderAndFooter() 实现当获取数据时将加载栏添加到 UI 上
推荐阅读