[MUSIC PLAYING]
CRAIG LABENZ: A common task in programming
is determining whether this object
you have over here is the same as that
object you have over there.
What does it mean to be the same as another object?
What if they're stored in different places in memory,
maybe because one was retrieved from an on-device cache,
but the other was loaded from the server,
but their values are all the same?
The idea that two records would be the same if their attributes
are all the same, regardless of how they arrived
or where they're stored in memory,
is called value equality, and it's usually
what developers want.
However, Dart is similar to most other programming languages
in that it does not operate this way
by default. In Dart, equality will always return false
for two objects created by separate non-const constructor
invocations.
Make a new class with one simple integer variable,
create two instances with the same value,
and Dart will not consider them equal.
Now, I said Dart counts two objects is different
if they come from separate non-const constructor
invocations.
So obviously, the first way to get around this
is to use const constructors where possible.
Use of a const constructor tells Dart to only ever create
one instance of a class for a given set of values
and at compile time.
So if we change our old code to use const,
suddenly a1 does equal a2.
This is neat, but a pinch academic,
because it only helps for objects that we know about at
compile time.
There's no way to use a const constructor for objects loaded
from a cache or the server.
Even classes that have const constructors
won't pass value equality onto their instances
unless the variables are created by that constructor.
Just because our A class now has a const constructor
means nothing if we don't use it.
So that's one option.
Use const constructors, which have limited flexibility.
Another option is to override the equality operator
for our class and give it precise instructions.
The method signature looks like this.
And a common pattern is to write this gobbledygook.
It checks whether the objects have the same address
in memory, then falls back to whether they're
of the same class, and lastly, compares their attributes.
Now, this code is not very fun to write or think about,
but luckily the equatable package on pub.dev
really makes things easier.
However, before we can fully understand equatable,
it's important to talk about hash codes.
Just like records in a database all
have a specific ID assigned by the database,
all objects in Dart have a hash code,
which is a pseudorandom number assigned by the Dart VM
during instantiation.
Returning to our fancy class, A, the two instances
of it we made, a1 and a2, we can evaluate
the hash codes ourselves and see the random numbers
with our own eyes.
But they're not interesting.
They're random.
And they'll change from one execution of our program
to the next.
Honestly, so far, they're not very helpful.
But what if we could make those hash codes
deterministic byproducts of our objects values?
That would simplify our equality override,
especially for classes with a lot of variables.
Luckily, we can do exactly this.
Imagine another class, B, which has several properties.
Override its hash code getter, and instead
of returning the original randomly assigned number,
return a value computed off the hash codes
of all of our attributes.
This means we're ready to plot that back in the equality
override, except instead of tediously comparing
every value, we can compare our respective hash codes
Now, this will work great, but it's still zero fun to write,
but that's OK, because this is where
the equatable package comes in.
With equatable, our definition for b shrinks to just this.
It should extend equatable, have the same fields
and constructors, and define a list of objects called props,
which equatable will wire into all of that tedious
boilerplate for us.
By internally overriding hash code and the equality operator,
equatable will allow two instances of our class
with identical values to finally be considered equivalent.
And one last note about hash code.
You may recall that what we in Dart call maps
are actually hash maps.
In Dart, everything has a hash code,
which means everything can be a key in a hash map.
However, once you start overriding the definition
of equality, objects behaviors as hash map keys change.
Imagine this code, which implements a class using
Dart's typical equality rules, and then uses an instance of it
as a key in a hash map.
You then create a fresh instance with the same values
and ask the map whether it already contains that key.
Will the map recognize it?
It will not, because they have been
assigned different pseudorandom hash codes.
However, do that same trick with a value equality class,
and we see that the map thinks it does already
contain a key matching a fresh instance.
Sets also work the same way, because again, we're
telling Dart how to decide if two things are equal,
and sets are never allowed to contain two equivalent items.
For more info on Dart, head to Dart.dev.
[MUSIC PLAYING]