Android Paging 3应用筛选后调用invalidate()如何清空RecyclerView旧数据
解决方案
出现这个问题的核心原因是 Jetpack Paging 库默认的数据源失效策略:调用invalidate()刷新时,会保留旧列表数据作为占位,直到新数据源完成首次加载,避免网络差时界面出现空白闪烁,和你需要筛选后立刻清空旧数据的需求冲突。你可以用以下两种方案实现预期效果:
方案1:监听加载状态主动清空旧数据(适配现有invalidate逻辑)
你只需要在筛选触发时标记刷新类型,监听PagingDataAdapter的加载状态,当检测到筛选触发的刷新处于加载中时,主动提交空数据清空列表:
// UI层代码,筛选触发时先设置标记位 var isFilterTriggeredRefresh = false fun onFilterConfirm(selectedDates: DateRange, categories: List<String>) { isFilterTriggeredRefresh = true // 更新DataSource里的筛选参数后调用原有invalidate逻辑 currentDataSource.invalidate() } // 监听加载状态处理清空逻辑 lifecycleScope.launch { pagingAdapter.loadStateFlow.collect { loadStates -> val refreshState = loadStates.source.refresh // 筛选触发的刷新进入加载状态时,主动清空旧数据 if (isFilterTriggeredRefresh && refreshState is LoadState.Loading) { pagingAdapter.submitData(PagingData.empty()) isFilterTriggeredRefresh = false } // 原有错误逻辑保留,此时列表已清空,不会残留旧数据 if (refreshState is LoadState.Error) { // 处理错误提示逻辑 } } }
方案2:每次筛选生成全新的数据源(更稳定,无并发问题)
建议优先选择这个方案,不要在DataSource内部持有可变的筛选参数引用,每次筛选时直接传入最新的筛选参数生成新的DataSource,重新提交PagingData,从根源上避免旧数据残留:
第一步:改造DataSource的构造函数,把筛选参数改为不可变传入
class EventDataSource( private val selectedDates: DateRange?, private val categoryFilters: List<String>, private val repository: EventRepository, private val globalData: GlobalData ) : RxPagingSource<Int, Event>() { override fun getRefreshKey(state: PagingState<Int, Event>): Int = 1 override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Event>> { val key = params.key ?: 1 return repository.getCityEvents( cityId = globalData.currentCityId, start = selectedDates?.start?.toApiFormat(), end = selectedDates?.end?.toApiFormat(), pageNo = key, categories = categoryFilters ) .map { it.content } .map { toLoadResult(it, params) } .onErrorReturn { LoadResult.Error(it) } } private fun toLoadResult(data: List<Event>, params: LoadParams<Int>): LoadResult<Int, Event> { val currentKey = params.key ?: 1 return if (data.isEmpty() && currentKey == 1) LoadResult.Error(NoEventsException()) else LoadResult.Page( data = data, prevKey = null, nextKey = if (data.size < params.loadSize) null else currentKey + 1 ) } }
第二步:ViewModel层每次筛选返回新的PagingData
// ViewModel中每次筛选调用该方法获取对应筛选条件的分页数据 fun getEventListByFilter(selectedDates: DateRange?, categories: List<String>): Flow<PagingData<Event>> { return Pager( config = PagingConfig( pageSize = 20, enablePlaceholders = false ) ) { // 每次都用最新的筛选参数创建全新的DataSource实例 EventDataSource(selectedDates, categories, repository, globalData) }.flow.cachedIn(viewModelScope) }
第三步:UI层筛选后直接提交新的PagingData即可
fun onFilterConfirm(selectedDates: DateRange, categories: List<String>) { lifecycleScope.launch { viewModel.getEventListByFilter(selectedDates, categories).collect { pagingData -> pagingAdapter.submitData(pagingData) } } }
额外注意事项
- 两种方案都可以和你现有footer逻辑兼容,清空旧数据后加载状态会正常显示加载footer,出错时也只会展示错误状态不会残留旧数据
- 如果需要区分下拉刷新和筛选刷新,只需要给下拉刷新的逻辑不加清空旧数据的判断即可,不影响原有下拉交互
内容的提问来源于stack exchange,提问作者MONK
相关产品推荐
相关产品推荐

