如何使用 Android androidx.paging.PagingDataAdapter loadStateFlow 显示 Empty、Loading 和 Loaded UI 状态

Hec*_*tor 5 android kotlin android-room android-paging-3

我当前的 Android 应用程序使用 Room Paging3 库

implementation 'androidx.paging:paging-runtime-ktx:3.0.0-beta01'
Run Code Online (Sandbox Code Playgroud)

我的要求是显示“数据加载微调器”、没有可用数据时的空消息或已加载的数据。

我可以成功显示加载微调器或加载时的数据,但是当没有数据可显示时,它不起作用。

我已经尝试过 SO 上提供的解决方案,但是没有一个能解决我的问题

我的代码成功处理加载和加载数据的版本类似于:-

viewLifecycleOwner.lifecycleScope.launch {
            adapter.loadStateFlow
                .distinctUntilChangedBy { it.refresh }
                .filter { it.refresh is LoadState.NotLoading }
                .flowOn(Dispatchers.Default)
                .collectLatest {
                    withContext(Dispatchers.Main) {
                        if (adapter.itemCount == 0) (viewModel as HomeViewModel).loading.value = View.VISIBLE
                        else (viewModel as HomeViewModel).loading.value = View.GONE
                    }
                }
        }
Run Code Online (Sandbox Code Playgroud)

我也尝试使用这个投票给 SO 答案来显示空状态,但是它对我不起作用

adapter.addLoadStateListener { loadState ->
            if (loadState.source.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached && adapter.itemCount < 1) {
                recycleView?.isVisible = false
                emptyView?.isVisible = true
            } else {
                recycleView?.isVisible = true
                emptyView?.isVisible = false
            }
        }
Run Code Online (Sandbox Code Playgroud)

该问题是由 loadStateFlow 发出此条件引起的

(loadState.source.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached && adapter.itemCount < 1)
Run Code Online (Sandbox Code Playgroud)

正在加载数据时。

我的列表屏幕具有拉动刷新功能,在数据刷新过程中,我首先删除所有现有行,然后远程获取所有数据。

我可以用它PagingDataAdapter.loadStateFlow来显示“正在加载”、“已加载”或“空”三种状态吗?

我的ViewModel代码类似于:-

repository.myData()
          .map { pagingData ->
                 pagingData.map { myDO -> myDO.mapUI() }
               }
           .cachedIn(viewModelScope)
           .collect {
                emit(it)
                    }
Run Code Online (Sandbox Code Playgroud)

我的Repository代码类似于:-

fun myData(): Flow<PagingData<MyDO>> {
        return Pager(
            PagingConfig(pageSize = 60, prefetchDistance = 30, enablePlaceholders = false, maxSize = 200)
        ) {
            database.myDAO().fetchMyData()
        }.flow.flowOn(Dispatchers.IO)
    }
Run Code Online (Sandbox Code Playgroud)

我的房间查询类似于此

@Query("SELECT * from my_table ORDER BY position_date DESC")
fun fetchMyData(): PagingSource<Int, MyDO>
Run Code Online (Sandbox Code Playgroud)

我哪里出错了?

是否可以实现我想要的功能?

更新

我相信我有两个不同的问题。

第一个是由我的初始数据同步引起的,我使用 Android Workers 从远程源下载数据并将其插入到我的本地 Room 数据库中。由于远程数据的组织方式和可用的 Restful API,列表中显示的数据可以由多个工作人员插入。本地插入的数据的这种“分区”导致提交到我的 Paging3 适配器的数据是零星的,并且意味着加载状态会循环加载 -> 未加载 -> 加载 -> 未加载。

我在同步进行时显示列表片段,这导致屏幕内容在加载微调器和显示的数据列表之间“闪烁”

ami*_*phy 8

我开发了一种从 的实例检索加载错误CombinedLoadStates状态的方法。您可以像下面这样使用它:

adapter.addLoadStateListener { loadState ->
    loadState.decideOnState(
        showLoading = { visible ->
            (viewModel as HomeViewModel).loading.value =
                if (visible) View.VISIBLE else View.GONE
        },
        showEmptyState = { visible ->
            emptyView?.isVisible = visible
            recycleView?.isVisible = !visible
        },
        showError = { message ->
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
        }
    )
}
Run Code Online (Sandbox Code Playgroud)
private inline fun CombinedLoadStates.decideOnState(
    showLoading: (Boolean) -> Unit,
    showEmptyState: (Boolean) -> Unit,
    showError: (String) -> Unit
) {
    showLoading(refresh is LoadState.Loading)

    showEmptyState(
        source.append is LoadState.NotLoading
                && source.append.endOfPaginationReached
                && adapter.itemCount == 0
    )

    val errorState = source.append as? LoadState.Error
        ?: source.prepend as? LoadState.Error
        ?: source.refresh as? LoadState.Error
        ?: append as? LoadState.Error
        ?: prepend as? LoadState.Error
        ?: refresh as? LoadState.Error

    errorState?.let { showError(it.error.toString()) }
}
Run Code Online (Sandbox Code Playgroud)

  • 非常优雅的解决方案 (2认同)

sho*_*ano 1

如果不实际检查本地数据库数据,我就无法做到这一点,因此我决定在进行刷新操作时实际检查本地数据库是否数据为空,如下所示:

val isRefreshLoading = states.refresh is LoadState.Loading
val isEmptyViewVisible = !isRefreshLoading && viewModel.isUnreadEmpty()

binding.refresher.isRefreshing = isRefreshLoading
binding.emptyView.isVisible = isEmptyViewVisible
Run Code Online (Sandbox Code Playgroud)

ViewModel.isUnreadEmpty() 通过调用 room dao 函数检查数据库中的数据是否为空。