You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Paging3切换分类时RecyclerView短暂显示旧数据问题求助

问题描述

UI包含RecyclerView和ChipGroup,每个Chip对应不同的Retrofit请求。点击Chip时,期望RecyclerView在新数据加载完成前隐藏,实际确实隐藏了,但重新显示时会先短暂展示旧数据,之后才更新为新数据。


Fragment代码实现

分页数据与加载状态观察

viewLifecycleOwner.lifecycleScope.launch {
    pagingData.collectLatest(customAdapter::submitData)
}

viewLifecycleOwner.lifecycleScope.launch {
    customAdapter.loadStateFlow.collect { loadState ->
        header.loadState = loadState.mediator
            ?.refresh
            ?.takeIf { it is LoadState.Error && customAdapter.itemCount > 0 }
            ?: loadState.prepend

        val isListEmpty = loadState.refresh is LoadState.NotLoading && customAdapter.itemCount == 0
        emptyList.isVisible = isListEmpty
        recyclerView.isVisible = loadState.mediator?.refresh is LoadState.NotLoading ||  loadState.source.refresh is LoadState.NotLoading
        progressBar.isVisible = loadState.mediator?.refresh is LoadState.Loading
        retryButton.isVisible = loadState.mediator?.refresh is LoadState.Error && customAdapter.itemCount == 0
        val errorState = loadState.source.append as? LoadState.Error
            ?: loadState.source.prepend as? LoadState.Error
            ?: loadState.append as? LoadState.Error
            ?: loadState.prepend as? LoadState.Error
        errorState?.let {
            Toast.makeText(
                requireContext(),
                "😨 Wooops ${it.error}",
                Toast.LENGTH_LONG
            ).show()
        }
    }
}

分类切换逻辑

chip1.setOnClickListener {
    viewLifecycleOwner.lifecycleScope.launch {
        onCategoryChanged(CustomAction.ChangeCategory(category = CustomCategory.A))
    }
}
chip2.setOnClickListener {
    viewLifecycleOwner.lifecycleScope.launch {
        customAdapter.refresh()
        onCategoryChanged(CustomAction.ChangeCategory(category = CustomCategory.B))
    }
}

ViewModel代码实现
val actionStateFlow = MutableSharedFlow<CustomAction>()
val changedCategory = actionStateFlow
    .filterIsInstance<CustomAction.ChangeCategory>()
    .distinctUntilChanged()
    .onStart { emit(CustomAction.ChangeCategory(category = initialCategory)) }
pagingDataFlow = state
    .flatMapLatest {
        when (it.category) {
            CustomCategory.A ->
                searchA(query = it.query)
            CustomCategory.B ->
                searchB(query = it.query)
        }
    }
    .cachedIn(viewModelScope)

private fun searchA(query: String): Flow<PagingData<CustomItem>> {
    return repository.getA(query = query).map { pagingData ->
        pagingData.map { it.mapToDomainModel() }
    }
}

private fun searchB(query: String): Flow<PagingData<CustomItem>> {
    return repository.getB(query = query).map { pagingData ->
        pagingData.map { it.mapToDomainModel() }
    }
}

修复方案

1. 移除多余的customAdapter.refresh()调用

切换分类时,ViewModel会通过flatMapLatest自动切换到新的分页数据流,collectLatest会自动提交新的PagingData到Adapter。手动调用refresh()会触发旧数据流的刷新操作,导致旧数据残留闪烁。修改Chip点击逻辑:

chip2.setOnClickListener {
    viewLifecycleOwner.lifecycleScope.launch {
        onCategoryChanged(CustomAction.ChangeCategory(category = CustomCategory.B))
    }
}

2. 优化RecyclerView显示控制逻辑

当前的显示条件会在刷新状态变为NotLoading时立即显示RecyclerView,但此时新数据可能还未完全替换旧数据。调整逻辑,确保只有在刷新完成且有新数据时才显示RecyclerView:

viewLifecycleOwner.lifecycleScope.launch {
    customAdapter.loadStateFlow.collect { loadState ->
        header.loadState = loadState.mediator
            ?.refresh
            ?.takeIf { it is LoadState.Error && customAdapter.itemCount > 0 }
            ?: loadState.prepend

        val isRefreshing = loadState.mediator?.refresh is LoadState.Loading || loadState.source.refresh is LoadState.Loading
        val isRefreshCompleted = loadState.mediator?.refresh is LoadState.NotLoading || loadState.source.refresh is LoadState.NotLoading
        val hasData = customAdapter.itemCount > 0
        val isListEmpty = isRefreshCompleted && !hasData

        emptyList.isVisible = isListEmpty
        // 仅当刷新完成且有数据时才显示RecyclerView
        recyclerView.isVisible = !isRefreshing && isRefreshCompleted && hasData
        progressBar.isVisible = isRefreshing
        retryButton.isVisible = (loadState.mediator?.refresh is LoadState.Error || loadState.source.refresh is LoadState.Error) && !hasData
        
        val errorState = loadState.source.append as? LoadState.Error
            ?: loadState.source.prepend as? LoadState.Error
            ?: loadState.append as? LoadState.Error
            ?: loadState.prepend as? LoadState.Error
        errorState?.let {
            Toast.makeText(
                requireContext(),
                "😨 Wooops ${it.error}",
                Toast.LENGTH_LONG
            ).show()
        }
    }
}

3. 确保数据流切换的原子性

ViewModel中使用flatMapLatest是正确的,它会在新的分类状态到来时立即取消旧的分页数据流请求,避免旧数据干扰。如果你的state流是基于changedCategory生成的,确保scan或其他转换逻辑正确,保证每次分类变化都会生成全新的状态对象,触发flatMapLatest切换数据流。


内容的提问来源于stack exchange,提问作者ParSerDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:25:22