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

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)
        }
    }
}

问题根源

  1. ViewModel数据源未重置:ViewModel中的shareVehicle是全局成员变量,每次调用loadPaperworkServer()时,都会在原有列表基础上追加新数据,不会清空旧数据。而横竖屏切换或Fragment重建时,ViewModel不会被销毁,再次调用加载方法会导致数据累积。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 02:38:09