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

如何将两个请求的返回体加载到基于PagingDataAdapter的同一RecyclerView

问题原因分析

  • 逻辑覆盖问题:ViewModel中你先后收集两个接口返回的PagingData流,后续搜索接口的结果会直接覆盖频道接口的结果赋值给LiveData,LiveData只会保留最后一次的赋值,根本没有实现两个数据集合并的效果。
  • 死循环问题:Fragment中在searchResult的观察回调里又调用了getSearchResult方法,这会再次触发接口请求、重新给LiveData赋值,进而再次触发观察回调,形成无限循环,阻塞了submitData的执行逻辑。
  • 方法误用问题:你对PagingData调用map方法时没有返回转换后的条目,反而在里面执行接口请求,属于对PagingData操作API的误用,会导致数据转换逻辑失效。

可行实现方案

步骤1:定义统一的列表条目类型

首先定义密封类统一两种条目的类型,方便适配器做viewType区分:

sealed class SearchListItem {
    // 搜索结果条目
    data class SearchResultItem(val originData: SearchItems) : SearchListItem()
    // 频道条目
    data class ChannelItem(val originData: Channel) : SearchListItem()
}

步骤2:修改ViewModel代码实现PagingData合并

使用combine操作符合并两个流的PagingData,避免后续结果覆盖之前的结果:

private val _searchResult = MutableLiveData<Resource<PagingData<SearchListItem>>>()
val searchResult: LiveData<Resource<PagingData<SearchListItem>>> = _searchResult

fun getSearchResult(q: String, id: String) = viewModelScope.launch {
    _searchResult.postValue(Resource.Loading())
    combine(
        // 转换搜索结果的条目类型
        repository.getSearch(q).map { pagingData ->
            pagingData.map { SearchListItem.SearchResultItem(it) }
        },
        // 转换频道结果的条目类型
        repository.fetchChannels(id).map { pagingData ->
            pagingData.map { SearchListItem.ChannelItem(it) }
        }
    ) { searchPaging, channelPaging ->
        // 可根据需求调整合并顺序,此处示例把频道条目放在列表头部
        channelPaging.insertFooterItem { searchPaging }
    }.collect { mergedPagingData ->
        _searchResult.value = Resource.Success(mergedPagingData)
    }
}

如果你的两个接口都不需要分页、是一次性返回全量数据,也可以直接合并成普通列表再转成PagingData,逻辑更简单:

fun getSearchResult(q: String, id: String) = viewModelScope.launch {
    _searchResult.postValue(Resource.Loading())
    runCatching {
        val searchList = repository.getSearch(q).first().map { SearchListItem.SearchResultItem(it) }
        val channelList = repository.fetchChannels(id).first().map { SearchListItem.ChannelItem(it) }
        val mergedList = channelList + searchList
        PagingData.from(mergedList)
    }.onSuccess { 
        _searchResult.value = Resource.Success(it)
    }.onFailure {
        _searchResult.value = Resource.Error(it.message ?: "请求失败")
    }
}

步骤3:修改Fragment代码移除死循环逻辑

删除观察回调里的getSearchResult调用,避免死循环:

viewModel.searchResult.observe(viewLifecycleOwner) {
    when (it) {
        is Resource.Error -> {
            it.message?.let { msg -> Log.d("mecal", msg) }
        }
        is Resource.Success -> {
            lifecycleScope.launch {
                it.data?.let { mergedData ->
                    searchAdapter.submitData(mergedData)
                }
            }
        }
        is Resource.Loading -> {
            // 可自行添加加载状态的UI逻辑
        }
    }
}

步骤4:适配适配器逻辑

确认你的PagingDataAdapter的getItemViewType方法根据SearchListItem的实际类型返回对应viewType,DiffUtil.ItemCallback正确处理两种条目的差分判断即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 18:06:08