Проблемы с DiffUtil: старый и новый список совпадают - PullRequest
1 голос
/ 07 июня 2019

Контекст:
MainActivity запускается, создает список ItemObject и вызывает ListFragment. Когда пользователь нажимает buttonMainActivity), он изменяет список, затем вызывает listFragment, чтобы обновить его.

Найдено проблем:
В функции update значения listFragment, newList и oldList совпадают.

Вот мой код.

MainActivity

class MainActivity : AppCompatActivity() {

    private val viewModel = MainViewModel()

    private var button : FloatingActionButton? = null

    private val listFragment = ListFragment.newInstance()

    var list = ArrayList<ItemObject>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.viewModel = viewModel

        button = binding.button
        button?.setOnClickListener { modifyList() }

        createItemsList()
    }

    /**
     *  This creates a list of 10 ItemObject, that will contains `i` as `randomInt` and "TEST $i" as `title`, then call
     *  setListFragment().
     */
    private fun createItemsList() {
        val itemsList = ArrayList<ItemObject>()
        var i = 0
        while (i < 10) {
            itemsList.add(ItemObject().apply { this.randomInt = i ; this.title = "TEST $i" })
            i++
        }
        list = itemsList
        setListFragment()
    }

    /**
     *  Set listFragment inside content.
     */
    private fun setListFragment() {
        supportFragmentManager.beginTransaction().replace(R.id.content, listFragment).commit()
    }

    /**
     *  Triggered when the user clicks on the FloatingActionButton. Will modify each even item, add 2 to its `randomInt`
     *  and set its `title` to "MODIFIED $randomInt".
     */
    private fun modifyList() {
        list.forEach {
            if (it.randomInt % 2 == 0) {
                it.randomInt += 2
                it.title = "MODIFIED ${it.randomInt}"
            }
        }
        if (listFragment.isAdded) {
            listFragment.updateList(list)
        }
    }

    inner class MainViewModel
}

А ListFragment:

class ListFragment : Fragment() {

    private val viewModel = ListViewModel()
    private val listAdapter = ListAdapter()

    private var listRv : RecyclerView? = null

    private var list = ArrayList<ItemObject>()

    override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding = DataBindingUtil.inflate<FragmentListBinding>(inflater, R.layout.fragment_list, parent, false)
        binding.viewModel = viewModel

        listRv = binding.listRv

        list = (activity as MainActivity).list

        setList()

        return (binding.root)
    }

    /**
     *  Sets up the RecyclerView and set the list inside it.
     */
    private fun setList() {
        listRv?.layoutManager = LinearLayoutManager(context)
        listRv?.adapter = listAdapter
        listRv?.post { listAdapter.setData(list) }
    }

    /**
     *  Triggered by MainActivity when the user clicks on the button and the list is modified. Will call update() method
     *  from adapter.
     */
    fun updateList(newList : ArrayList<ItemObject>) {
        listAdapter.update(newList)
    }

    companion object {
        fun newInstance() : ListFragment = ListFragment()
    }

    inner class ListViewModel

    inner class ItemDiff : DiffUtil.Callback() {

        private var old = ArrayList<ItemObject>()
        private var new = ArrayList<ItemObject>()

        fun setLists(old : ArrayList<ItemObject>, new : ArrayList<ItemObject>) {
            this.old = old
            this.new = new
        }

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            println("ARE ITEMS ${old[oldItemPosition].title} THE SAME ? ${(old[oldItemPosition] == new[newItemPosition])}")
            return (old[oldItemPosition] == new[newItemPosition])
        }

        override fun getOldListSize(): Int = old.size

        override fun getNewListSize(): Int = new.size

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            println("ARE ITEMS ${old[oldItemPosition].title} CONTENTS THE SAME ? ${(old[oldItemPosition].title == new[newItemPosition].title
                && old[oldItemPosition].randomInt == new[newItemPosition].randomInt)}")
            return (old[oldItemPosition].title == new[newItemPosition].title
                && old[oldItemPosition].randomInt == new[newItemPosition].randomInt)
        }

        override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
            val oldItem = old[oldItemPosition]
            val newItem = new[newItemPosition]
            val bundle = Bundle()
            if (oldItem.title != newItem.title) {
                println("SHOULD ADD NEW STRING ${newItem.title}")
                bundle.putString("title", newItem.title)
            }
            if (oldItem.randomInt != newItem.randomInt) {
                println("SHOULD ADD NEW INT ${newItem.randomInt}")
                bundle.putInt("randomInt", newItem.randomInt)
            }
            return (bundle)
        }
    }

    inner class ListAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

        private var items = ArrayList<ItemObject>()

        fun setData(list : ArrayList<ItemObject>) {
            items = list
            notifyDataSetChanged()
        }

        /**
         *  @param new
         *  Triggered when the list is modified in the parent activity. Uses DiffUtil to update the list.
         */
        fun update(new : ArrayList<ItemObject>) {
            println("///// IN UPDATE ; WILL PRINT OLD AND NEW LIST /////")
            items.forEach { println("OLD ITEM ${it.title}") }
            new.forEach { println("NEW ITEM ${it.title}") }
            println("///// PRINT END /////")
            val diffCallback = ItemDiff()
            diffCallback.setLists(old = items, new = new)
            val diffResult = DiffUtil.calculateDiff(diffCallback)
            diffResult.dispatchUpdatesTo(this)
            items = new

        }

        override fun onCreateViewHolder(parent: ViewGroup, position: Int): RecyclerView.ViewHolder {
            return (ItemViewHolder(DataBindingUtil.inflate<ItemBinding>(LayoutInflater.from(parent.context),
            R.layout.item, parent, false).apply {
                viewModel = ItemViewModel()
            }))
        }

        override fun getItemCount(): Int = items.size

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            (holder as ItemViewHolder).setData(items[position])
        }

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) {
            println("IN ON BIND VIEWHOLDER ; PAYLOAD SIZE = ${payloads.size}")
            if (payloads.isEmpty())
                super.onBindViewHolder(holder, position, payloads)
            else {
                val bundle = payloads[0] as Bundle
                if (bundle.size() != 0) {
                    val name = bundle.getString("name")
                    val randomInt = bundle.getInt("randomInt")
                    if (name != null)
                        (holder as ItemViewHolder).setName(name)
                    (holder as ItemViewHolder).setRandomInt(randomInt)
                }
            }
        }

        inner class ItemViewHolder(private val binding : ItemBinding) : RecyclerView.ViewHolder(binding.root) {

            fun setData(item : ItemObject) {
                binding.viewModel?.setData(item)
            }

            fun setRandomInt(newInt : Int) {
                binding.viewModel?.setRandomInt(newInt)
            }

            fun setName(newName : String) {
                binding.viewModel?.setTitle(newName)
            }
        }

        inner class ItemViewModel {

            val title = ObservableField<String>("")
            val randomInt = ObservableField<String>("")

            fun setData(item : ItemObject) {
                setRandomInt(item.randomInt)
                setTitle(item.title)
            }

            fun setRandomInt(newInt : Int) {
                randomInt.set(newInt.toString())
            }

            fun setTitle(newName : String) {
                title.set(newName)
            }
        }

    }
}

Вот мой ItemObject класс:

class ItemObject {

    var title = ""
    var randomInt = 0
}

Странно, что я НИКОГДА не изменяю свой список внутри ListFragment. Так как это возможно, что при обновлении списка моя отладочная печать показывает мне, что старый и новый списки совпадают?

1 Ответ

0 голосов
/ 07 июня 2019

Вы можете попробовать сделать:

fun setData(list : ArrayList<ItemObject>) {
            items.clear
            items.addAll(list)
            notifyDataSetChanged()
}
...