RecyclerView搜索内容不更新:编辑搜索框后仍显示旧文章结果
解决NewsApi搜索仅首次有效问题
问题描述
正在开发一款基于NewsApi的应用,设置了搜索EditText用于检索文章,但仅首次搜索有效。当在EditText中输入新关键词后,RecyclerView仍显示与之前关键词相关的文章。
原Fragment代码
class SearchNewsFragment : Fragment(R.layout.fragment_search_news) { lateinit var viewModel: NewsViewModel lateinit var newsAdapter: NewsAdapter private var recyclerView: RecyclerView? = null private var gridLayoutManager: GridLayoutManager? = null private var sourceAdapters: SourceAdapters? = null val TAG = "SearchNewsFragment" override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel = (activity as NewsActivity).viewModel setupRecyclerView() initViews() // 跳转到点击的文章详情页 newsAdapter.setOnItemClickListener { val bundle = Bundle().apply { putSerializable("article", it) } findNavController().navigate( R.id.action_searchNewsFragment_to_articleFragment, bundle ) } var job: Job? = null etSearch.addTextChangedListener { editable -> job?.cancel() job = MainScope().launch { delay(Constants.SEARCH_NEWS_TIME_DELAY) editable?.let { if (editable.toString().isNotEmpty()) { viewModel.searchNews(editable.toString()) } if (editable.toString().isEmpty()){ hideRecyclerView() showSources() } } } } viewModel.searchNews.observe(viewLifecycleOwner, Observer { response -> when (response) { is Resource.Success -> { hideSources() showRecyclerView() hideProgressBar() response.data?.let { newsResponse -> newsAdapter.differ.submitList(newsResponse.articles.toList()) val totalPages = newsResponse.totalResults / Constants.QUERY_PAGE_SIZE + 2 isLastPage = viewModel.searchNewsPage == totalPages if (isLastPage) { rvSearchNews.setPadding(0, 0, 0, 0) } } } is Resource.Error -> { hideProgressBar() response.message?.let { message -> Log.e(TAG, "发生错误: $message") } } is Resource.Loading -> { showProgressBar() } } }) } private fun hideSources(){ recycler_view_source.visibility = View.INVISIBLE } private fun showSources(){ recycler_view_source.visibility = View.VISIBLE } private fun hideRecyclerView(){ rvSearchNews.visibility = View.INVISIBLE } private fun showRecyclerView(){ rvSearchNews.visibility = View.VISIBLE } private fun hideProgressBar() { paginationProgressBar.visibility = View.INVISIBLE isLoading = false } private fun showProgressBar() { paginationProgressBar.visibility = View.VISIBLE isLoading = true } var isLoading = false var isLastPage = false var isScrolling = false var scrollListener = object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { isScrolling = true } } override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) val layoutManager = recyclerView.layoutManager as LinearLayoutManager val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() val visibleItemCount = layoutManager.childCount val totalItemCount = layoutManager.itemCount val isNotLoadingAndNotLastPage = !isLoading && !isLastPage val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount val isNotAtBeginning = firstVisibleItemPosition >= 0 val isTotalMoreThanVisible = totalItemCount >= Constants.QUERY_PAGE_SIZE val shouldPaginate = isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning && isTotalMoreThanVisible && isScrolling if (shouldPaginate) { viewModel.searchNews(etSearch.text.toString()) isScrolling = false } } } private fun setupRecyclerView() { newsAdapter = NewsAdapter() rvSearchNews.apply { adapter = newsAdapter layoutManager = LinearLayoutManager(activity) addOnScrollListener(this@SearchNewsFragment.scrollListener) } } private fun initViews() { recyclerView = requireView().findViewById(R.id.recycler_view_source) gridLayoutManager = GridLayoutManager( requireContext().applicationContext, 2, LinearLayoutManager.VERTICAL, false ) recyclerView?.layoutManager = gridLayoutManager recyclerView?.setHasFixedSize(true) sourceAdapters = SourceAdapters(requireContext().applicationContext, listOf()) viewModel.getSources("us") viewModel.sourcesPage.observe(viewLifecycleOwner){ sourceAdapters?.submitList(it) } sourceAdapters?.onSourceClick = { val action = SearchNewsFragmentDirections.actionSearchNewsFragmentToBreakingNewsFragment(null, it) findNavController().navigate(action) } recyclerView?.adapter = sourceAdapters } }
原NewsAdapter代码
class NewsAdapter : RecyclerView.Adapter<NewsAdapter.ArticleViewHolder>() { inner class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) private val differCallback = object : DiffUtil.ItemCallback<Article>() { override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean { return oldItem.url == newItem.url } override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean { return oldItem == newItem } } val differ = AsyncListDiffer(this, differCallback) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder { return ArticleViewHolder( LayoutInflater.from(parent.context).inflate( R.layout.item_article_preview, parent, false ) ) } override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) { val article = differ.currentList[position] holder.itemView.apply { Glide.with(this).load(article.urlToImage).into(article_imageView) source.text = article.source?.name headline.text = article.title setOnClickListener{ onItemClickListener?.let { it(article) } } } } override fun getItemCount(): Int { return differ.currentList.size } fun setOnItemClickListener(listen: (Article) -> Unit) { onItemClickListener = listen } private var onItemClickListener: ((Article) -> Unit)? = null }
解决方案
核心问题
搜索新关键词时,ViewModel中的分页页码searchNewsPage未重置,导致请求的是旧关键词的后续页码数据;同时未及时清空旧列表,DiffUtil无法正确识别数据更新。
具体修复
1. 重置ViewModel分页参数
在ViewModel的searchNews方法中,每次接收新关键词时重置页码为1,同时记录当前搜索关键词,避免同一关键词重复重置:
private var currentSearchQuery: String? = null fun searchNews(query: String) { if (currentSearchQuery != query) { // 新关键词,重置分页参数 currentSearchQuery = query searchNewsPage = 1 } // 发起搜索请求(根据searchNewsPage加载对应页码) viewModelScope.launch { val response = newsRepository.searchNews(query, searchNewsPage) _searchNews.postValue(response) } }
2. 搜索新关键词时清空旧列表
在Fragment的搜索监听中,发起新搜索前先提交空列表,确保旧数据被清除,同时重置分页状态:
etSearch.addTextChangedListener { editable -> job?.cancel() job = MainScope().launch { delay(Constants.SEARCH_NEWS_TIME_DELAY) editable?.let { if (editable.toString().isNotEmpty()) { // 清空旧列表 newsAdapter.differ.submitList(emptyList()) // 重置分页状态 isLoading = false isLastPage = false viewModel.searchNews(editable.toString()) } if (editable.toString().isEmpty()){ hideRecyclerView() showSources() } } } }
3. 确保Article类正确实现equals方法
如果Article不是数据类,手动实现equals()和hashCode(),保证DiffUtil能正确判断内容是否变化;如果是数据类则无需额外处理:
// 示例数据类实现 data class Article( val url: String, val title: String, val source: Source?, val urlToImage: String? )
4. 修正分页加载逻辑
确保ViewModel中分页加载时,只有同一关键词才追加数据,新关键词直接替换现有数据:
// 示例ViewModel中处理搜索结果的逻辑 fun handleSearchResponse(response: Resource<NewsResponse>) { if (response is Resource.Success) { response.data?.let { newsResponse -> val currentList = mutableListOf<Article>() if (searchNewsPage > 1) { // 同一关键词,追加下一页数据 currentList.addAll(_searchNews.value?.data?.articles ?: emptyList()) } currentList.addAll(newsResponse.articles) _searchNews.postValue(Resource.Success(NewsResponse(currentList, newsResponse.totalResults))) } } else { _searchNews.postValue(response) } }
内容的提问来源于stack exchange,提问作者Fary
相关产品推荐
相关产品推荐

