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

点击关闭搜索后更新AsyncListDiffer列表失效(submitList(null)无效)

问题:关闭搜索后AsyncListDiffer提交新数据不生效,界面空屏

在Android应用中使用AsyncListDiffer组件,工具栏带有搜索栏和搜索图标。需求是用户点击关闭搜索时,清空旧搜索列表、重新发起API请求并加载新列表,但调用submitList(null)后新数据无法正常显示,界面一直为空屏。

相关代码实现

differCallback实现

private val differCallback = object : DiffUtil.ItemCallback<Item>() {
    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
        return (oldItem.id == newItem.id)
    }

    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
      return (oldItem == newItem)
    }
}

val differ = AsyncListDiffer(this, differCallback)

核心API请求方法requestApiData

private fun requestApiData() {
    showShimmerEffect()
    Log.d(TAG, "requestApiData: called")
    postViewModel.getPostListByLabel()
    postViewModel.postsResponse.observe(viewLifecycleOwner) { response ->
        postsAPiFlag = true
        when (response) {
            is NetworkResult.Success -> {
                hideShimmerEffect()
                response.data?.let {
                    binding.progressBar.visibility = View.GONE
                    adapter.differ.submitList(it.items.toList())
                }

            }

            is NetworkResult.Error -> {
                hideShimmerEffect()
                binding.progressBar.visibility = View.GONE
                Log.e(TAG, response.data.toString())
                Log.e(TAG, response.message.toString())
            }

            is NetworkResult.Loading -> {
                binding.progressBar.visibility = View.VISIBLE
            }
        }

    }
}

搜索API方法requestSearchApi

private fun requestSearchApi(keyword: String) {

    if (Utils.hasInternetConnection(requireContext())) {

        showShimmerEffect()

        postViewModel.finalURL.value =
            "${BASE_URL}?labels=Accessory&maxResults=500&key=$API_KEY"

        postViewModel.getItemsBySearch()
        postViewModel.searchedPostsResponse.observe(this) { response ->
            when (response) {
                is NetworkResult.Success -> {
                    postsAPiFlag = false

                    if (searchItemList.isNotEmpty()) {
                        searchItemList.clear()
                    }

                    binding.progressBar.visibility = View.GONE

                    lifecycleScope.launch {
                        searchItemList = async {
                            response.data?.items?.filter {
                                it.title.contains(keyword) || it.content.contains(keyword)
                            } as ArrayList<Item>
                        }.await()
                    }

                    Log.d(TAG, "requestSearchApi: test size ${searchItemList.size}")


                    if (searchItemList.isEmpty()) {
                        Toast.makeText(
                            requireContext(),
                            "未找到包含该关键词的帖子",
                            Toast.LENGTH_SHORT
                        ).show()
                        hideShimmerEffect()
                        return@observe

                    } else {
                        postsAPiFlag = false

                        binding.accessoryRecyclerView.removeAllViews()
                        binding.accessoryRecyclerView.recycledViewPool.clear()
                        hideShimmerEffect()

                        adapter.differ.submitList(searchItemList.toList())
                    }

                }
                is NetworkResult.Error -> {
                    hideShimmerEffect()
                    binding.progressBar.visibility = View.GONE
                    Toast.makeText(
                        requireContext(),
                        response.message.toString(),
                        Toast.LENGTH_SHORT
                    ).show()
                    Log.e(TAG, "onQueryTextSubmit: $response")

                }

                is NetworkResult.Loading -> {
                    binding.progressBar.visibility = View.VISIBLE

                }
            }
        }
    } else {
        noInternetConnectionLayout()
    }
}

searchView.setOnCloseListener实现

searchView.setOnCloseListener {

    setOnCloseListenerFlag = true
    if (keyword.isNullOrEmpty()) {
        return@setOnCloseListener false
    }
    if (Utils.hasInternetConnection(requireContext())) {
        showShimmerEffect()

        searchItemList.clear()
        adapter.differ.submitList(null)
        postViewModel.postListResponse = null
        linearLayoutManager?.removeAllViews()

        postViewModel.finalURL.value =
            BASE_URL_POSTS_BY_LABEL + "posts?labels=Accessory&key=$API_KEY"

        requestApiData() //====> 此处重新调用API请求方法

        Log.d(
            TAG,
            "setOnCloseListener: called ${adapter.differ.currentList.size.toString()}"
        )

        Log.d(TAG, "setOnCloseListener: ${postViewModel.finalURL.value.toString()}")
    } else {
        Log.d(TAG, "setOnCloseListener: called")
        adapter.differ.submitList(null)
        searchItemList.clear()
        noInternetConnectionLayout()
    }
    false
}

问题现象

已尝试在调用submitList(null)后执行adapter.notifyDataSetChanged(),问题仍未解决,效果如下:
关闭搜索后界面空屏效果


问题分析与解决方案

核心问题点

  1. 重复注册Observer导致数据异常:每次调用requestApiData都重新注册postsResponse的Observer,多实例监听会导致旧Observer覆盖新数据更新。
  2. submitList(null)使用错误:直接提交null会打乱DiffUtil的差异计算逻辑,导致后续新数据无法触发界面更新。
  3. 不必要的视图操作破坏状态:调用linearLayoutManager?.removeAllViews()和recyclerView.removeAllViews()会破坏RecyclerView内部状态,干扰AsyncListDiffer的正常工作。
  4. 协程异步时序问题:requestSearchApi中协程赋值searchItemList后直接使用,存在数据未准备好就提交列表的情况。

具体修复步骤

1. 统一Observer注册时机,避免重复监听

将postsResponse的Observer移到初始化方法中只注册一次:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    // 其他初始化代码...
    postViewModel.postsResponse.observe(viewLifecycleOwner) { response ->
        postsAPiFlag = true
        when (response) {
            is NetworkResult.Success -> {
                hideShimmerEffect()
                response.data?.let {
                    binding.progressBar.visibility = View.GONE
                    adapter.differ.submitList(it.items.toList())
                }
            }
            is NetworkResult.Error -> {
                hideShimmerEffect()
                binding.progressBar.visibility = View.GONE
                Log.e(TAG, response.data.toString())
                Log.e(TAG, response.message.toString())
            }
            is NetworkResult.Loading -> {
                binding.progressBar.visibility = View.VISIBLE
            }
        }
    }
}

// 修改requestApiData方法,移除内部Observer注册
private fun requestApiData() {
    showShimmerEffect()
    Log.d(TAG, "requestApiData: called")
    postViewModel.getPostListByLabel()
}

2. 替换submitList(null)为提交空列表

用emptyList()替代null,保证DiffUtil能正确处理状态切换:

// 在setOnCloseListener中修改
// adapter.differ.submitList(null)
adapter.differ.submitList(emptyList())

3. 移除破坏RecyclerView状态的代码

删除以下不必要的操作,AsyncListDiffer会自动处理列表更新:

// linearLayoutManager?.removeAllViews()
// binding.accessoryRecyclerView.removeAllViews()

4. 修复requestSearchApi中的协程异步问题

将数据过滤和列表提交逻辑放到协程内部,确保数据准备完成后再提交:

// 修改requestSearchApi的Success分支
is NetworkResult.Success -> {
    postsAPiFlag = false
    binding.progressBar.visibility = View.GONE

    lifecycleScope.launch {
        val filteredList = async {
            response.data?.items?.filter {
                it.title.contains(keyword) || it.content.contains(keyword)
            } ?: emptyList()
        }.await() as ArrayList<Item>

        if (filteredList.isEmpty()) {
            Toast.makeText(
                requireContext(),
                "未找到包含该关键词的帖子",
                Toast.LENGTH_SHORT
            ).show()
            hideShimmerEffect()
        } else {
            searchItemList.clear()
            searchItemList.addAll(filteredList)
            hideShimmerEffect()
            adapter.differ.submitList(searchItemList.toList())
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 01:45:38