• Stars
    star
    784
  • Rank 58,032 (Top 2 %)
  • Language
    Dart
  • License
    MIT License
  • Created almost 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Dart package that helps to implement value based equality without needing to explicitly override == and hashCode.

logo

Simplify Equality Comparisons

Build Status Code Coverage Pub Package
Star on GitHub style: effective dart Discord MIT License


Overview

Being able to compare objects in Dart often involves having to override the == operator as well as hashCode.

Not only is it verbose and tedious, but failure to do so can lead to inefficient code which does not behave as we expect.

By default, == returns true if two objects are the same instance.

Let's say we have the following class:

class Person {
  const Person(this.name);

  final String name;
}

We can create instances of Person like so:

void main() {
  final Person bob = Person("Bob");
}

Later if we try to compare two instances of Person either in our production code or in our tests we will run into a problem.

print(bob == Person("Bob")); // false

For more information about this, you can check out the official Dart Documentation.

In order to be able to compare two instances of Person we need to change our class to override == and hashCode like so:

class Person {
  const Person(this.name);

  final String name;

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

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

Now if we run the following code again:

print(bob == Person("Bob")); // true

it will be able to compare different instances of Person.

You can see how this can quickly become a hassle when dealing with complex classes. This is where Equatable comes in!

What does Equatable do?

Equatable overrides == and hashCode for you so you don't have to waste your time writing lots of boilerplate code.

There 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.

With Equatable there is no code generation needed and we can focus more on writing amazing applications and less on mundane tasks.

Usage

First, we need to do add equatable to the dependencies of the pubspec.yaml

dependencies:
  equatable: ^2.0.0

Next, we need to install it:

# Dart
pub get

# Flutter
flutter packages get

Lastly, we need to extend Equatable

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

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

When working with json:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

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

  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(json['name']);
  }
}

We can now compare instances of Person just like before without the pain of having to write all of that boilerplate. 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).

Equatable also supports const constructors:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

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

toString Implementation

Equatable can implement toString method including all the given props. If you want that behaviour for a specific Equatable object, just include the following:

@override
bool get stringify => true;

For instance:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

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

  @override
  bool get stringify => true;
}

For the name Bob, the output will be:

Person(Bob)

This flag by default is false and toString will return just the type:

Person

EquatableConfig

stringify can also be configured globally for all Equatable instances via EquatableConfig

EquatableConfig.stringify = true;

If stringify is overridden for a specific Equatable class, then the value of EquatableConfig.stringify is ignored. In other words, the local configuration always takes precedence over the global configuration.

Note: EquatableConfig.stringify defaults to true in debug mode and false in release mode.

Recap

Without Equatable

class Person {
  const Person(this.name);

  final String name;

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

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

With Equatable

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name);

  final String name;

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

EquatableMixin

Sometimes it isn't possible to extend Equatable because your class already has a superclass. In this case, you can still get the benefits of Equatable by using the EquatableMixin.

Usage

Let's say we want to make an EquatableDateTime class, we can use EquatableMixin like so:

class EquatableDateTime extends DateTime with EquatableMixin {
  EquatableDateTime(
    int year, [
    int month = 1,
    int day = 1,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int millisecond = 0,
    int microsecond = 0,
  ]) : super(year, month, day, hour, minute, second, millisecond, microsecond);

  @override
  List<Object> get props {
    return [year, month, day, hour, minute, second, millisecond, microsecond];
  }
}

Now if we want to create a subclass of EquatableDateTime, we can just override props.

class EquatableDateTimeSubclass extends EquatableDateTime {
  final int century;

  EquatableDateTimeSubclass(
    this.century,
    int year,[
    int month = 1,
    int day = 1,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int millisecond = 0,
    int microsecond = 0,
  ]) : super(year, month, day, hour, minute, second, millisecond, microsecond);

  @override
  List<Object> get props => super.props..addAll([century]);
}

Performance

You might be wondering what the performance impact will be if you use Equatable.

Results (average over 10 runs)

Equality Comparison A == A

Class Runtime (ΞΌs)
Manual 0.193
Empty Equatable 0.191
Hydrated Equatable 0.190

Instantiation A()

Class Runtime (ΞΌs)
Manual 0.165
Empty Equatable 0.181
Hydrated Equatable 0.182

*Performance Tests run using: Dart VM version: 2.4.0

Maintainers

More Repositories

1

bloc

A predictable state management library that helps implement the BLoC design pattern
Dart
11,735
star
2

mason

Tools which allow developers to create and consume reusable templates called bricks.
Dart
967
star
3

mocktail

A mock library for Dart inspired by mockito
Dart
616
star
4

cubit

Cubit is a lightweight state management solution. It is a subset of the bloc package that does not rely on events and instead uses methods to emit new states.
Dart
583
star
5

flow_builder

Flutter Flows made easy! A Flutter package which simplifies navigation flows with a flexible, declarative API.
Dart
393
star
6

fresh

πŸ‹ A token refresh library for Dart.
Dart
271
star
7

hydrated_bloc

An extension to the bloc state management library which automatically persists and restores bloc states.
Dart
189
star
8

bloc.js

A predictable state management library that helps implement the BLoC design pattern in JavaScript
TypeScript
182
star
9

fluttersaurus

A Flutter Thesaurus made for Byteconf Flutter 2020
Dart
148
star
10

web_socket_client

A simple WebSocket client for Dart which includes automatic reconnection logic.
Dart
79
star
11

sealed_flutter_bloc

flutter_bloc state management extension that integrates sealed_unions.
Dart
70
star
12

flutter_hub

one-stop-shop to discover flutter projects, developers, and news
Dart
66
star
13

flutter_text_view_plugin

A simple flutter plugin that demonstrates how to use PlatformViews with Android to embed native views within the flutter widget tree.
Dart
43
star
14

felangel

31
star
15

bloc_library_basics_and_beyond

Bloc Library: Basics and Beyond - Flutter Europe Talk 2020
Dart
30
star
16

flutter_services_binding

A subset of WidgetsFlutterBinding specifically for initializing the ServicesBinding.
C++
27
star
17

inherited_stream

An inherited widget for Streams, which updates its dependencies when the stream emits data.
C++
22
star
18

cubit_and_beyond

Cubit and Beyond - Talk given at Flutter Vikings 2020
Dart
21
star
19

flutter_flows

A sample Flutter project which demonstrates how to use flow_builder. Part of talk given at Flutter Hub 2021
Dart
20
star
20

sealed_flutter_bloc_samples

Dart
20
star
21

rainbow_container

🌈 A magical container which changes colors whenever its build method is called.
C++
20
star
22

stream_listener

Stream Helpers for Flutter & Dart
Dart
20
star
23

meet_mason

Meet Mason: Introduction to Templating and Code Generation presented at Flutter Vikings 2022
18
star
24

cool_counter

A cool counter application which showcases HydratedCubit and ReplayCubit -- Flutter Warsaw 2020
Dart
16
star
25

broadcast_bloc

An extension to the bloc state management library which adds support for broadcasting state changes to stream channels.
Dart
16
star
26

hydrated_weather

An example of how to use hydrated_bloc to cache application state in a Weather App
Dart
15
star
27

struct

Experimental support for data classes in Dart using macros.
Dart
14
star
28

homebrew-mason

The official mason tap for homebrew
Ruby
13
star
29

codemagic_bloc_unit_tests

Bloc Unit Tests + Codemagic YAML
Dart
13
star
30

Flutter-Localization

Dart
9
star
31

simple_weather

A simple weather app built with flutter and the metaweather api.
Dart
7
star
32

bloc_todos

Dart
6
star
33

Flutter-Nested-TabBar

Dart
5
star
34

nested

A Flutter Widget which helps nest multiple widgets without needing to manually nest them.
Dart
4
star
35

flutter_platform_view_error

Ruby
3
star
36

flutter_skt_map_bug

Java
2
star
37

FelixAngelov

2
star
38

flutter_flavors_bug

C++
1
star
39

dartdevc_exception_bug

Dart
1
star
40

e2e_zones

C++
1
star
41

monorepo-ci-test

Dart
1
star
42

locale-manager

Locale-Manager is designed to be the simplest way possible to localize content while keeping an organized, scalable project structure.
JavaScript
1
star