Android Room Paging3 动态过滤的正确实现方法

6

我正在调查新的Android Room分页库

   implementation "androidx.paging:paging-runtime-ktx:3.0.0-alpha09"

我的源数据库表大约有10,000行,我按照名称字段的首个字符进行筛选,如下所示:

DAO

@Query("SELECT * from citation_style WHERE citation_style_name LIKE :startsWith ORDER BY citation_style_name ASC")
fun fetch(startsWith: String): PagingSource<Int, CitationStyleDO>

代码库

fun fetch(startsWith: String): Flow<PagingData<CitationStyleDO>> {
    return Pager(
        PagingConfig(pageSize = 60, prefetchDistance = 30, enablePlaceholders = false, maxSize = 200)
    ) { database.citationStyleDao().fetch("$startsWith%") }.flow
}

视图模型

fun fetch(startsWith: String): Flow<PagingData<CitationStyleDO>> {
    return repository.fetch(startsWith).cachedIn(viewModelScope)
}

碎片

override fun onStartsWithClicked(startsWith: String) {
    lifecycleScope.launch {
        viewModel.fetch(startsWith).collectLatest { adapter.submitData(it) }
    }
}

每次更改Starts With字符时重复使用 lifecycleScope.launch {...} 是正确的方法吗?

对于StartsWith,我应该使用由 MutabaleLiveData<String> 触发的 map{}switchMap{}

1个回答

9

这样做行不通,因为submitData函数要等到PagingData失效才会返回。你可能会遇到竞争情形,其中PagingDataAdapter尝试从多个PagingData中收集数据。

更"Flow"的方式是将您的获取调用转换为一个flow,并将其与Flow<PagingData>结合使用,这样每次查询更改时,它将自动传播取消。

还有一些事项:

建议让Paging为您过滤数据,这样您就可以避免在搜索更改时重新从数据库获取数据,而且还可以依赖Paging来处理配置更改和恢复状态。

应该使用viewLifecycleOwner而不是直接使用lifecycleScope,因为您不希望在片段的视图销毁后继续由Paging执行工作。

例如:

ViewModel

val queryFlow = MutableStateFlow("init_query")
val pagingDataFlow = Pager(...) {
        dao.pagingSource()
    }.flow
    // This multicasts, to prevent combine from refetching
    .cachedIn(viewModelScope)
    .combine(queryFlow) { pagingData, query -> 
        pagingData.filter { it.startsWith(query)
    }
    // Optionally call .cachedIn() here a second time to cache the filtered results. 

碎片

override fun onStartsWithClicked(startsWith: String) {
    viewModel.queryFlow = startsWith
}

override fun onViewCreated(...) {
     viewLifecycleOwner.lifecycleScope.launch {
viewModel.pagingDataFlow.collectLatest { adapter.submitData(it) }
}

注意:如果你愿意,你肯定可以使用 Room 进行过滤,可能正确的方法是在 queryFlow 上使用 .flatMapLatest,并每次返回一个新的 Pager,然后将查询条件传递给返回 PagingSource 的 dao 函数。
视图模型
queryFlow.flatMapLatest { query ->
    Pager(...) { dao.pagingSource(query) }
        .cachedIn(...)
}

如何同时进行过滤和 API 调用?我猜您可以进行一个 API 调用,将响应保存到数据库中,稍后再发出到 PagingSource。 - Paul Okeke
这里的API调用是什么意思?你是指远程获取更多项目吗?这些在分页中发生在不同的层,因此不会改变您在我的答案中示例中应用过滤器运算符的方式。 - dlam
这里合并的问题在于,已经被删除的页面不会被正确过滤,所以过滤所有内容的唯一方法是让 room 进行过滤。 - DennisVA
@DennisVA 如果一个页面被删除了,它在用户界面上就不会再显示出来,如果重新插入,过滤转换将再次运行。PagingData转换是增量的,因此您永远不必担心它们会“丢失”页面。 - dlam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接