将新列表提交给RecyclerView ListAdapter时,diff检查始终为areContentsTheSame()返回true [英] When submitting a new list to RecyclerView ListAdapter the diff check always returns true for areContentsTheSame()

查看:498
本文介绍了将新列表提交给RecyclerView ListAdapter时,diff检查始终为areContentsTheSame()返回true的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用MVVM体系结构构建一个简单的订购应用程序.我在ProductsFragment中使用RecyclerView列出了所有可以订购的产品.我还在ViewModel中使用LiveData,在Fragment中可以观察到它,以检查对产品列表的任何更改.

I am using an MVVM architechture to build a simple ordering app. I am using a RecyclerView in my ProductsFragment to list all the products that can be ordered. I am also using LiveData in my ViewModel and observable in my Fragment to check for any changes to the List of products.

在我的列表项中,我有3个按钮:1个按钮,用于将产品添加到购物篮中;另一个按钮,用于增加客户想要添加到购物篮中的数量;第三个按钮,用于减少客户想要添加到购物篮中的数量

In my list item I have 3 buttons: 1 button to add a product to the basket, another to increment the quantity that customer wants to add to the basket and a third to decrement the quantity the customer wants to add to the basket.

在我的产品数据类中,每当客户单击增量或减量按钮时,数量都会在数据模型中更新.

In my product data class, whenever the customer clicks on increment or decrement button, the quantity is updated in the data model.

我还使用数据绑定将产品绑定到recyclerview列表项布局和Click侦听器.我正在使用ListAdapter开箱即用地访问DiffUtil.

I am also using databinding to bind the product to the recyclerview list item layout and the click listeners. I am using the ListAdapter to get access to DiffUtil out of the box.

我遇到的问题是,当通知了observable时,我想使用ListAdapter的SubmitList方法,因此在RecyclerView中仅更新已更改的项目.但是,我注意到DiffUtil方法areContentsTheSame()始终返回true.因此,列表项未更新.我不想使用notifyDatasetChanged,因为这会阻塞UI线程.

The problem I am having is that when the observable is notified I want to use the submitList method of the ListAdapter so only the item that has changed is updated in the RecyclerView. However, I have noticed that the DiffUtil method areContentsTheSame() always returns true. So the list item isn't updated. I don't want to use notifyDatasetChanged as this blocks the UI thread.

我遇到的另一个问题是,当我向购物篮中添加产品时,会保留对该产品的引用,因此,当我向购物篮中添加产品时,当我只希望将MutableLiveData添加为购物篮时,也会更新MutableLiveData.更新.将产品添加到购物篮时,如何停止对正在创建的LiveData的引用?

Another problem I am having is when I add a product to the basket, the reference to the product is being kept, so when I add a product to the basket, the MutableLiveData is also updated, when I just want MutableLiveData to be updated. How can I stop a reference to the LiveData being created when I add a product to the basket?

ProductsViewModel

class ProductsViewModel : ViewModel() {

    // LIVE DATA
    private val _basket = MutableLiveData<Basket>()

    val basket: LiveData<Basket>
        get() = _basket

    private val _products = MutableLiveData<List<Product>>()

    val products: LiveData<List<Product>>
        get() = _products


    init {
        _basket.value = Basket()
        _products.value = dummyData
    }

    fun onIncrementProductQuantityButtonPressed(product: Product) {
        //product.quantity += 1
        //val newList = _products.value

        //_products.value = newList   
        val newProduct = product.copy(quantity = product.quantity.plus(1))
        _basket.value!!.updateProductInBasket(newProduct)
        _basket.value = _basket.value
    }

    fun onDecrementProductQuantityButtonPressed(product: Product) {
        if (product.quantity>1) {
            //product.quantity = product.quantity.minus(1)    
            val newProduct = product.copy(quantity = product.quantity.minus(1))
            _basket.value!!.updateProductInBasket(newProduct)
            _basket.value = _basket.value       
        }
    }
}


产品片段

class ProductsFragment : Fragment() {
    private lateinit var viewModel: ProductsViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        val binding: FragmentProductsBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_products, container, false)

        viewModel = ViewModelProviders.of(this).get(ProductsViewModel::class.java)

        val adapter = ProductsAdapter(ProductListener { product, onClickType ->
            when(onClickType) {
                OnClickType.INCREMENT -> {
                    viewModel.onIncrementProductQuantityButtonPressed(product)
                }
                OnClickType.DECREMENT -> {
                    viewModel.onDecrementProductQuantityButtonPressed(product)
                }
                OnClickType.BASKET -> {
                    viewModel.addToBasketButtonPressed(product)
                }
            }
        })

        viewModel.products.observe(this, Observer { list ->
            adapter.submitList(list)
            //adapter.notifyDataSetChanged() // TODO: check why I have to do notifyDataSetChanged()
        })

        viewModel.basket.observe(this, Observer {
            activity?.invalidateOptionsMenu()
        })

        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        binding.productsRecyclerView.adapter = adapter

        setHasOptionsMenu(true)

        return binding.root
    }


ProductsAdapter

class ProductsAdapter(private val clickListener: ProductListener) : ListAdapter<Product, ProductsAdapter.ProductViewHolder>(ProductDiffUtil()) {

    class ProductDiffUtil: DiffUtil.ItemCallback<Product>() {
        override fun areItemsTheSame(oldItem: Product, newItem: Product): Boolean {
            Log.d("Products", "Are items the same")
            return oldItem.name == newItem.name && oldItem.size == newItem.size
        }

        override fun areContentsTheSame(oldItem: Product, newItem: Product): Boolean {
            Log.d("Products", "Are contents the same ${oldItem == newItem}")
            Timber.d("Are contents the same ${oldItem == newItem}")
            Timber.d("OLD ITEM: $oldItem")
            Timber.d("NEW ITEM: $newItem")
            return oldItem == newItem //need to check this
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
        return ProductViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
        val item = getItem(position)

        //use holder to access all the views in the card item
        holder.bind(clickListener, item)
    }

    class ProductViewHolder private constructor(private val binding: LayoutProductCardBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(clickListener: ProductListener, item: Product) {
            binding.product = item
            binding.clickListener = clickListener
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ProductViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = LayoutProductCardBinding.inflate(layoutInflater, parent, false)

                return ProductViewHolder(binding)
            }
        }
    }

}

class ProductListener(val clickListener: (product: Product, clickType: OnClickType) -> Unit) {
    fun onAddToBasket(product: Product) = clickListener(product, OnClickType.BASKET)
    fun onDecrementProductQuantity(product: Product) = clickListener(product, OnClickType.DECREMENT)
    fun onIncrementProductQuantity(product: Product) = clickListener(product, OnClickType.INCREMENT)
}


enum class OnClickType { BASKET, DECREMENT, INCREMENT  }

推荐答案

您的问题似乎有一个简单的解决方案:).您正在做的是仅更新从视图传递到VM的产品的价值.您应该要做的是:使用.copy(quantity = ...)方法创建产品的副本,覆盖数量.然后替换列表中的上一个项目,并将新列表传递给LiveData.为什么您需要主动调用notifyDataSetChanged(根据代码中的注释)也可能是这种情况.

Your issue seems to have a simple solution :). What you're doing is update only the value of the product that is passed to VM from view. What you should do is : create a copy of the product using .copy(quantity = ...) method, overwriting the quantity. Then replace your previous item in list and pass your new list to LiveData. This might also be the case why you need to actively call notifyDataSetChanged(as per your comment in code).

希望这会有所帮助.干杯!

Hope this helps. Cheers!

这篇关于将新列表提交给RecyclerView ListAdapter时,diff检查始终为areContentsTheSame()返回true的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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