16

It seems Android Studio/Gradle 3.4 has introduced a new lint error DiffUtilEquals. It is triggered by having a DiffUtil<Any> and then calling as a fallback oldItem == newItem in the areContentsTheSame function. The error the linter throws is

Suspicious equality check: equals() is not implemented in Object

Example code:

 override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
        return when {
            oldItem is MyKotlinClass && newItem is MyKotlinClass -> true
            oldItem is MyKotlinClass2 && newItem is MyKotlinClass2 -> oldItem.title == newItem.title
            else -> oldItem == newItem // Lint error on this line
        }
    }

This when statement would be pretty common in a DiffUtil for an Adapter that has multiple types where you compare each type based on their class.

What is the best way to handle this error? Should <Any> be changed to some interface that is like Equatable or maybe all the classes used in the adapter should implement some interface that includes a way to compare them?

ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Ben Trengrove
  • 8,191
  • 3
  • 40
  • 58
  • "This code would be pretty common in a DiffUtil for an Adapter that has multiple types" -- that first case in your `when` seems rather strange. The contents are the same... purely based on what Kotlin class they are? – CommonsWare Apr 28 '19 at 23:20
  • Just a contrived example, you're right that it probably wouldn't happen in the real world but I wanted to keep the example simple – Ben Trengrove Apr 28 '19 at 23:21
  • Where exactly are you getting the class from? The [embedded DiffUtil](https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil.Callback.html#areContentsTheSame(int,%2520int)) takes ints in the `areContentsTheSame`, and there are no generic types. – Zoe Apr 29 '19 at 06:56
  • This one, https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil.ItemCallback.html – Ben Trengrove Apr 29 '19 at 07:05
  • add data keyword to your class. eg data class Model – Kunchok Tashi Jun 23 '21 at 09:58

2 Answers2

22

All java.lang.Objects have an equals() function. It's a part of the base of the language. However, not all override it, and that's what the linter is triggering on. (SomeClass() as Any).equals(SomeClass()) will compile fine for an instance (assuming you have a class named SomeClass of course).

I couldn't reproduce this with just any class - it had to be the one you mentioned (DiffUtil.ItemCallback). I expanded the inspection, which says:

Suspicious equality check: equals() is not implemented in Object

Inspection info:areContentsTheSame is used by DiffUtil to produce diffs. If the method is implemented incorrectly, such as using identity equals instead of equals, or calling equals on a class that has not implemented it, weird visual artifacts can occur.

This answer is best demonstrated with a different snippet:

data class One(val t: String)

val x = object : DiffUtil.ItemCallback<One>() {
    override fun areItemsTheSame(p0: One, p1: One): Boolean { TODO() }
    override fun areContentsTheSame(p0: One, p1: One): Boolean {
        return p0 == p1
    }

}

This will compile. If you don't know, a data class generates a custom equals method. If you, in Android Studio, remove the data keyword, the error will reappear, because there is no overridden equals method.

TL;DR: The inspection complains about a lack of a custom equals method, and/or the use of identity checking (== in Java or === in Kotlin). However, === will raise a separate message which is considerably easier to actually identify the solution to:

Suspicious equality check: Did you mean == instead of ===?

And I imagine == in Java raises a similar message, but with equals as the suggested replacement. I have not verified this

As for solutions:

If you know what you're doing (NOT recommended)

You can suppress or change the severity away from error. Changing the severity or suppressing globally is in the same place. File -> Settings -> Editor -> Inspections > Android -> Lint -> Correctness -> Suspicious DiffUtil equality

enter image description here

Or you can suppress it locally with:

@SuppressLint("DiffUtilEquals")

If you want to play it safe

This is unfortunately more complicated.

Any has no guarantee equals is overridden - hence the inspection. The only viable option you really have is to use a different class. And using an Equatable isn't a bad idea either. However, unlike Any, this isn't implemented by default. In fact, it doesn't exist in the SDK. You can, however, create one yourself.

The catch is, any of the implementing classes need an equals method now. If you use data classes, this isn't a problem (and I've demonstrated this in the code). However, if you don't, you'll need to implement it manually. Manually here either means you write it, or you otherwise have it generated (for an instance with annotations).

interface Equatable  {
    override fun equals(other: Any?) : Boolean;
}

// Data classes generate the necessary equals methods. Equatable ensures all child classes do implement it, which fixes what the inspection wants you to fix.
data class One(val t: String) : Equatable  
data class Two(val title: String) : Equatable  

val x = object : DiffUtil.ItemCallback<Equatable /*Or a higher level inheritance model, i.e. a MySharedItemClass, where it either contains an abstract equals or implements Equatable. Implementing it doesn't require it to be abstract, but it depends on where you want the equals implementation. Equatable is an example of forced equals implementation, but there's many ways to Rome. */>() {
    override fun areItemsTheSame(p0: Equatable, p1: Equatable): Boolean {
        TODO("not implemented")
    }

    override fun areContentsTheSame(p0: Equatable, p1: Equatable): Boolean {
         return when {
             p0 is One && p1 is One -> true
             p0 is Two && p1 is Two -> p0.title == p1.title
             else -> p0 == p1 // No error!
         }
    }
}
Community
  • 1
  • 1
Zoe
  • 27,060
  • 21
  • 118
  • 148
  • Excuse me, this might be a stupid question, but what is `MySharedItemClass`? I don't see it's implementation in your answer. – Marat Feb 28 '20 at 15:21
  • @Marat I have no idea. Assumed it was a typo and that I meant `Equatable`. No idea where it came from, but it's not supposed to be there :') – Zoe Feb 28 '20 at 16:29
2

bro all what you need is to override equals() in your POJO class and voila this error will disappear

Michael
  • 411
  • 2
  • 6
  • 15