Android Kotlin中RecyclerView旋转/切Fragment时条目重复问题
RecyclerView列表重复显示问题排查与修复
问题现象
使用Kotlin+Android Studio开发Firebase同步的Fragment应用,其中ShareFragment通过RecyclerView展示网页列表。正常场景功能正常,但横竖屏切换或离开Fragment再返回时,列表条目会重复显示,修改Adapter后无效,notifyDataSetChanged()也未解决问题。
相关代码
ShareFragment代码
class ShareFragment : Fragment() { private lateinit var shareBinding: FragmentShareBinding private lateinit var shareViewModel: ShareViewModel private lateinit var shareAdapter: ShareAdapter private var shareVehicle: ArrayList<PaperworkServer> = ArrayList() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { shareBinding = FragmentShareBinding.inflate(inflater, container, false) shareViewModel = ViewModelProvider(this)[ShareViewModel::class.java] return shareBinding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) shareViewModel.loadShareFromServerDone.observe(viewLifecycleOwner) { result -> onLoadChareFromServerDoneSubscribe(result) } shareViewModel.loadPaperworkServer() shareAdapter = ShareAdapter(shareVehicle, onItemClicked = { onShareItemClicked(it) }) shareBinding.shareView.apply { layoutManager = LinearLayoutManager(this@ShareFragment.requireContext()) adapter = shareAdapter setHasFixedSize(false) } } private fun onShareItemClicked(share: PaperworkServer) { findNavController().navigate(ShareFragmentDirections.actionShareFragmentToLinkTwoFragment(share)) } private fun onLoadChareFromServerDoneSubscribe(shareServerLoaded: ArrayList<PaperworkServer>) { shareVehicle = shareServerLoaded shareAdapter.appendItems(shareVehicle) } }
ShareAdapter代码
class ShareAdapter( private val shareList: ArrayList<PaperworkServer>, private val onItemClicked: (PaperworkServer) -> Unit ) : RecyclerView.Adapter<ShareAdapter.ShareViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShareViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.card_view_item_share, parent, false) return ShareViewHolder(view) } override fun onBindViewHolder(holder: ShareViewHolder, position: Int) { val share = shareList[position] holder.bind(share) holder.itemView.setOnClickListener{ onItemClicked(shareList[position])} } override fun getItemCount(): Int = shareList.size fun appendItems(newList: ArrayList<PaperworkServer>){ shareList.clear() shareList.addAll(newList) notifyDataSetChanged() } class ShareViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ private val binding = CardViewItemShareBinding.bind(itemView) fun bind(share: PaperworkServer){ with(binding){ titleLinkTextView.text = share.name cardShareTextView.text = share.information } } } }
ShareViewModel代码
class ShareViewModel : ViewModel() { val paperworkServerRepository = PaperworkServerRepository () private var shareVehicle: ArrayList<PaperworkServer> = ArrayList() val loadPaperworkServer: MutableLiveData<ArrayList<PaperworkServer>> = MutableLiveData() val loadShareFromServerDone: LiveData<ArrayList<PaperworkServer>> = loadPaperworkServer fun loadPaperworkServer (){ GlobalScope.launch(Dispatchers.IO){ val querySnapshot = paperworkServerRepository.share() for (document in querySnapshot) { val paperworkServer: PaperworkServer = document.toObject<PaperworkServer>() shareVehicle.add(paperworkServer) } loadPaperworkServer.postValue(shareVehicle) } } }
问题根源
- ViewModel数据源未重置:ViewModel中的
shareVehicle是全局成员变量,每次调用loadPaperworkServer()时,都会在原有列表基础上追加新数据,不会清空旧数据。而横竖屏切换或Fragment重建时,ViewModel不会被销毁,再次调用加载方法会导致数据累积。 - Fragment重建重复触发加载:每次Fragment重建(如横竖屏切换),
onViewCreated都会调用shareViewModel.loadPaperworkServer(),进一步加剧数据重复。
修复方案
1. 重置ViewModel中的数据源
修改ShareViewModel,每次加载数据前清空原有列表,或直接创建新列表避免复用:
方案A:创建新列表(推荐)
class ShareViewModel : ViewModel() { val paperworkServerRepository = PaperworkServerRepository () val loadPaperworkServer: MutableLiveData<ArrayList<PaperworkServer>> = MutableLiveData() val loadShareFromServerDone: LiveData<ArrayList<PaperworkServer>> = loadPaperworkServer fun loadPaperworkServer (){ // 改用viewModelScope避免内存泄漏,替代GlobalScope viewModelScope.launch(Dispatchers.IO){ // 每次加载创建新列表,彻底避免数据累积 val shareVehicle = ArrayList<PaperworkServer>() val querySnapshot = paperworkServerRepository.share() for (document in querySnapshot) { val paperworkServer: PaperworkServer = document.toObject<PaperworkServer>() shareVehicle.add(paperworkServer) } loadPaperworkServer.postValue(shareVehicle) } } }
方案B:清空原有列表
fun loadPaperworkServer (){ viewModelScope.launch(Dispatchers.IO){ // 加载前清空旧数据 shareVehicle.clear() val querySnapshot = paperworkServerRepository.share() for (document in querySnapshot) { val paperworkServer: PaperworkServer = document.toObject<PaperworkServer>() shareVehicle.add(paperworkServer) } loadPaperworkServer.postValue(shareVehicle) } }
2. 优化Fragment数据更新逻辑
Fragment中shareVehicle的重新赋值对Adapter无效(Adapter持有初始化时的列表引用),可以简化onLoadChareFromServerDoneSubscribe方法:
private fun onLoadChareFromServerDoneSubscribe(shareServerLoaded: ArrayList<PaperworkServer>) { // 直接传递新数据给Adapter即可 shareAdapter.appendItems(shareServerLoaded) }
3. 可选:用DiffUtil提升更新效率
替代notifyDataSetChanged(),使用DiffUtil实现局部刷新,提升性能:
// 在Adapter中添加DiffUtil逻辑 fun updateItems(newList: List<PaperworkServer>) { val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize(): Int = shareList.size override fun getNewListSize(): Int = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 根据数据唯一标识判断,比如id return shareList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 判断内容是否一致 return shareList[oldItemPosition] == newList[newItemPosition] } }) shareList.clear() shareList.addAll(newList) diffResult.dispatchUpdatesTo(this) }
然后将appendItems替换为updateItems即可。
内容的提问来源于stack exchange,提问作者Sebastian Ricaurte
相关产品推荐
相关产品推荐

