簡體   English   中英

DiffUtil 在嵌套的 recyclerview Kotlin 中不起作用

[英]DiffUtil Not working in nested recyclerview Kotlin

我有兩個回收站的觀點。 在我使用notifyDataSetChanged之前,我的視圖不會更新。 我問過類似類型的問題,但這次我有Github鏈接。 所以請看一下並向我解釋我做錯了什么。 謝謝

MainActivity.kt

package com.example.diffutilexample

import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.example.diffutilexample.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private val viewModel by viewModels<ActivityViewModel>()
    private lateinit var binding: ActivityMainBinding
    private var groupAdapter: GroupAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setupViewModel()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        viewModel.fetchData()

        binding.button.setOnClickListener {
            viewModel.addData()
        }
    }

    private fun setupViewModel() {
        viewModel.groupListLiveData.observe(this) {
            if (groupAdapter == null) {
                groupAdapter = GroupAdapter()
                binding.recyclerview.adapter = groupAdapter
            }
            groupAdapter?.submitList(viewModel.groupList?.toMutableList())
            binding.recyclerview.post {
                groupAdapter?.notifyDataSetChanged()
            }
        }
    }
}

活動視圖模型.kt

package com.example.diffutilexample

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class ActivityViewModel(app: Application) : AndroidViewModel(app) {

    var groupListLiveData: MutableLiveData<Boolean> = MutableLiveData()
    var groupList: ArrayDeque<Group>? = null
        set(value) {
            field = value
            groupListLiveData.postValue(true)
        }
    var value = 0

    fun fetchData() {
        viewModelScope.launch {
            val response = ApiInterface.create().getResponse()

            groupList = groupByData(response.abc)
        }
    }

    private fun groupByData(abc: List<Abc>?): ArrayDeque<Group> {
        val result: ArrayDeque<Group> = groupList ?: ArrayDeque()

        abc?.iterator()?.forEach { item ->
            val key = GroupKey(item.qwe)
            result.addFirst(Group(key, mutableListOf(item)))
        }
        return result
    }

    fun addData() {
        groupList?.let { lastList ->
            val qwe = Qwe("Vivek ${value++}", "Modi")
            val item = Abc(type = "Type 1", "Adding Message", qwe)
            val lastGroup = lastList[0]
            lastGroup.list.add(item)
            groupList = lastList
        }
    }
}

請在 Github 鏈接中找到完整代碼。 我附在上面

我不完全確定,我承認我沒有廣泛研究你的代碼,這不是一個解決方案,但這可能會為你指明如何解決它的正確方向。

關於的事情

groupAdapter?.submitList(viewModel.groupList?.toMutableList())

是否toMutableList()確實復制了列表。 但是列表中的每個對象都不是副本。 如果您將內容添加到原始列表中的 object 中,就像您在addData()中所做的那樣,實際上它也已經添加到適配器中的副本中。 這就是為什么新的 submitList 不會將其識別為更改的原因,因為它實際上與 submitList 之前的相同。

據我了解,如果您提交的列表僅包含不可變的對象,則使用 DiffUtil 效果最好,因此不會發生此類錯誤。 我之前遇到過類似的問題,解決方案也不是很簡單。 事實上,我不完全記得當時我是如何解決它的,但希望這能將你推向正確的方向。

我沒有對此進行調試,但是如果您消除對 MutableLists 和var的過度使用,並簡化您的 LiveData,您可能會消除您的錯誤。 至少,它會幫助你找出問題所在。

MutableLists 和 DiffUtil 不能很好地配合使用!

例如,Group 的列表應該是一個只讀的 List:

data class Group(
    val key: GroupKey,
    val list: List<Abc?> = emptyList()
)

有一個 LiveData 很復雜,它只報告其他屬性是否可用。 然后,您在這里和觀察者的所有地方都在處理可空性,因此很難從空安全調用中判斷何時將跳過某些代碼。 我會更改您的 LiveData 以直接發布只讀列表。 您可以通過使用emptyList()來避免可為空的列表,也可以簡化代碼。

您也可以避免使用 ArrayDeque 公開展示您的內部工作原理。 而且您在不必要地延遲加載 ArrayDeque 時,會導致不得不處理不必要的可空性。

class ActivityViewModel(app: Application) : AndroidViewModel(app) {

    private val _groupList = MutableLiveData<List<Group>>()
    val groupList: LiveData<List<Group>> get() = _groupList
    private val trackedGroups = ArrayDeque<Group>()
    private var counter = 0

    fun fetchData() {
        viewModelScope.launch {
            val response = ApiInterface.create().getResponse()
            addFetchedData(response.abc.orEmpty())
            _groupList.value = trackedGroups.toList() // new copy for observers
        }
    }

    private fun addFetchedData(abcList: List<Abc>) {
        for (item in abcList) {
            val key = GroupKey(item.qwe)
            trackedGroups.addFirst(Group(key, listOf(item)))
        }
    }

    fun addData() {
        if (trackedGroups.isEmpty())
            return // Might want to create a default instead of doing nothing?
        val qwe = Qwe("Vivek ${counter++}", "Modi")
        val item = Abc(type = "Type 1", "Adding Message", qwe)
        val group = trackedGroups[0]
        trackedGroups[0] = group.copy(list = group.list + item)

        _groupList.value = trackedGroups.toList() // new copy for observers
    }
}

在您的 Activity 中,由於您的 GroupAdapter 沒有依賴項,您可以在調用站點對其進行實例化以避免處理延遲加載。 您可以立即將其設置為onCreate()中的 RecyclerView。

由於 ViewModel 的變化,觀察變得非常簡單。

如果您在setupViewModel()中執行了立即更新視圖的操作,則會發生崩潰,因此您應該在調用setContentView()之后移動它。

class MainActivity : AppCompatActivity() {

    private val viewModel by viewModels<ActivityViewModel>()
    private lateinit var binding: ActivityMainBinding
    private val groupAdapter = GroupAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).apply {
            setContentView(root)
            recyclerview.adapter = groupAdapter
            button.setOnClickListener {
                viewModel.addData()
            }
        }

        setupViewModel()
        viewModel.fetchData()
    }

    private fun setupViewModel() {
        viewModel.groupList.observe(this) {
            groupAdapter.submitList(it)
        }
    }
}

您在DiffUtil.ItemCallback.areItemsTheSame中的 DiffUtil.ItemCallback.areItemsTheSame 不正確。 您只應該檢查它們是否代表相同的項目,而不是它們的內容是否相同,因此不應該比較列表。

override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
    return oldItem.key == newItem.key
}

在 GroupViewHolder 中,每次反彈時,您都在為內部 RecyclerView 創建一個新適配器。 這完全違背了使用 RecyclerView 的目的。 您應該只創建一次適配器。

我預測,當視圖被回收而不是僅僅更新時,嵌套列表中的更改會看起來很奇怪,因為它會為之前視圖中的更改設置動畫,這可能來自不同的項目。 因此,如果新密鑰不匹配,我們可能應該跟蹤舊項目密鑰並避免 animation。 我認為這可以在submitList()回調參數中通過調用notifyDataSetChanged()在適配器中更新列表內容后運行,但我還沒有測試過。

class GroupViewHolder(val binding: ItemLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
    
    companion object {
        //...
    }

    private val adapter = NestedGroupAdapter().also {
        binding.nestedRecyclerview.adapter = it
    }

    private var previousKey: GroupKey? = null

    fun bindItem(item: Group?) {
        val skipAnimation = item?.key != previousKey
        previousKey = item?.key
        adapter.submitList(item?.list.orEmpty()) {
            if (skipAnimation) adapter.notifyDataSetChanged()
        }
    }
}

旁注:您的適配器的bindView函數名稱容易混淆。 我只是將它們變成輔助構造函數,您可以將主構造函數設為私有。

class GroupViewHolder private constructor(private val binding: ItemLayoutBinding) :
    RecyclerView.ViewHolder(binding.root) {

    constructor(parent: ViewGroup) : this(
        ItemLayoutBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
    )

    //...
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM