Giter Club home page Giter Club logo

equatable's Introduction

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];
}

Equatable also supports nullable props:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  const Person(this.name, [this.age]);

  final String name;
  final int? age;

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

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

equatable's People

Contributors

726d avatar abhishek01039 avatar amir-p avatar azack avatar devmil avatar dustin-graham avatar fabryx92 avatar felangel avatar gorniv avatar jeroen-meijer avatar kumabotz avatar machinescream avatar mhadaily avatar micimize avatar pegasisforever avatar petjahn avatar phamnhuvu-dev avatar rrousselgit avatar slimyjimmy avatar tenhobi avatar yafkari avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

equatable's Issues

Feature Request!!! Add Support for object diffs

Is your feature request related to a problem? Please describe.
I'm always frustrated when debugging and I think an object should be equal to another and it's not.

Describe the solution you'd like
I'd like to say

if(obj1 != obj2){
  Map diff = obj2.difference(object1); // was going to go with operator -, 
             //but that can be overrriden and cause problems.
}



**Describe alternatives you've considered**
Writing one per class. But that's tedious.

Should we always call super([...])?

I just saw the code sample provided at README file:

import 'package:equatable/equatable.dart';

class Person extends Equatable {
  final String name;

  Person(this.name) : super([name]);
}

I want to know if we should always call the super function providing all my properties or just extends with Equatable and we are ready to go?!

Documentation refers to an old version

The readme file uses version 0.1.0 which is quite old and stumped me as it did not include the mixins. Update the docs to refer to the latest version.

Build runner for equatable

This is an ease-of-use idea. I've been using the json_serializable pub package with the build_runner running, which automagically generates my from/to json methods. While I was doing this I thought, hey, wouldn't it be sweet if equatable did this too? This way I wouldn't forget to add fields to the props array.

The solution for this feature I'm envisioning: Before each class that you want to be equatable, you'd add an @equatable annotation. Then, with the build_runner running, the equatable fields are generated on the fly and store in another file my_model.g.dart (or something). (I believe this is possible using extension methods, but not 100% sure). Possible solution:

my_model.dart

@equtable
class MyModel {
  final String value;
}

my_model.g.dart (auto-generated)

extension on MyModel {
  bool operator ==(MyModel a, MyModel b) {
    return a.value == b.value; // and hash code stuff too
  }
}

Is this a sort of feature that would be feasible?

P.S Equatable is in my top 5 favorite packages, really awesome 🙌 🙌 🙌

Equatable doesn't work with List prop

`class EquatableStrings extends Equatable{
final List strings;
const EquatableStrings({this.strings});

@OverRide
List get props => [strings];
}

EquatableStrings(['a']) == EquatableStrings(['b']) //true
['a'] == ['b'] // false, obviously`

I expect the first comparison to be false, as the prop inside it was different. Is there something that I'm missing?
I'm working with flutter_bloc, which I have a state that contains a list item. When I try to modify that list and yield new state, it does not update, as the two states are equal

Why does Equatable requieres class properties to be final?

i was running the next test

import 'package:test/test.dart';
import 'package:equatable/equatable.dart';

void main() {
  group('Equatable', () {
    test('Equality among lists', () {
      ListItems lista1 = ListItems(
        items: [
          Item(
            value: 5
          ),
          Item(
            value: 6
          ),
          Item(
            value: 7
          ),
        ]
      );

      ListItems lista2 = ListItems(
        items: [
          Item(
            value: 5
          ),
          Item(
            value: 6
          ),
          Item(
            value: 8
          ),
        ]
      );

      expect(lista1 == lista2, false);
    });

    test('Equality among lists with mixin', () {
      ListItemsMixin lista1 = ListItemsMixin(
        items: [
          ItemMixin(
            value: 5
          ),
          ItemMixin(
            value: 6
          ),
          ItemMixin(
            value: 7
          ),
        ]
      );

      ListItemsMixin lista2 = ListItemsMixin(
        items: [
          ItemMixin(
            value: 5
          ),
          ItemMixin(
            value: 6
          ),
          ItemMixin(
            value: 8
          ),
        ]
      );

      expect(lista1 == lista2, false);
    });
  });
}

class Item extends Equatable {
  final int value;

  Item({
    this.value
  });

  Item copyWith({
    int value,
  }) {
    return Item(
      value: value ?? this.value
    );
  }

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

class ListItems extends Equatable {
  final List<Item> items;

  ListItems({
    this.items
  });

  ListItems copywith({
    List<Item> items
  }) {
    return ListItems(
      items: items ?? this.items,
    );
  } 

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


class ItemMixin with EquatableMixin {
  int value;

  ItemMixin({
    this.value
  });

  ItemMixin copyWith({
    int value,
  }) {
    return ItemMixin(
      value: value ?? this.value
    );
  }

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

class ListItemsMixin with EquatableMixin {
  final List<ItemMixin> items;

  ListItemsMixin({
    this.items
  });

  ListItemsMixin copywith({
    List<Item> items
  }) {
    return ListItemsMixin(
      items: items ?? this.items,
    );
  } 

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

When i set Item class properties to non-final:

class Item extends Equatable {
 int value;

  Item({
    this.value
  });

  Item copyWith({
    int value,
  }) {
    return Item(
      value: value ?? this.value
    );
  }

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

i get the next hint

This class (or a class which this class inherits from) is marked as '@immutable', but one or more of its instance fields are not final:

but in the end, whit or without final, the tests are always successful.

  • Is there any caveat in setting the properties to non-final (extending equatable)?
  • Using the equatable mixin it looks like there is not need to set class properties to final. does this impact any kind of process in the package?

Btw, i love your package ❤️

Introduce special notation for null props

Is your feature request related to a problem? Please describe.
It will be great to have the possibility to permanently or temporarily disable Equatable functionality and compare objects only by references.

Example from bloc library area. In some conditions, we want that new bloc state update the current bloc state of some bloc even if they hold the same data.

Describe the solution you'd like
I propose to introduce special notation for the null value returned by the props method. So if props returned null then objects compared only by references.

Currently, to implement it only one line must be moved (and some tests updated):

index a5e0d52..641588f 100644
--- a/lib/src/equatable_utils.dart
+++ b/lib/src/equatable_utils.dart
@@ -8,8 +8,8 @@ const DeepCollectionEquality _equality = DeepCollectionEquality();

 /// Determines whether [list1] and [list2] are equal.
 bool equals(List list1, List list2) {
-  if (identical(list1, list2)) return true;
   if (list1 == null || list2 == null) return false;
+  if (identical(list1, list2)) return true;
   final length = list1.length;
   if (length != list2.length) return false;

Describe alternatives you've considered
For sure, this functionality could be achieved by override == and hashCode methods in a particular class or some base class. But it all adds redundant code.

Additional context
For now, null and [] values returned by the props method lead to one result - different objects of the same class are equal. With my proposal, they will have a different meaning, which will give additional flexibility.

Provider stronger hash codes

I was looking for ways to ease operator == and hashCode in Dart and found equatable.

It seems a reasonable approach overall.

But, looking at the implementation, it looks like it uses XOR to combine hashes. This would mean, for example, that

class Point {
  final int x = 3;
  final int y = 4;
}

and

class Point {
  final int x = 4;
  final int y = 3;
}

will hash to the same value. Did I miss something?

I think the correct approach would be to use something like

https://en.wikipedia.org/wiki/Jenkins_hash_function

--which is what package:quiver uses

https://github.com/google/quiver-dart/blob/master/lib/src/core/hash.dart

Good news is it should be possible to add this within the current implementation with no changes to how it's used :)

Thanks!

Add EquatableMixin

The current most major issue with this package is that you have to extend Equatable, so Equatable objects cannot have superclasses. This issue can be easily solved with the use of Dart mixins, at the cost of a *slightly* more verbose API. (I'd submit this as a PR but don't have time to write tests).

equatable_mixin.dart:

mixin EquatableMixin on Object {
  List get _props;

  List props(List newProps) => (super is EquatableMixin ? (super as EquatableMixin)._props : [])..addAll(newProps);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is EquatableMixin && runtimeType == other.runtimeType && _equals(_props, other._props);

  @override
  int get hashCode => runtimeType.hashCode ^ _propsHashCode;

  int get _propsHashCode {
    int hashCode = 0;

    _props.forEach((prop) {
      hashCode = hashCode ^ prop.hashCode;
    });

    return hashCode;
  }

  static bool _equals(list1, list2) {
    if (identical(list1, list2)) return true;

    if (list1 == null || list2 == null) return false;

    int length = list1.length;

    if (length != list2.length) return false;

    for (int i = 0; i < length; i++) {
      if (list1[i] is Iterable) {
        if (!_equals(list1[i], list2[i])) return false;
      } else {
        if (list1[i] != list2[i]) return false;
      }
    }

    return true;
  }
}

Usage example:

class Animal with EquatableMixin {

  final int age;
  final bool isAlive;

  Animal({this.age, this.isAlive});

  @override
  List get _props => props([age, isAlive]);
}

class Person extends Animal with EquatableMixin {
  final String name;
  final int netWorth;

  Person({this.name, this.netWorth, int age, bool isAlive}) : super(age: age, isAlive: isAlive);

  List get _props => props([name, netWorth]);
}

The one downside of this approach is that classes can no longer be const. However, for many use cases that is an acceptable tradeoff.

Remove @immutable

Please remove @immutable It has nothing to do with equatable/ comparing values.

False positive equality with Lists

Describe the bug
I have 2 states that are showing up equal when lists are involved.

@immutable
class TrackerState extends Equatable {
  final WordIndex currentMatch;
  final List<String> resultsHistory;
  final bool isAvailable;
  final bool isListening;
  final List<String> source;

  @override
  String toString() {
    return 'TrackerState{currentIndex: $currentMatch, '
        'resultsHistory: $resultsHistory, isAvailable: $isAvailable, '
        'isListening: $isListening}';
  }

  TrackerState({
    @required this.currentMatch,
    @required this.resultsHistory,
    @required this.isAvailable,
    @required this.isListening,
    @required this.source,
  });

  TrackerState copyWith({
    WordIndex currentMatch,
    List<String> resultsHistory,
    bool isAvailable,
    bool isListening,
    List<String> source,
  }) {
    return TrackerState(
      currentMatch: currentMatch ?? this.currentMatch,
      resultsHistory: resultsHistory ?? this.resultsHistory,
      isAvailable: isAvailable ?? this.isAvailable,
      isListening: isListening ?? this.isListening,
      source: source ?? this.source,
    );
  }

  @override
  List<Object> get props =>
      [currentMatch, resultsHistory, isAvailable, isListening];
}

Screen Shot 2019-11-18 at 2 27 18 PM

Screen Shot 2019-11-18 at 2 27 40 PM

Screen Shot 2019-11-18 at 2 27 54 PM

Expected behavior
Classes with Lists of different lengths or contents should be considered equal

Screenshots
If applicable, add screenshots to help explain your problem.

Version
Dart VM version: 2.7.0 (Thu Nov 14 02:10:15 2019 +0000) on "macos_x64"

Additional context
Add any other context about the problem here.

work with json not perfect

Describe the bug

code is document

To Reproduce
Steps to reproduce the behavior:

total test code is blow

import 'dart:convert';
import 'package:test/test.dart';

class PersonEntity extends Equatable {
  String dress;
  String name;

  PersonEntity({this.dress, this.name}) : super([name]);

  PersonEntity.fromJson(Map<String, dynamic> json) : super([json]) {
    dress = json['dress'];
    name = json['name'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['dress'] = this.dress;
    data['name'] = this.name;
    return data;
  }
}

void main() {
  test('Equatable for json test', () {
    var map1 = json.decode('''
    {"name":"Kite","dress":"bikini"}
     ''');

    var map2 = json.decode('''
    {"name":"Kite","dress":"t-shirt"}
     ''');

    var _obj1 = PersonEntity.fromJson(map1);
    var _obj2 = PersonEntity.fromJson(map2);

    var _obj3 = PersonEntity(name: 'Kite', dress: 'bikini');
    var _obj4 = PersonEntity(name: 'Kite', dress: 't-shirt');

    expect(_obj1 == _obj2, true); // not passed
    expect(_obj1 == _obj3, true); // not passed
    expect(_obj1 == _obj4, true); // not passed
    expect(_obj2 == _obj3, true); // not passed
    expect(_obj2 == _obj4, true); // not passed
    expect(_obj3 == _obj4, true); // passed
  });
}

Expected behavior

equatable properties should acordding the properties passed by constructor

Screenshots
If applicable, add screenshots to help explain your problem.

Version
latest

Additional context
Add any other context about the problem here.

Something strange with hashcode

code:

void main() {
  test('fits', () {
    final test = Test('');
    final test2 = Test('123');
    print(test.hashCode);
    print(test2.hashCode);
  });
}

class Test extends Equatable {
  final id;
  final items = [];

  Test(this.id);

  @override
  List<Object> get props => [id, items];
}

Expected: different hashcodes
Actual: the same

code:

void main() {
  test('fits', () {
    final test = Test('');
    final test2 = Test('123');
    print(test.hashCode);
    print(test2.hashCode);
  });
}

class Test extends Equatable {
  var id = '';
  final items = [];

  @override
  //order of props changed
  List<Object> get props => [items, id];
}

Expected: different hashcodes
Actual: different hashcodes

Different hashCodes produced for classes with List properties

Describe the bug
When a class extends Equatable and that class has a list property, two different instances, with the exact same properties, produce different hashCode values.

To Reproduce

class Foo extends Equatable {
  String foo;
  String bar;
  List<String> list;

  Foo(this.foo, this.bar, this.list) : super([foo, bar, list]);
}

void main() {
  group("hashCode test", () {
    test("foo", () {
      expect(Foo('aaa', 'bbb', []).hashCode, equals(Foo('aaa', 'bbb', []).hashCode));
    });
  });
}

Expected behavior
expected the same hashCode to be produced

Version
0.2.1

About const constructor changed

Hi, I'm a fan of Bloc. and found out that a lot of changed from old version. equatable is the most confusion part for me. According to example, there is a fairly of use case on const Constructor, and then I digged in some post about It. Here is the post I read from stackoverflow, and official documentation.

It's really mess about reading those with the example of Authentication Events.

import 'package:equatable/equatable.dart';

abstract class AuthenticationEvent extends Equatable {
  const AuthenticationEvent();

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

class AppStarted extends AuthenticationEvent {}

class LoggedIn extends AuthenticationEvent {}

class LoggedOut extends AuthenticationEvent {}

Did we really need const? I guessed If we don't use bloc.add(const AppStarted()), then mean using the default constructor, and not benefit with the const constructor, Am I right? Sorry, This might be a newbie dart question, but I cannot find any discussion on this 😢

EquatableMixin does not use EquatableConfig.stringify by default

Describe the bug
When extending Equatable, toString will by default look at EquatableConfig.stringify to determine what to do, but when using EquatableMixin by default toString just prints class name

To Reproduce

Here is a unit test demonstrating the issue:

import 'package:equatable/equatable.dart';
import 'package:flutter_test/flutter_test.dart';

class FooExtends extends Equatable {
  final int i;

  FooExtends(this.i);

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

class FooMixin with EquatableMixin {
  final int i;

  FooMixin(this.i);

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

void main() {
  group("Equtable toString default behavior", () {
    test("Equatable base class uses EquatableConfig.stringify by default", () {
      EquatableConfig.stringify = true;

      expect(FooExtends(1).toString(), "FooExtends(1)");
    });
    test("Equatable mixin class uses EquatableConfig.stringify by default", () {
      EquatableConfig.stringify = true;

      expect(FooMixin(1).toString(), "FooMixin(1)");
    });
  });
}

Expected behavior
Both unit tests should pass

Actual Behavior
The mixin case fails:

Expected: 'FooMixin(1)'
  Actual: 'FooMixin'
   Which: is different. Both strings start the same, but the actual value is missing the following trailing characters: (1)

Version
Dart SDK version: 2.10.0-4.0.dev.flutter-9d279d41e3 (be) (Mon Aug 10 08:03:29 2020 +0000) on "macos_x64"

Additional context
The issue is that this line should set stringify to null just like the Equatable base class does

It's a confusion about List comparison

HI! Thanks for the great package with equatable! It's very helpful for object comparision, but I got a confusion on the following code:

void testListCompare() {
  List<String> logs = new List<String>();
  List quickCheck = logs..add("123...")..toList();
  print("quickCheck == logs: ${quickCheck == logs}");
  print("quickCheck == new logs: ${quickCheck == (logs..add('new data...')..toList())}");
  print("quickCheck: $quickCheck");
  print("logs:$logs");
}

result:

quickCheck == logs: true
quickCheck == new logs: true
quickCheck: [123..., new data...]
logs:[123..., new data...]

Currently, I met the problem above with equatable, but for deep testing that's the default behavior with dart's List, I expect that after toList, the quickCheck list is not affected by logs list, but the result is kinda supprising for me that It's affected! How did you slove the list issue about this problem in equatable? or dart... 😞

always true for fromJson

i use json_to_dart for work with json.

class Credentials extends Equatable {
  String username;
  String password;

  Credentials({this.username, this.password}) : super([username, password]);

  Credentials.fromJson(Map<String, dynamic> json) {
    username = json['username'];
    password = json['password'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['username'] = this.username;
    data['password'] = this.password;
    return data;
  }
}

class TestEquatable {

  TestEquatable();

  void AssertJson() {
    final credentialsA = Credentials.fromJson(json.decode("""
    {
  "username":"Joe",
  "password":"password123"
}
    """));
    final credentialsB = Credentials.fromJson(json.decode("""
    {
  "username":"Joe1",
  "password":"password1231"
}
    """));
    print(credentialsA == credentialsA); // true
    print(credentialsB == credentialsB); // true
    print(credentialsA == credentialsB); // true, but need false!!!
  }
}

How to fix the comparison when working with json?

Proposal for introducing breaking changes in the future

With the release of 0.6.0 and the introduction of props getter, we had to scramble to update the codebases we are working on across multiple projects.

These projects are in flux, changing rapidly, following 3rd party changes quick, therefore we usually don't version lock our dependencies, only around the first release. Equatable is used extensively in all of the projects. The circumstances were unfortunate, 3rd party lib upgraded to 0.6 with new features we needed. We kind of had to follow that.

With this background info, I'd like to propose, that some time before such a breaking change (even if it's just a week), release a PATCH version that could look like so:

class Equatable {
  @Deprecated('Will be removed in 0.6.0, see veryshort.url')
  Equatable();
}

This would generate a warning in linters, allowing allocation of time to fix it and version lock the dependency until the we can follow the changes.

Some thoughts on EquatableConfig

Is your feature request related to a problem? Please describe.
I really like being able to configure my Equatable classes globally using EquatableConfig (#68), since it saves me a lot of stringify overrides in the various classes. However, I'm worried about the global, shared state of this class.

First of all, there's no logical place for me to put the statement EquatableConfig.stringify = true; – every single test has its own main class, and in addition my project has various main classes for different environments, which leads to a lot of code duplication and makes it very easy to forget this call somewhere. I can also easily mess up by adding it in the unit tests but then forgetting it in the application, which basically invalidates all the toString calls in my unit tests.

Secondly, and more worryingly, any part of my code can now change this static value, which suddenly causes a behaviour change in all of my Equatable classes. This is dangerous from reproducibility perspective (for which we can't even test, because the value might be different in the unit tests), as well as from a security perspective: any part of my code can now make change other classes to have sensitive data show up in my logs, or hide the data I want from my logs, by simply changing this one variable. Currently, the only safeguard against this behaviour is overriding stringify in every Equatable class, which defeats the whole purpose of having this configurable field in the first place.

Describe the solution you'd like
The first part of this problem can be solved in a non-breaking way, by defaulting EquatableConfig.stringify to a compile-time constant, eg. EquatableConfig.stringify = bool.fromEnvironment('equatable.defaultStringify', false);. This would already save a lot of trouble in getting the change consistent across my various main classes without having to specify them. However, this doesn't solve the reproducibility/security issue, unless the value were to be made const – but that'd be a breaking change and possibly make a lot of people very sad. It also doesn't guarantee that the production behaviour is identical to what was run in the unit tests, but at least it makes it a lot harder to randomly mess it up somewhere in the code.

I'm not entirely sure if I'm making a valid point or if I'm just spewing nonsense here, so I'm very curious to hear the developers' opinions about this! And as a final remark, I love the project and use it extensively, so thanks for all the work that went into it :) really appreciate it!

Full Output toString

Is your feature request related to a problem? Please describe.
I was recently comparing two classes, the objects were different, but I couldn't see which because the current toString displays only the first and last set of props. I have had to manually print out all props when I faced this.

Describe the solution you'd like
Would be nice to have a toString which prints out props in a propName: prop\n format for visual comparisons.

Describe alternatives you've considered
I have used the following function whenever I had said issue.

for (var i = 0; i < staffEntity.props.length; i++) {
        print('${staffEntity.props[i].toString()}\n');
      }

Equality for objects with a list parameter

I'm playing around with the new library bloc_test for flutter and I implemented the following test

blocTest('should return ReservationsLoadSucess when the use case returns a list of reservationsList',

    build: () {
      when(mockGetReservations(any)).thenAnswer((_) async => Right(reservationsList));
      return ReservationBloc(getReservations: mockGetReservations);
    },
    act: (bloc) async {
      bloc.add(ReservationsRequested(user));
    },
    expect: [
      ReservationsInitial(),
      ReservationsLoadInProgress(),
      ReservationsLoadSuccess(reservationsList),
    ],
  );

This is the implementation of ReservationsLoadSuccess

class ReservationsLoadSuccess extends ReservationState {
  final List<Reservation> list;

  ReservationsLoadSuccess(this.list);

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

Where ReservationState extends Equatable Now, when running the test, you get the following error

should return ReservationsLoadSucess when the use case returns a list of reservationsList:

ERROR: Expected: [
            ReservationsInitial:ReservationsInitial,
            ReservationsLoadInProgress:ReservationsLoadInProgress,
            ReservationsLoadSuccess:ReservationsLoadSuccess
          ]
  Actual: [
            ReservationsInitial:ReservationsInitial,
            ReservationsLoadInProgress:ReservationsLoadInProgress,
            ReservationsLoadSuccess:ReservationsLoadSuccess
          ]
   Which: was ReservationsLoadSuccess:<ReservationsLoadSuccess> instead of ReservationsLoadSuccess:<ReservationsLoadSuccess> at location [2]

Basically saying that the state ReservationsLoadSuccess at position 2 in the actual list is not equal to its peer in the expected list.

I tried overriding the == operator in the ReservationsLoadSuccess class as follows

class ReservationsLoadSuccess extends ReservationState {
  final List<Reservation> list;

  ReservationsLoadSuccess(this.list);

  final Function eq = const ListEquality().equals;
  @override
  List<Object> get props => [];

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ReservationsLoadSuccess &&
          runtimeType == other.runtimeType &&
          eq(list, other.list);
}

But that didn't seem to work and running the test still outputted the same error. The only way I got it to work is to leave the props method returning an empty list or adding any other dummy variable and pass it to the props list.

Is there any way I can make the class equatable in regards to the list parameter?

EquatableMixin fails on equality, but extending Equatable works

Describe the bug
Extending Equatable works, but using EquatableMixin does not result in equality.
I tried debugging it, and it does not even go into EquatableMixin ... this may be a Dart issue?

It doesn't even work when I remove all of the fields. It's as if, EquatableMixin is being ignored by Dart and simply doing object equality.

I am using Dart 2.7 and running the tests via flutter test (Flutter (Channel stable, v1.12.13+hotfix.5, on Linux, locale en_US.UTF-8));

To Reproduce

import 'package:equatable/equatable.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:to_string/to_string.dart';

part 'quick_test.g.dart';

void main() {

  test('Equatable', () {
    // arrange
    final bar = Bar('1', 2, 3, 4);
    final bar2 = Bar(bar.id, bar.createdOnMillisecondsSinceEpoch, bar.version,
        bar.updatedOnMillisecondsSinceEpoch);

    // assert
    expect(bar, bar2);
  });

  test('EquatableMixin', () {
    // arrange
    final bar = Foo('1', 2, 3, 4);
    final bar2 = Foo(bar.id, bar.createdOnMillisecondsSinceEpoch, bar.version,
        bar.updatedOnMillisecondsSinceEpoch);

    // assert
    expect(bar, bar2);
  });
}

@ToString()
class Bar extends Equatable {
  final String id;
  final int createdOnMillisecondsSinceEpoch;
  final int version;
  final int updatedOnMillisecondsSinceEpoch;

  Bar(this.id, this.createdOnMillisecondsSinceEpoch, this.version,
      this.updatedOnMillisecondsSinceEpoch);

  @override
  List<Object> get props => [
        id,
        createdOnMillisecondsSinceEpoch,
        version,
        updatedOnMillisecondsSinceEpoch,
      ];

  @override
  String toString() {
    return _$BarToString(this);
  }
}

@ToString()
class Foo implements EquatableMixin {
  final String id;
  final int createdOnMillisecondsSinceEpoch;
  final int version;
  final int updatedOnMillisecondsSinceEpoch;

  Foo(this.id, this.createdOnMillisecondsSinceEpoch, this.version,
      this.updatedOnMillisecondsSinceEpoch);

  @override
  List<Object> get props => [
    id,
    createdOnMillisecondsSinceEpoch,
    version,
    updatedOnMillisecondsSinceEpoch,
  ];

  @override
  String toString() {
    return _$FooToString(this);
  }
}

Test Results

package:test_api                                   expect
package:flutter_test/src/widget_tester.dart 234:3  expect
test/quick_test.dart 26:5                          main.<fn>

Expected: Foo:<Foo{id: 1, createdOnMillisecondsSinceEpoch: 2, version: 3, updatedOnMillisecondsSinceEpoch: 4}>
  Actual: Foo:<Foo{id: 1, createdOnMillisecondsSinceEpoch: 2, version: 3, updatedOnMillisecondsSinceEpoch: 4}>

updated List equality produces false negative

Describe the bug
The new list equality check introduces false negatives in my tests when comparing identical lists with different runtime types. In consequence of other business logic in my code, I end up with a GrowableList in one side of the equality and a CopyOnWriteList on the other. Contents are identical but the runtime check fails.

To Reproduce
In my test I generate dummy data with List.generate(). This produces a GrowableList. However in my code I am using BuildList which has a toList() method that returns a CopyOnWriteList. The equality check worked as expected with older code.

Expected behavior
Deep equals check should find that the list contents are identical

Screenshots
Screen Shot 2019-05-27 at 7 46 53 AM

Version
Dart VM version: 2.3.0 (Fri May 3 10:32:31 2019 +0200) on "macos_x64"

Additional context
I'm able to work around this in my tests by wrapping my original dummy data in a BuiltList and using the toList() to compare. But this is a bit more work than I would expect and might be confusing to future maintainers. Thanks!

dartdoc shows warnings when defining the props getter in a EquatableMixin

Describe the bug
Using dartdoc to generate docs for a class implemented with EquatableMixin shows a warning about [Equatable] not being defined in the inherited documentation for props.

To Reproduce
Steps to reproduce the behavior:

  1. flutter create eq (probably not necessary to reproduce but it was a quick way to get a new project to use equatable).
  2. cd eq
  3. Edit pubspec.yaml and add equatable: to the dependencies: section.
  4. Run flutter pub get
  5. Edit lib/main.dart and replace the content with:
    import 'package:equatable/equatable.dart';
    
    /// Test class.
    class E with EquatableMixin {
      /// Test bool.
      bool b;
      @override
      List<Object> get props => [b];
    }
    
    void main() {
      final e = E();
      e.b = true;
    }
  6. Run dartdoc

Expected behavior
The documentation should be generated without any warnings.

Screenshots
Not exactly a screenshot, but the dartdoc output:

$ dartdoc 
Documenting eq...
Initialized dartdoc with 56 libraries in 19.4 seconds
Generating docs for library main from package:eq/main.dart...
  warning: unresolved doc reference [Equatable]
    from main.E.props: (file:///home/luca/devel/flutter/eq/lib/main.dart:6:20)
    in documentation inherited from equatable_mixin.EquatableMixin.props: (file:///home/luca/.pub-cache/hosted/pub.dartlang.org/equatable-1.2.3/lib/src/equatable_mixin.dart:12:20)
Validating docs...
found 1 warning and 0 errors
Documented 1 public library in 4.0 seconds
Success! Docs generated into /home/luca/devel/flutter/eq/doc/api

Version
Dart SDK version: 2.9.1 (stable) (Wed Aug 12 12:37:19 2020 +0200) on "linux_x64"
equatable 1.2.3
dartdoc version: 0.32.1

Additional context
Not really.

How to use with abstract class with fields?

Hello
If i have this, how to use Equatable, i don't know :(
Thanks.

abstract class SomeClass {
  final String someField;
  SomeClass(this.someField);
}

class Inherited1 extends SomeClass{
final String otherFied;
Inherited1(this.otherField, String someField) : super(someField);
} 

The class has the collection variable is not the "List" object.

Describe the bug
If the class has the collection variable is not the "List" object. Comparing is always return true.

To Reproduce
Steps to reproduce the behavior:

class E extends Equatable {
  final Map<String, int> map;

  E(this.map);
}

E e1 = E({"A": 1});
E e2 = E({"B": 2});
assert(e1 == e2); // return true

Expected behavior
assert(e1 != e2) //return true

Problem
https://github.com/felangel/equatable/blob/master/lib/src/equatable_utils.dart#L18-L24

Solution
Compare between 2 collections(List, Map, Set, v.v…) have to use Equality object(ListEquality, MapEquality, SetEquality, v.v…) in package:collection/collection.dart.
Don’t use “==” operator.

MapEquality mapEquality = MapEquality();
Map map1 = {"A": 1};
Map map2 = {"A": 1};
Map map3 = {"B": 2};
assert(mapEquality.equals(map1, map2)); //map1 == map2
assert(!mapEquality.equals(map2, map3)); //map2 != map3

Lists even if elements are Equatable don't cause changes

Describe the bug
If a list is one of the properties on an object, then add/remove and per item comparisons fail which causes things like FlutterBloc etc. that rely on equitable to determine if something has changed, to also fail.

To Reproduce

  1. Create an object, add a List property and add it to props
  2. Add an item to the list, note that nothing changes and equatable still doesn't equal and as a result FlutterBloc etc. won't update.
  3. Remove an item from the list. Note that nothing changes in the status of equatable.

Expected behavior
Equatable should by default inspect all elements for equatableness of a list and flag the property dirty based on an mismatch in size or specific elements changing. Alternatively this should be a setting that you can configure, or there should be a specific type of list (EquatableList<>) that will explicitly cause the same.

Version
Dart SDK version: 2.10.0-7.3.beta (beta) (Wed Aug 26 09:46:50 2020 +0200) on "windows_x64"

Issue with False Equatability

I am struggling with a unit test that I expect to return true but doesn't seem to be registering equatability. I hope this is the correct way/place to ask for help. I've tried to research what I can but most guides seem aimed at older versions of Equatable.

The unit test message shows the contents are the same but Equatable doesn't seem to be taking effect? I'm sure it's user error but I just don't know what, any guidance would be appreciated.


Activity entity which extends Equatable

class Activity extends Equatable {
  final String result;
  final String message;
  final Map data;

  Activity({
    this.data,
    this.message,
    this.result
  });

  @override
  List<Object> get props => [data, message, result];

  @override
  bool get stringify => true;
}

ActivityModel model that extends the Activity entity

class ActivityModel extends Activity {
  ActivityModel({
    @required Map data,
    @required String message,
    @required String result,
  }) : super(data: data, message: message, result: result);


  factory ActivityModel.fromJson(Map<String, dynamic> json) {
    return ActivityModel(
      data: json['response']['data'],
      message: json['response']['message'],
      result: json['response']['result'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'result': result,
      'message': message,
      'data': data,
    };
  }
}

Unit test

void main() {
  final tActivityModel = ActivityModel(
    result: 'success',
    message: null,
    data: {
      'stream_count': 1,
      'sessions': [],
    },
  );

  group('fromJson', () {
    test(
      'should return a valid model',
      () async {
        // arrange
        final Map<String, dynamic> jsonMap = {
          "response": {
            "result": "success",
            "message": null,
            "data": {
              "stream_count": "1",
              "sessions": [],
            }
          }
        };
        //act
        final result = ActivityModel.fromJson(jsonMap);
        //assert
        expect(result, tActivityModel);
      },
    );
  });
}

Unit test failure message

Expected: ActivityModel:<ActivityModel({stream_count: 1, sessions: []}, , success)>
  Actual: ActivityModel:<ActivityModel({stream_count: 1, sessions: []}, , success)>

package:test_api                                                  expect
package:flutter_test/src/widget_tester.dart 348:3                 expect
test\features\activity\data\models\activity_model_test.dart 44:9  main.<fn>.<fn>

✖ fromJson should return a valid model

Version 6 has a bug with passing to super.

Describe the bug
I have a project using your blocs and I was using equatable 6.1 on events and states. I had an event that would fire twice and then nada. I downgrade to 5.1 and it worked perfectly.

I have a github of the project if you’d like to see the updates. It is tiny project w/ only one bloc.

By the way I love your plugins— you did an excellent job.

Here is the project:

https://github.com/jrmarkham/7dieroller

Here are the updates so you don't have to dig:
`
========== problems w/ 6.1 =========================
import 'package:equatable/equatable.dart';

abstract class FirebaseState extends Equatable {
const FirebaseState([List props = const []]);
@OverRide
List get props => [props];
}

class FirebaseStateInit extends FirebaseState {}
class FirebaseStateLoading extends FirebaseState {}
class FirebaseStateInitResponse extends FirebaseState {
final Stream stream;
FirebaseStateInitResponse(this.stream);

@OverRide
List get props => [stream];
}
class FirebaseStateLoaded extends FirebaseState {

}

===== works w/ 5.1 ============
import 'package:equatable/equatable.dart';

abstract class FirebaseEvent extends Equatable {
FirebaseEvent([List props = const []]):super(props);
}

class FirebaseEventRollDie extends FirebaseEvent {}

========================

import 'package:equatable/equatable.dart';

abstract class FirebaseState extends Equatable {
FirebaseState([List props = const []]):super(props);
}

class FirebaseStateInit extends FirebaseState {}
class FirebaseStateLoading extends FirebaseState {}
class FirebaseStateInitResponse extends FirebaseState {
final Stream stream;
FirebaseStateInitResponse(this.stream):super([stream]);
}
class FirebaseStateLoaded extends FirebaseState {

}`

Thanks— John

Equatable doesn't support const classes

Is your feature request related to a problem? Please describe.
I loved the idea of not having to keep track of overriding ==operator and hashCode just for equality checks. Thanks for the library, but i noticed i can't use the current implementation with const classes which isn't proper for performance reasons.

Describe the solution you'd like
Making Equatable a const class itself so that other const classes wouldn't have issues extending from it. And maybe adding more type-safety to the props argument being passed into it.

Describe alternatives you've considered
A Pull request would be made available.

Additional context
None needed.

Allow const constructors

I would like to use Equatable for my model classes. But if I use it, I can't make them const. Since I use them in my UI widgets, I'll be forced to make them non-const too. Which is something I would like to avoid.

If you can somehow solve this issue, IMHO it would be a useful addition to this wonderful package.

This was already raised in #2 but that issue was closed by its author.

Equality with a modified copy of the same object does not work

I am using this package mainly for flutter bloc.

I have a situation where I need to yield the same state but with a field on the state modified. This was failing to trigger the state transition which I traced it down to how this package determines equality.

I have written a deepCopy method that utilises existing constructors on states to create a new copy of the states. I then modify the fields on the copied object. However, since the props are fixed at the time of object creation, if any field is changed after the creation of the object, the Equatable base class still uses the field values from when the object was created and results in treating both objects as same.

I have temporarily fixed this by changing the props from a constructor hydrated list to a method that can be overridden and that seems to work for me. Happy to submit a PR.

Nested object equality doesn't work

HI @felangel ,

I have used the following class heirarchy to maintain state. And it works fine with bloc, when trying to test the equality in unit test i get the error. Couldn't figure out what is the issue.

abstract class Resource{

}

class Uninitialized extends Resource{
static final Uninitialized _uninitialized = Uninitialized.init();
factory Uninitialized() => _uninitialized;
Uninitialized.init();

}

abstract class AppState implements Equatable {

const AppState();

@OverRide
bool get stringify => true;
}

class LoginState extends AppState {
Resource resource;

LoginState(this.resource);

@OverRide
List get props => [resource];
}

void main(){
var demo1 = LoginState(Uninitialized());
var demo2 = LoginState(Uninitialized());

test('check for equality',(){
// assert(demo1 == demo2);
expect(demo1,equals(demo2));
});

}

This is the error I am getting on testing

package:test_api expect
package:flutter_test/src/widget_tester.dart 348:3 expect
test\test_sample.dart 27:5 main.

Expected: <Instance of 'LoginState'>
Actual: <Instance of 'LoginState'>

Last changes with hash function is not good at all

code:

class ComparableWrapper extends Equatable {
  final Object property;
  ComparableWrapper(this.property);

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

v1.0.0:
code:

    final map = {1: '1', 2: '2'};
    print(ComparableWrapper(map).hashCode);
    map.remove(1);
    print(ComparableWrapper(map).hashCode);

console:
1061099485
1061099485

v0.6.1:
code:

    final map = {1: '1', 2: '2'};
    print(ComparableWrapper(map).hashCode);
    map.remove(1);
    print(ComparableWrapper(map).hashCode);

console:
343974154
350953922

Well, I am understand you want to find a good hash function, but I think last one was better =) Thx

Would you be able to explain this weird equatable behaviour?

Describe the bug
Equatable is returning false when it should return true

To Reproduce
Using equatable 1.0.3
I created the following class

class ProjectTreeType implements Equatable {
  final int projectId;
  final int treeTypeId;

  ProjectTreeType(this.projectId, this.treeTypeId);

  @override
  List<Object> get props => [projectId, treeTypeId];
}

And ran the following check

ProjectTreeType(1, 1) == ProjectTreeType(1, 1)
//return false

I wanted to debug in and see which condition was returning false so i imported the equals function and overrode the operator, I was extremely surprised to see that the equals operator returned true

import 'package:equatable/src/equatable_utils.dart';

class ProjectTreeType implements Equatable {
  final int projectId;
  final int treeTypeId;

  ProjectTreeType(this.projectId, this.treeTypeId);

  @override
  bool operator ==(Object other) =>
      identical(this, other)
      || other is Equatable 
      && runtimeType == other.runtimeType 
      && equals(props, other.props);

  @override
  List<Object> get props => [projectId, treeTypeId];
}

This time it will return true

ProjectTreeType(1, 1) == ProjectTreeType(1, 1)
//return true

Additional context
Am I going mad?

Add ability not to have public props field

Is your feature request related to a problem? Please describe.
Say I want to create an equatable class that is part of a public API. In that case I would not want to have the public field props exposed because that may confuse the user.

Describe the solution you'd like
One solution for immutable classes would be to have an abstract class ImmutableEquatable that has a private _props field and has the list passed in the constructor. For mutable classes this might work by passing a List<Object> Function() to the super constructor.

Describe alternatives you've considered
The approach above does not not work when the class needs to extend another class, since mixins cannot declare constructors. Because of this it might be good to expose the functions in equatable_utils.dart to be able to easily override hashCode and == yourself without having a public props attribute. This would also allow to easily implement an abstract class like ImmutableEquatable yourself.

Make stringify = true by default

I have to always write:

@override
bool get stringify => true;

Mostly all classes which extends Equatable needs to override toString().

Equatable should override toString and return a String with the name of each property and value

Is your feature request related to a problem? Please describe.

If you have a Credential class:

class Credentials extends Equatable {
  final String username;
  final String password;

  Credentials({this.username, this.password}) : super([username, password]);
}

When you create a credential and print:

final credentials = Credentials(username: 'Joe', password: 'password123');
print(credentials)

The output is:

Instance of 'Credentials'

In most cases you want to show the name of class, the property name and the value of each property, so you need to override toString() manually:

class Credentials extends Equatable {
  final String username;
  final String password;

  Credentials({this.username, this.password}) : super([username, password]);

  @override
  String toString() =>
      'Credentials { userName: $username, password: $password }';
}

And now the output is:

Credentials { userName: Joe, password: password123 }

Describe the solution you'd like

Equatable accept a map instead of list.

import 'package:meta/meta.dart';
import './equatable_utils.dart';

@immutable
abstract class Equatable {
  final Map props;

  Equatable([this.props = const {}]);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Equatable &&
          runtimeType == other.runtimeType &&
          equals(props.values.toList(), other.props.values.toList());

  @override
  int get hashCode =>
      runtimeType.hashCode ^ mapPropsToHashCode(props.values.toList());

  @override
  String toString() => '$runtimeType${_propsToString()}';

  String _propsToString() {
    StringBuffer _result = StringBuffer();

    if (props.isNotEmpty) {
      _result.write(' {');
      for (int i = 0; i < props.length; i++) {
        _result
          ..write(' ')
          ..write(props.keys.elementAt(i))
          ..write(': ')
          ..write(props.values.elementAt(i));
        if (i != props.length - 1) _result..write(',');
      }
      _result.write(' }');
    }

    return _result.toString();
  }
}

Now you need to pass a Map to the super constructor, when the key is the String for show in toString() method, and the value, is the value to use to override == and hashcode

class Credentials extends Equatable {
  final String username;
  final String password;

  Credentials({this.username, this.password})
      : super({'userName': username, 'password': password});
}

And now the output is:

Credentials { userName: Joe, password: password123 }

Thanks 😀

The same hashcode of Equatables with different length lists of zeros.

class ComparableWrapper<V> extends Equatable {
  final V _value;

  ComparableWrapper(this._value);

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

void main() async {
  final c1 = ComparableWrapper([0]);
  final c2 = ComparableWrapper([0,0]);
  print(c1.hashCode == c2.hashCode); //true
  print(c1 == c2); // false
}

[Proposal] Global Stringify

Is your feature request related to a problem? Please describe.
It would be nice to have a way to globally configure stringify for all Equatable instances. The proposal includes a breaking change to refactor stringify from a bool to an enum in order to be able to support a "default" (normal) setting in addition to an "enabled" (always) and "disabled" (never) setting.

Describe the solution you'd like

class Credentials extends Equatable {
  final String username;
  final String password;

  const Credentials({this.username, this.password});

  @override
  List<Object> get props => [username, password];
}

// override stringify for all Equatable instances
EquatableConfig.stringify = true;

If stringify is overridden for an individual Equatable it will take precedence over the EquatableConfig value.

Addresses #60 and #65

hashcodes different on web vs. flutter app

I have an Equatable object, I'm creating the same object in the flutter app, then passing the data to a dart web app and noticed the hashCodes are different on the two platforms. I wanted the Ids to match across web and flutter so I could pass the hashcode back and forth to identify the data I wanted, but the hashcodes are different on web and flutter.

Add a const constructor and make props @protected.

Is your feature request related to a problem? Please describe.
I would like to use Equatable with my data classes that have const constructors.

Describe the solution you'd like
Add a const constructor option to Equatable such as

const Equatable.const([this.props = const []]);

I started making a PR for this but it also seems like the Equatable class should mixin EquatableMixin (after accepting #30) to eliminate duplicated code. I'm not really sure how to go about all this but it seems like it should be easy enough.

Add @immutable annotation to Equatable class

Is your feature request related to a problem? Please describe.

At the moment Equatable only work for immutable classes, but you can extends Equatable in a classes that not are immutable

#21
#22

Describe the solution you'd like
You can add the @immutable annotation, and now you get analysis warning.

@immutable
class Equatable ....

Describe alternatives you've considered
Equatable can override the hashCode and == for not immutable objects.

Additional
Also like the same behavior for EquatableMixin but i don't know how to do that.

Matching HashCodes for objects with inner lists

Describe the bug
I have created two objects, both contain a String object and a List<int> object. The List<int> objects are the same, but the String object differs. In theory, this should mean the two objects are different. Checking the equality using == and != works fine, but the hash codes match exactly.

To Reproduce

void main() {
  test("Equatable check", () {
    final a = _MyClass(items: _createItemList(title: "abcde"));
    final b = _MyClass(items: _createItemList(title: "fghij"));

    expect(a != b, true); // This passes
    final aHashCode = a.hashCode;
    print("a\'s hashcode: $aHashCode");
    final bHashCode = b.hashCode;
    print("b\'s hashcode: $bHashCode");
    expect(aHashCode != bHashCode, true); // This fails.
  });
}

List<ListItem> _createItemList({String title = "mock"}) {
  final result = <ListItem>[
    ListItem(
      title: "title " + title,
      innerList: const <int>[12345],
    ),
  ];
  return result;
}

class _MyClass extends Equatable {
  final List<ListItem> items;

  const _MyClass({
    @required this.items,
  });

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

class ListItem extends Equatable {
  final String title;
  final List<int> innerList;

  const ListItem({
    @required this.title,
    this.innerList,
  });

  @override
  List get props => [title, innerList];
}

The result is:

00:03 +0: Equatable check
a's hashcode: 875940833
b's hashcode: 875940833
00:03 +0 -1: Equatable check [E]
  Expected: <true>
    Actual: <false>

However, if I create my List<ListItem> without an inner list:

  final result = <ListItem>[
    ListItem(
      title: "title " + title,
    ),
  ];

The hash codes differ and the test passes:

00:03 +0: Equatable check
a's hashcode: 237995961
b's hashcode: 268130525
00:03 +1: All tests passed! 

Expected behaviour
Unless I've misunderstood, I'd expect the == operator to be in sync with the hashcode, such that a == b is the same as a.hashcode == b.hashcode.

Version
Running Equatable 1.0.2

Additional context
I am testing this using Flutter:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-GB)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 11.3)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.41.1)
[✓] Connected device (1 available)

• No issues found!

What I have tried

  • I have tried using the SDK collection dependency (1.14.11) and forcing the latest dependency (1.14.12), but both show the same result.
  • I have tried creating the lists outside of a method so that:
const a = _MyClass(items: <ListItem>[
  ListItem(
    title: "ABCDE",
    innerList: <int>[12345],
  ),
]);
const b = _MyClass(items: <ListItem>[
  ListItem(
    title: "FGHIJ",
    innerList: <int>[12345],
  ),
]);
  • Giving different values in the innerList property. This works so that the test passes, but I don't understand how two ListItems with the same innerList but different title properties have the same hashcode:
final a = _MyClass(items: <ListItem>[
  ListItem(
    title: "ABCDE",
    innerList: <int>[12345],
  ),
]);
final b = _MyClass(items: <ListItem>[
  ListItem(
    title: "FGHIJ",
    innerList: <int>[67890], // Different values cause the test to pass
  ),
]);

How to compare List<T extends Equatable> by elements ?

Describe the question

I have a question about how to compare lists of instances extending Equatable.

Dart == for list does not compare their elements, but hashcode as you know.
So when you compare two instances even extending Equatable, the result would be always false.
The comparison does not depend on their values. This is an expected behavior I guess.

But I would like to know if there's any practice to do it. I'd appreciate it if you have any suggestions.

To Reproduce

class E extends Equatable {
  const E(this.value);
  
  final int value;

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

final originalList = [E(1), E(1), E(1)];
final a = [...originalList];
final b = [...originalList];

a == b; // return false, expected to be true

Expected behavior

Expected the comparison to return true, comparing depending on their actual values, not hashcode.

Version

Dart version 2.8.4

Additional context

N/A

Typo in utils source code

You have following code in utils:
if (list1[i] is List && list1[i] is List) { which I think is a typo.
Could you check?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.