Home > Back-end >  How can DiffUtil know about list changes when only the companion object has changed?
How can DiffUtil know about list changes when only the companion object has changed?

Time:03-31

enter image description here

As shown in the image, I would like the unit of the Detail item to be changed at once according to the toggle button.

Detail list items were set as companion objects because it was determined that it was not necessary to have a unit property individually.

However, it seems that DiffUtil determines that there is no change between the new list and the old list, perhaps because the unit property is set as a companion object.

So there is no update of the view either.

How can I make DiffUtil responsive while changing the companion object?


Detail

@Entity
data class Detail(
    @PrimaryKey(autoGenerate = true)
    var id: Int,
    val set: Int,
    var weight: String = "",
    var reps: String = "") {

    companion object {
        var title: String = ""
        var unit: String = "kg"
        val memo = ""
    }
}

ViewModel

class DetailViewModel(application: Application) : ViewModel() {
    private val repository: DetailRepository
    private val _items: MutableLiveData<List<Detail>> = MutableLiveData()
    val items = _items
    private val list: List<Detail>
        get() = _items.value ?: emptyList()

    init {
        val detailDao = DetailDatabase.getDatabase(application)!!.detailDao()
        repository = DetailRepository(detailDao)
    }

    fun changeUnit(unit: String) {
        Detail.unit = unit
        if(list == null)
            return

        _items.postValue(list) // To notify the observer.
    }

    fun addDetail() {

        viewModelScope.launch(Dispatchers.IO){
            val item = Detail(0, set = list.size 1)
            repository.add(item)

            // If use plus(), a new List is returned.
            // Therefore, the change is notified to the Observer by postValue of the new list added.
            _items.postValue(list.plus(item))

        }
    }
    fun deleteDetail() {
        // Delete the last set and return a new list to postValue to notify the Observer of the change.
        _items.postValue(list.dropLast(1))
    }
}

DiffUtil

class DetailDiffCallback : DiffUtil.ItemCallback<Detail>() {
    override fun areItemsTheSame(
        oldItem: Detail,
        newItem: Detail
    ): Boolean  {
       return (oldItem.id == newItem.id)
    }

    override fun areContentsTheSame(
        oldItem: Detail,
        newItem: Detail
    ): Boolean {
        return oldItem == newItem
    }
}

Fragment

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    vm.items.observe(viewLifecycleOwner) { newList ->
        adapter.submitList(newList)
    }
}

CodePudding user response:

Detail list items were set as companion objects because it was determined that it was not necessary to have a unit property individually.

This is the root of the problem. If you want DiffUtil to be able to "see" these changes, you will have to move this information out of the companion object.

DiffUtil works by taking in two instances of your class and doing work (the areItemsTheSame() and areContentsTheSame() methods) to see if anything has changed. Since this information is part of the companion object, it will always be identical for all instances, which means there's no way for DiffUtil to detect a change, even if one has happened.

CodePudding user response:

Like Ben P says, when you change the value in the companion object, that affects the entire class (since they all share that object). It's not included in the generated equals() code for the data class - because why would it need to be? Every instance shares the same value, it's not part of the state!

Even if it were, when you compare oldList and newList, oldList has still been "updated" with the new unit value, because it's in that shared companion object. If you want oldList and newList to be able to have different values for unit, they need to be per-instance properties.


But the way you're doing things here, the unit doesn't even seem to be part of the data, right? It's not like you're storing a value and a unit of measure, and doing conversions when the displayed unit type changes. It looks like this is just a general display option, that just applies to how the data is displayed.

So in that case, why not just call notifyDataSetChanged() on the adapter, or something similar? Force a redraw, let it show the new unit type, that's it. I'm not sure if you need to do anything special if you're using DiffUtil, but that's what I'd look into.

(I feel like storing the unit type in the data would be a way better approach, seems important to what the stored number actually means, but for what you're doing right now, a display refresh should be enough)

  • Related