Comparing objects in Dart made easy with Equatable.
Sometimes we need to compare objects to see if they are equal to one another. This can easily be accomplished with the equatable package.
I took a closer look into this concept of equality when I started working on the puzzle game that I’m building. It was not the first time I’ve had to compare objects, but this time it became more crucial. Now my game heavily relies on comparing objects and by doing it the 'right' way, it even made my code easier to read and understand.
Allow me to enlighten you.
Same vs Equal
So there is a difference between objects being the same, and objects being equal.
When we are talking about objects being the same, we refer to the same instance of an object. When you’re carpooling to work, you and your colleague are in the same car.
On the other hand, when you and your colleague both go with your own cars, they could be considered equal, depending on which properties you decide to compare. Like when the cars are the same make, year and color, we could consider them equal, right?
Comparing objects in Dart
The most obvious way to compare two objects is of course the ==
operator. This will in fact check for equality. To check if two objects are the same instance, we should use identical()
.
identical
can act a little unexpected when working with primitives likeint
orString
. You hardly ever need to useidentical
but you should be aware.
Let’s checkout some examples:
As you probable noticed, both identical and equality checks result in the same output. So what is going on here?
Hashcode and ==
By default, each instance you create is unique. This explains the behavior in the example above.
When we want two instances to be considered equal, we must override ==
operator and hashcode
on that class. When overriding, it’s important that the outcomes of both overrides are consistent.
When we want two objects to be considered equal the ==
operator should return true
and hashcode
on both objects should be returning the same int
.
Let’s do this for our car example:
Now that we’ve implemented our own logic for ==
and hashcode
, we decide which instances of Car
s are equal. We do this by checking the three properties; color
, make
and year
. We also calculate a new hashcode
based on these properties.
As you can see in the example, car1
and car2
are instantiated with the same properties, so they are now considered equal.
car3
is completely different, and is therefor not equal to car1
.
How we implement the ==
and hashcode
logic is completely up to us. We could leave out certain properties if we’d like. The important thing is that both ==
and hashcode
behave similar. If ==
says the objects are equal, then hashcode
of both objects should also return the same value.
As you might think after seeing the example above; “damn, that’s a lot of work for something so simple”. And you are right. In the example above it takes up most of the class for a simple class that only holds a little bit of data.
Not only that, it is very error prone. The tiniest mistake can lead to unexpected behavior.
Luckily, there is a solution…
Equatable
To help you gain the advantages of object equality and prevent you from making application breaking mistakes, there is the equatable package.
Equatable will do the necessary overrides for you. All you have to do is tell it which properties to take into account by implementing the props
getter.
Let’s update our example so it uses Equatable
.
That’s it. This will now result in the same outcome as our tedious manual override.
If your class already
extends
another class, you can use theEquatableMixin
instead.
Using object equality in your code
We can now use our new ‘equatable’ objects to write code that’s easier to read, understand and maintain.
Comparing objects
Let’s start with a simple example of comparing objects that don’t override ==
and hashcode
or implement Equatable
.
As you can see, we have to manually compare each and every property of which we decided to be of importance for our equality check. And this example only has three properties…
So let’s now do this for when Car
extends Equatable
.
Now doesn’t this look way better, and less error prone? I think so too!
Sets and Maps
Not only we can use it to compare objects, Dart uses it as well. Set
s and Map
s use equality comparison to define uniqueness. Set
uses equality checks to prevent two equal
objects to be entered and Map
uses it for the keys.
Pro tip: If you want all the distinct objects in a
List
(of items that extendEquatable
) you can simply do:myList.toSet()
.
Unit tests
Last, but certainly not least, Equatable
objects make it so much easier for us to write unit tests. Similar to what we’ve done in ‘Comparing objects’ above, we can simplify our expect
.
Conclusion
Equatable makes it much easier for us to create comparable objects by allowing us to tell which fields to take into account.
Comparable objects can improve the readability and maintainability of our code by making comparing shorter and less error prone.