点击关闭搜索后更新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(),问题仍未解决,效果如下:
问题分析与解决方案
核心问题点
- 重复注册Observer导致数据异常:每次调用
requestApiData都重新注册postsResponse的Observer,多实例监听会导致旧Observer覆盖新数据更新。 - submitList(null)使用错误:直接提交
null会打乱DiffUtil的差异计算逻辑,导致后续新数据无法触发界面更新。 - 不必要的视图操作破坏状态:调用
linearLayoutManager?.removeAllViews()和recyclerView.removeAllViews()会破坏RecyclerView内部状态,干扰AsyncListDiffer的正常工作。 - 协程异步时序问题:
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
相关产品推荐
相关产品推荐

