blob: 05ec1d5cd718e604a73f62c4b626b88dd78d1206 [file] [log] [blame] [view]
Felix Angelov6f2d66d2019-01-07 13:53:35 -06001<img src="https://github.com/felangel/equatable/raw/master/doc/assets/equatable_logo_full.png" width="100%" alt="logo" />
Felix Angelovd778e3c2019-01-06 22:07:28 -06002<h2 align="center">
Felix Angelovb3762832019-01-06 22:04:01 -06003 Simplify Equality Comparisons
Felix Angelovd778e3c2019-01-06 22:07:28 -06004</h2>
Felix Angelovda3586e2019-01-06 20:49:23 -06005<p align="center">
Felix Angelov90567e42019-11-16 15:19:23 -06006 <a href="https://travis-ci.com/felangel/equatable">
7 <img alt="Build Status" src="https://travis-ci.com/felangel/equatable.svg?branch=master">
Felix Angelovda3586e2019-01-06 20:49:23 -06008 </a>
9 <a href="https://codecov.io/gh/felangel/equatable">
10 <img alt="Code Coverage" src="https://codecov.io/gh/felangel/equatable/branch/master/graph/badge.svg">
11 </a>
12 <a href="https://pub.dartlang.org/packages/equatable">
13 <img alt="Pub Package" src="https://img.shields.io/pub/v/equatable.svg">
14 </a>
15 <br/>
16 <a href="https://opensource.org/licenses/MIT">
17 <img alt="MIT License" src="https://img.shields.io/badge/License-MIT-blue.svg">
18 </a>
19 <a href="https://gitter.im/equatable_package/community">
20 <img alt="Gitter" src="https://img.shields.io/badge/gitter-equatable-yellow.svg">
21 </a>
22</p>
Felix Angelov774a32e2019-01-06 13:20:35 -060023
Felix Angeloveac630f2019-01-06 20:32:28 -060024---
25
Felix Angelov774a32e2019-01-06 13:20:35 -060026## Overview
27
28Being able to compare objects in `Dart` often involves having to override the `==` operator as well as `hashCode`.
29
30Not only is it verbose and tedious, but failure to do so can lead to inefficient code which does not behave as we expect.
31
32By default, `==` returns true if two objects are the same instance.
33
34Let's say we have the following class:
35
36```dart
37class Person {
Felix Angelovda3586e2019-01-06 20:49:23 -060038 final String name;
Felix Angelov774a32e2019-01-06 13:20:35 -060039
Felix Angelovda3586e2019-01-06 20:49:23 -060040 const Person(this.name);
Felix Angelov774a32e2019-01-06 13:20:35 -060041}
42```
43
44We can create create instances of `Person` like so:
45
46```dart
47void main() {
Felix Angelovda3586e2019-01-06 20:49:23 -060048 final Person bob = Person("Bob");
Felix Angelov774a32e2019-01-06 13:20:35 -060049}
50```
51
52Later if we try to compare two instances of `Person` either in our production code or in our tests we will run into a problem.
53
54```dart
55print(bob == Person("Bob")); // false
56```
57
58For more information about this, you can check out the official [Dart Documentation](https://www.dartlang.org/guides/language/effective-dart/design#equality).
59
60In order to be able to compare two instances of `Person` we need to change our class to override `==` and `hashCode` like so:
61
62```dart
63class Person {
Felix Angelovda3586e2019-01-06 20:49:23 -060064 final String name;
Felix Angelov774a32e2019-01-06 13:20:35 -060065
Felix Angelovda3586e2019-01-06 20:49:23 -060066 const Person(this.name);
Felix Angelov774a32e2019-01-06 13:20:35 -060067
Felix Angelovda3586e2019-01-06 20:49:23 -060068 @override
69 bool operator ==(Object other) =>
70 identical(this, other) ||
71 other is Person &&
72 runtimeType == other.runtimeType &&
73 name == other.name;
Felix Angelov774a32e2019-01-06 13:20:35 -060074
Felix Angelovda3586e2019-01-06 20:49:23 -060075 @override
76 int get hashCode => name.hashCode;
Felix Angelov774a32e2019-01-06 13:20:35 -060077}
78```
79
80Now if we run the following code again:
81
82```dart
83print(bob == Person("Bob")); // true
84```
85
86it will be able to compare different instances of `Person`.
87
88You can see how this can quickly become a hassle when dealing with complex classes. This is where `Equatable` comes in!
89
Felix Angelovdc2523e2019-01-06 13:31:51 -060090## What does Equatable do?
Felix Angelov774a32e2019-01-06 13:20:35 -060091
92`Equatable` overrides `==` and `hashCode` for you so you don't have to waste your time writing lots of boilerplate code.
93
94There are other packages that will actually generate the boilerplate for you; however, you still have to run the code generation step which is not ideal.
95
96With `Equatable` there is no code generation needed and we can focus more on writing amazing applications and less on mundane tasks.
97
98## Usage
99
100First, we need to do add `equatable` to the dependencies of the `pubspec.yaml`
101
102```yaml
103dependencies:
Majid Hajian7d65ce92019-11-20 14:42:16 +0100104 equatable: ^1.0.0
Felix Angelov774a32e2019-01-06 13:20:35 -0600105```
106
107Next, we need to install it:
108
109```sh
110# Dart
111pub get
112
113# Flutter
114flutter packages get
115```
116
117Lastly, we need to extend `Equatable`
118
119```dart
120import 'package:equatable/equatable.dart';
121
122class Person extends Equatable {
Felix Angelovda3586e2019-01-06 20:49:23 -0600123 final String name;
Felix Angelov774a32e2019-01-06 13:20:35 -0600124
Felix Angelov71e98962019-09-26 21:42:02 -0500125 Person(this.name);
126
127 @override
128 List<Object> get props => [name];
Felix Angelov774a32e2019-01-06 13:20:35 -0600129}
130```
131
Felix Angelov133770a2019-03-02 20:34:50 -0600132When working with json:
Felix Angelovca20d4e2019-07-13 02:08:27 -0500133
Kravchenko Igorcb3cfb62019-03-03 05:32:21 +0300134```dart
135import 'package:equatable/equatable.dart';
136
137class Person extends Equatable {
138 final String name;
139
Felix Angelov71e98962019-09-26 21:42:02 -0500140 Person(this.name);
141
142 @override
143 List<Object> get props => [name];
Kravchenko Igorcb3cfb62019-03-03 05:32:21 +0300144
Felix Angelov357b9fd2019-03-08 00:33:15 -0600145 factory Person.fromJson(Map<String, dynamic> json) {
146 return Person(json['name']);
Kravchenko Igorcb3cfb62019-03-03 05:32:21 +0300147 }
148}
149```
150
Felix Angelov774a32e2019-01-06 13:20:35 -0600151We can now compare instances of `Person` just like before without the pain of having to write all of that boilerplate.
Michael Joseph Rosenthal06245962020-01-08 10:51:42 -0600152**Note:** Equatable is designed to only work with immutable objects so all member variables must be final (This is not just a feature of `Equatable` - [overriding a `hashCode` with a mutable value can break hash-based collections](https://dart.dev/guides/language/effective-dart/design#avoid-defining-custom-equality-for-mutable-classes)).
Felix Angelovda3586e2019-01-06 20:49:23 -0600153
Felix Angelov71e98962019-09-26 21:42:02 -0500154Equatable also supports `const` constructors:
Felix Angelov0d117732019-08-29 09:43:01 -0500155
156```dart
157import 'package:equatable/equatable.dart';
158
159class Person extends Equatable {
160 final String name;
161
162 const Person(this.name);
163
164 @override
165 List<Object> get props => [name];
166}
167```
168
Felix Angelovda3586e2019-01-06 20:49:23 -0600169## Recap
170
171### Without Equatable
172
173```dart
174class Person {
175 final String name;
176
177 const Person(this.name);
178
179 @override
180 bool operator ==(Object other) =>
181 identical(this, other) ||
182 other is Person &&
183 runtimeType == other.runtimeType &&
184 name == other.name;
185
186 @override
187 int get hashCode => name.hashCode;
188}
189```
190
191### With Equatable
192
193```dart
194import 'package:equatable/equatable.dart';
195
196class Person extends Equatable {
197 final String name;
198
Felix Angelov71e98962019-09-26 21:42:02 -0500199 Person(this.name);
200
201 @override
202 List<Object> get props => [name];
Felix Angelovda3586e2019-01-06 20:49:23 -0600203}
204```
Felix Angelov92a5eb62019-01-09 22:44:58 -0600205
Felix Angelov8a5ca342019-02-20 20:18:34 -0600206## EquatableMixin
207
208Sometimes it isn't possible to extend `Equatable` because your class already has a superclass.
209In this case, you can still get the benefits of `Equatable` by using the `EquatableMixin`.
210
211### Usage
212
Remi Rousseletb12930c2019-08-26 11:10:17 +0200213Let's say we want to make an `EquatableDateTime` class, we can use `EquatableMixin` like so:
Felix Angelov8a5ca342019-02-20 20:18:34 -0600214
215```dart
Remi Rousseletb12930c2019-08-26 11:10:17 +0200216class EquatableDateTime extends DateTime with EquatableMixin {
Felix Angelov8a5ca342019-02-20 20:18:34 -0600217 EquatableDateTime(
218 int year, [
219 int month = 1,
220 int day = 1,
221 int hour = 0,
222 int minute = 0,
223 int second = 0,
224 int millisecond = 0,
225 int microsecond = 0,
226 ]) : super(year, month, day, hour, minute, second, millisecond, microsecond);
227
228 @override
Remi Rousseletb12930c2019-08-26 11:10:17 +0200229 List<Object> get props {
Felix Angelov8a5ca342019-02-20 20:18:34 -0600230 return [year, month, day, hour, minute, second, millisecond, microsecond];
231 }
232}
233```
234
Remi Rousseletb12930c2019-08-26 11:10:17 +0200235Now if we want to create a subclass of `EquatableDateTime`, we can just override `props`.
Felix Angelov8a5ca342019-02-20 20:18:34 -0600236
237```dart
Remi Rousseletb12930c2019-08-26 11:10:17 +0200238class EquatableDateTimeSubclass extends EquatableDateTime {
Felix Angelov8a5ca342019-02-20 20:18:34 -0600239 final int century;
240
241 EquatableDateTime(
242 this.century,
243 int year,[
244 int month = 1,
245 int day = 1,
246 int hour = 0,
247 int minute = 0,
248 int second = 0,
249 int millisecond = 0,
250 int microsecond = 0,
251 ]) : super(year, month, day, hour, minute, second, millisecond, microsecond);
252
253 @override
Remi Rousseletb12930c2019-08-26 11:10:17 +0200254 List<Object> get props => super.props..addAll([century]);
Felix Angelov8a5ca342019-02-20 20:18:34 -0600255}
256```
257
Felix Angelov92a5eb62019-01-09 22:44:58 -0600258## Performance
259
260You might be wondering what the performance impact will be if you use `Equatable`.
261
262[Performance Tests](https://github.com/felangel/equatable/raw/master/performance_tests) have been written to test how `Equatable` stacks up to manually overriding `==` and `hashCode` in terms of class instantiation as well as equality comparison.
263
Felix Angelov92a5eb62019-01-09 22:44:58 -0600264### Results (average over 10 runs)
265
266#### Equality Comparison A == A
267
Felix Angelov71e98962019-09-26 21:42:02 -0500268| Class | Runtime (μs) |
269| ------------------ | ------------ |
270| RAW | 0.193 |
271| Empty Equatable | 0.191 |
272| Hydrated Equatable | 0.190 |
Felix Angelov92a5eb62019-01-09 22:44:58 -0600273
274#### Instantiation A()
275
Felix Angelov71e98962019-09-26 21:42:02 -0500276| Class | Runtime (μs) |
277| ------------------ | ------------ |
278| RAW | 0.165 |
279| Empty Equatable | 0.181 |
280| Hydrated Equatable | 0.182 |
281
282\*_Performance Tests run using: Dart VM version: 2.4.0_