Equality in Flutter and Dart with Equatable

In this article, we will review how equality works in Dart and Flutter and how the Equatable package can help us to avoid writing a lot of boilerplate code.

We already know that If we want to compare two variables, we can use the == operator. For example, if we want to compare two strings, the code is:

  final car1 = "Toyota";
  final car2 = "Toyota";
  print(car1 == car2); // Result: true

And if we want to compare two numbers, the code is:

  final phone1 = 52123456;
  final phone2 = 52123456;
  print(phone1 == phone2); // Result: true

Let's run the previous code in DartPad:

Compare two different objects of the same class

In the previous examples, we learned that comparing two variables is easy using ==, but when we want to compare two objects of the same class is not that easy.

If we have the following class:

class Car {
  final String brand;

  Car(this.brand);
}

And we create two objects with the same values:

  final car1 = Car('Toyota');
  final car2 = Car('Toyota');
  print(car1 == car2); // false

We may think that car1 and car2 are equal, but they are not. Comparing car1 and car2 will be false.

Let's run the previous code in DartPad:

Why is false if all the values are the same? The answer is easy, in Dart, the operator == verifies the memory address and not the properties of the objects. Because car1 and car2 are in different memory addresses, the result is false

Using the const constructor

Using the const constructor tells Dart to create only one class instance for a given set of values at compile time. Let's run the previous code, but this time using const constructors:

Using the const constructors solves the equality problem but only works for objects we know at compile time. There is no way to use a const constructor for objects created at runtime. This is where Equatable can help us.

Equatable package

Without the help of Equatable, if we want to compare the property values of two objects to check if they are the same, we have to override == and hashCode. The Car class code will be:

class Car {
  final String brand;

  const Car(this.brand);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Car && 
    runtimeType == other.runtimeType && 
    brand == other.brand;

  @override
  int get hashCode => brand.hashCode;
}

Equatable makes it easier to compare objects of the same class without overriding == and hashCode.

Internally Equatable overrides == and hashCode so we do not waste time writing boilerplate code.

Using Equatable the previous code will be:

import 'package:equatable/equatable.dart';

class Car extends Equatable {
  const Car(this.brand);
  
  final String brand;

  @override
  List<Object> get props => [brand];
}

If we add more properties like name, the code will be:

class Car extends Equatable {
  const Car(this.brand, this.name);

  final String brand;
  final String name;

  @override
  List<Object> get props => [brand, name];
}

Let's run the previous code in DartPad:

Now, when we compare two objects with the same values, the result is true. Try changing the values of car2. What is the result?

Conclusion

Equatable is a handy package to compare objects of the same class. Without it, we will have to write a lot of boilerplate code.

Share this article