乐趣区

关于android:获取数据并绑定到-UI-MAD-Skills

欢送回到 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 全副被定义,咱们能够将其组合成 UiStateStateFlow,并和 PagingDataFlow 一起裸露进去给 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 切换到 PagingDataAdapterPagingDataAdapter 是为比拟 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 裸露的 loadStateFlowUiState 中的 "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 加载数据时主动对其进行告诉,使其能够依据须要在列表顶部或底部插入我的项目。

而它的精华是您甚至不须要扭转现有的 PagingDataAdapterwithLoadStateHeaderAndFooter 扩大函数能够很不便地应用头部和尾部包裹您已有的 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

欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!

退出移动版