DiffUtil打破了areContentTheSame合同

问题描述 投票:4回答:1

最近我发现我的应用程序奇怪的崩溃。我发现,他们是由ListAdapter造成的 - > DiffUtil下方。合同说,只有当areContentsTheSame返回相应的项目真正areItemsTheSame回调将被调用。问题是areContentsTheSame称为该areItemsTheSame是从来没有所谓的项目。

我在String项目测试它,所以它不应该与我自己回收的实现。我真的很困惑,如果是我的错(几乎不存在逻辑现在)或错误的DiffUtil工具

我创建了失败上提到的情况简单的仪表测试 - 可能更有经验的人来看看是:

package com.example.diffutilbug

import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner

@RunWith(BlockJUnit4ClassRunner::class)
internal class ExampleUnitTest {

    @Test
    fun testDiffUtil4() {

        val handler = CoroutineExceptionHandler { _, exception ->
            throw exception
        }
        // adapter compare items :
        // areItemsTheSame -> compare length of String
        // areContentsTheSame -> compare content with ==
        val adapter = StringAdapterJunit(handler)

        runBlocking {
            adapter.submitList(
                mutableListOf<String>(
                    "1",//1,
                    "22",//2,
                    "333",//3,
                    "4444",//4,
                    "55555",//5,
                    "666666",//6,
                    "7777777",//7,
                    "88888888",//8,
                    "999999999",//9,
                    "55555",//5,
                    "1010101010",//10,
                    "1010109999",//10,
                    "55555",//5,
                    "1313131313",//10,
                    "1414141414",//10,
                    "55555",//5,
                    "1313131313",//10,
                    "1414141414",//10,
                    "55555"//5

                )
            )

            delay(40)

            adapter.submitList(
                mutableListOf<String>(
                    "55555",//5,
                    "1010101010",//10,
                    "1010109999",//10,
                    "55555",//11,
                    "1313131313",//10,
                    "1414141414",//10,
                    "11111111111"//11
                )
            )

            delay(500)
        }
    }

}

// Stub Adapter for Strings that uses DiffUtil underneath.
// logs all callbacks to logcat

class StringAdapterJunit(val handler: CoroutineExceptionHandler) : ListAdapter<String, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<String>() {
    override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
        Log.e("DiffUtilTest", "areItemsTheSame comparing $oldItem with $newItem = ${oldItem.length == newItem.length}")
        return oldItem.length == newItem.length
    }

    override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
        //should be called only if areContentsTheSame == true
        Log.e(
            "DiffUtilTest",
            "areContentsTheSame error = ${oldItem.length != newItem.length} comparing $oldItem with $newItem"
        )

        runBlocking {
            GlobalScope.launch(handler + Dispatchers.Main) {
                assertTrue("areContentsTheSame can be called only if areItemsTheSame return true" , areItemsTheSame(oldItem, newItem))
            }.join()
        }
        return oldItem == newItem
    }

    override fun getChangePayload(oldItem: String, newItem: String): Any? {
    //should be called only if areItemsTheSame = true and areContentsTheSame = false

        Log.e(
            "DiffUtilTest",
            "getChangePayload error = ${oldItem.length == newItem.length && oldItem == newItem} $oldItem with $newItem"
        )
        return null
    }
}) {
    // stub implementation on adapter - never used
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = object : RecyclerView.ViewHolder(View(null)) {}


    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}

    override fun getItemViewType(position: Int): Int = getItem(position).length
}

并在需要它依赖的gradle:

dependencies {
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'

    //coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
}

请注意,您需要添加

android.useAndroidX=true
android.enableJetifier=true

gradle.properties

协同程序和处理程序添加,因为DiffUtil计算在后台线程diff和JUnit处理断言只在主线程异常

android android-support-library recycler-adapter listadapter androidx
1个回答
1
投票

我已经得到来自谷歌作出回应,他们确认有臭虫DiffUtil当列表包含重复项目(空,相同的对象等)

我现在的解决方法是通过自己执行,以便之前检查“合同”:

override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
    return compare items
}

override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
    //should be called only if areContentsTheSame == true
    return areItemsTheSame(oldItem, newItem) && compare items contents

}

override fun getChangePayload(oldItem: String, newItem: String): Any? {
    //should be called only if areItemsTheSame = true and areContentsTheSame = false
    if(areItemsTheSame(oldItem, newItem) && !areContentsTheSame(oldItem, newItem)) {
        return compute changePayload
    } else {
         return null
    }
}

将更新回答问题时,解决武功

© www.soinside.com 2019 - 2024. All rights reserved.