androidkotlinandroid-recyclerviewandroid-diffutils

DiffUtil.Callback getChangePayload oldItem is same as newItem


I have a DiffUtil.Callback that compares 2 lists of model StorageModelUi.

sealed class StorageModelUi {
    class ServerItem(val server: Server): StorageModelUi()
    class StorageContent(val used: Int, val large: Int, val server: Server, val attachmentWithMessageList: List<AttachmentWithMessage>): StorageModelUi()
}


data class AttachmentWithMessage(
    @Embedded
    val attachment: Attachment,

    @Relation(entity = Message::class, parentColumn = "attachments_message_id", entityColumn = "messages_message_id")
    val message: Message
)

Each StorageModelUi item can be either a Server (which is like a Header to the RecyclerView) or a StorageContent (which is 2 TextViews and a RecyclerView below them). So i create a list of StorageModelUi with the function

fun Map<Server, List<AttachmentWithMessage>>.groupToStorageModelUi(): List<StorageModelUi> {
    val final = mutableListOf<StorageModelUi>()

    for ((server, list) in this) {
        final.add(StorageModelUi.ServerItem(server))

        final.add(
            StorageModelUi.StorageContent(
                used = list.sumOf { s -> s.attachment.size },
                large = list.filter { s -> s.attachment.size > 5 * 1000 * 1024 }.sumOf { s -> s.attachment.size },
                server = server,
                attachmentWithMessageList = list
            )
        )
    }
    return final
}

The recyclerView gets updated when an attachment is being downloaded, so the only thing that changes the UI is the percent, which i take it from the attachmentWithMessage.message.body field. For that reason i have created a DiffUtil to trace that change and return a payload to the BindViewHolder.

class StorageDiffUtilCallback(
    private val oldList: List<StorageModelUi>,
    private val newList: List<StorageModelUi>
): DiffUtil.Callback() {

    override fun getOldListSize(): Int =
        oldList.size

    override fun getNewListSize(): Int =
        newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]

        return if (oldItem is StorageModelUi.StorageContent && newItem is StorageModelUi.StorageContent)
            oldItem.server.serverId == newItem.server.serverId
        else if (oldItem is StorageModelUi.ServerItem && newItem is StorageModelUi.ServerItem)
            oldItem.server.serverId == newItem.server.serverId
        else false
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]

        return if (oldItem is StorageModelUi.StorageContent && newItem is StorageModelUi.StorageContent) {
            oldItem.attachmentWithMessageList.map { it.message } == newItem.attachmentWithMessageList.map { it.message }
        } else if (oldItem is StorageModelUi.ServerItem && newItem is StorageModelUi.ServerItem)
            oldItem.server.serverId == newItem.server.serverId
        else false
    }

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        println("## $$ $oldItem")
        println("## $$ $newItem")

        return when {
            oldItem is StorageModelUi.StorageContent && newItem is StorageModelUi.StorageContent -> {
                println("## $$ oldItem is StorageModelUi.StorageContent")
                if (oldItem.large != newItem.large) {
                    println("## $$ large")
                    Bundle().apply {
                        putString("key", "large")
                    }
                } else if (oldItem.used != newItem.used) {
                    println("## $$ used")
                    Bundle().apply {
                        putString("key", "used")
                    }
                } else if (oldItem.attachmentWithMessageList.map { it.message.body.value } != newItem.attachmentWithMessageList.map { it.message.body.value }) {
                    println("## $$ IT SHOULD HAVE BEEN IN HERE BUT IT DOES NOT...!!!!!!!")
                    println("## $$ attachmentWithMessageList payLoad")
                    Bundle().apply {
                        putString("key", "percent")
                    }
                }
                else {
                    println("## $$ First else")
                    super.getChangePayload(oldItemPosition, newItemPosition)
                }
            }
            else -> {
                println("## $$ super else")
                super.getChangePayload(oldItemPosition, newItemPosition)
            }
        }
    }

The list of the RecyclerView is updated with this

storageViewModel.storageModelUi.observe(viewLifecycleOwner) {
            it?.let {
                viewAdapter.updateStorage(it)
            }
        }


    // StorageRecycler Adapter
    var storage = mutableListOf<StorageModelUi>()

    fun updateStorage(newStorage: List<StorageModelUi>) {
        val diffCallback = StorageDiffUtilCallback(this.storage, newStorage)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        this.storage.clear()
        this.storage.addAll(newStorage)
        diffResult.dispatchUpdatesTo(this)
    }
// Inside BindViewHolder of the StorageRecycler 
fun bind(storage: StorageModelUi.StorageContent, storageCallback: RecyclerCallback.StorageCallback) {
            bindTexts(storage)

            val viewManager = LinearLayoutManager(itemView.context)
            viewManager.initialPrefetchItemCount = storage.attachmentWithMessageList.size
            viewAdapter = DownloadRecyclerAdapter(storageCallback)

            downloadRecycler.apply {
                setHasFixedSize(true)
                layoutManager = viewManager
                adapter = viewAdapter
            }

            bindAdapter(storage)
        }

        fun bindTexts(storage: StorageModelUi.StorageContent) {
            used.text = FileUtils.getFileSize(storage.used.toLong())
            large.text = FileUtils.getFileSize(storage.large.toLong())
        }

        fun bindAdapter(storage: StorageModelUi.StorageContent) {
            viewAdapter.server = storage.server
            viewAdapter.updateItem( storage.attachmentWithMessageList.toMutableList())
        }

// Nested RecyclerAdapter (that shows the Download attachments
var downloadItems = mutableListOf<AttachmentWithMessage>()
    var server: Server? = null

    fun updateItem(newDownloadItem: List<AttachmentWithMessage>) {
        val diffCallback = DownloadRecyclerDiffUtilCallback(this.downloadItems, newDownloadItem)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        this.downloadItems.clear()
        this.downloadItems.addAll(newDownloadItem)
        diffResult.dispatchUpdatesTo(this)
    }

However, i have noticed that although the "areContentsTheSame" returns false, when it goes to the getChangePayload, it never goes inside the 3rd IF statement. So after debugging it seems that the oldItem == newItem returns true, and as i see in the Logcat, the oldItem.message == newItem.message. How can this be? I mean why the old and new items are the same??


Solution

  • Problem solved. First of all we should declare the list inside the Adapter as listOf<> and not as mutableListOf(). That means that the .clear() and .addAll() should change. After that in the DiffUtil.Callback i should check the fields that i only need