DiffUtil 不适用于嵌套的 recyclerview Kotlin [英] DiffUtil Not working in nested recyclerview Kotlin

查看:36
本文介绍了DiffUtil 不适用于嵌套的 recyclerview Kotlin的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个回收站视图.在我使用 notifyDataSetChanged 之前,我的视图不会更新.我要求类似类型的问题,但这次我有Github 链接.所以请看一看并向我解释我做错了什么.谢谢

I have two recycler views. My view is not updated until I used notifyDataSetChanged. I asked for a similar type of issue, but this time I have Github Link. So please have a look and explain to me what I am doing wrong. Thanks

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

ActivityViewModel.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 链接中找到完整代码.我附在上面

Please find the whole code in Github Link. I attached in above

推荐答案

我还没有调试过这个,但是如果你消除你对 MutableLists 和 vars 的过度使用,并简化你的 LiveData,你会可能会消除您的错误.至少,它会帮助您追踪问题.

I haven't debugged this, but if you remove your overuse of MutableLists and vars, and simplify your LiveData, you will likely eliminate your bug. At the very least, it will help you track down the problem.

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

MutableLists and DiffUtil do not play well together!

例如,Group 的列表应该是只读列表:

For example, Group's list should be a read-only List:

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

让 LiveData 仅报告其他一些属性是否可用是令人费解的.然后,您将在这里和观察者中处处处理可空性,因此很难判断何时会从空安全调用中跳过或不跳过某些代码.我会更改您的 LiveData 以直接发布只读列表.您可以通过使用 emptyList() 来避免可空列表,以简化代码.

It's convoluted to have a LiveData that only reports if some other property is usable. Then you're dealing with nullability all over the place here and in the observer, so it becomes hard to tell when some code is going to be skipped or not from a null-safe call. I would change your LiveData to directly publish a read-only List. You can avoid nullable Lists by using emptyList() to also simplify code.

您也可以避免使用 ArrayDeque 公开展示您的内部工作.并且您在不必要地延迟加载 ArrayDeque,这导致不得不处理不必要的可空性.

You can avoid publicly showing your interior workings with the ArrayDeque as well. And you are lazy loading the ArrayDeque unnecessarily, which leads to having to deal with nullability unnecessarily.

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.

In your Activity, since your GroupAdapter has no dependencies, you can instantiate it at the call site to avoid dealing with lazy loading it. And you can set it to the RecyclerView in onCreate() immediately.

因为ViewModel的变化,观察变得非常简单.

Because of the changes in ViewModel, observing becomes very simple.

如果你在 setupViewModel() 中做了一些立即更新视图的事情,你会崩溃,所以你应该在调用 setContentView() 后移动它.

If you do something in setupViewModel() that updates a view immediately, you'll have a crash, so you should move it after calling 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)
        }
    }
}

您在 GroupAdapter 中的 DiffUtil.ItemCallback.areItemsTheSame 不正确.你只应该检查它们是否代表同一个项目,而不是它们的内容是否相同,所以不应该比较列表.

Your DiffUtil.ItemCallback.areItemsTheSame in GroupAdapter is incorrect. You are only supposed to check if they represent the same item, not if their contents are the same, so it should not be comparing lists.

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

并且在 GroupViewHolder 中,您每次都在为内部 RecyclerView 创建一个新的适配器.这完全违背了使用 RecyclerView 的目的.您应该只创建一次适配器.

And in GroupViewHolder, you are creating a new adapter for the inner RecyclerView every time it is rebound. That defeats the purpose of using RecyclerView at all. You should only create the adapter once.

我预测,当视图被回收而不是更新时,嵌套列表中的更改看起来会很奇怪,因为它会为之前视图中的更改设置动画,这可能来自不同的项目.因此,如果新键不匹配,我们可能应该跟踪旧项目键并避免动画.我认为这可以在 submitList() 回调参数中完成,以便在通过调用 notifyDataSetChanged() 在适配器中更新列表内容后运行,但我没有测试了一下.

I am predicting that the change in the nested list is going to look weird when the view is being recycled rather than just updated, because it will animate the change from what was in the view previously, which could be from a different item. So we should probably track the old item key and avoid the animation if the new key doesn't match. I think this can be done in the submitList() callback parameter to run after the list contents have been updated in the adapter by calling notifyDataSetChanged(), but I haven't tested it.

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 函数命名混乱.我会把它们变成二级构造函数,你可以将主构造函数设为私有.

Side note: your adapters' bindView functions are confusingly named. I would just make those into secondary constructors and you can make the primary constructor private.

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

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

    //...
}

这篇关于DiffUtil 不适用于嵌套的 recyclerview Kotlin的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆