Skip to content

Commit

Permalink
Add EquatableMixin (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel authored Feb 21, 2019
1 parent 715888e commit 8a5ca34
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 52 deletions.
50 changes: 27 additions & 23 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
# 0.1.0
# 0.2.0

Initial Version of the library.
Add `EquatableMixin` and `EquatableMixinBase`

- Includes the ability to extend `Equatable` and not have to override `==` and `hashCode`.
# 0.1.10

# 0.1.1
Enhancements to `toString` override

Minor Updates to Documentation.
# 0.1.9

# 0.1.2
equatable has 0 dependencies

Additional Updates to Documentation.
# 0.1.8

- Logo Added
Support `Iterable` props

# 0.1.3
# 0.1.7

Bug Fixes
Added `toString` override

# 0.1.4
# 0.1.6

Performance Optimizations
Documentation Updates

- Performance Tests

# 0.1.5

Additional Performance Optimizations & Documentation Updates

# 0.1.6
# 0.1.4

Documentation Updates
Performance Optimizations

- Performance Tests
# 0.1.3

# 0.1.7
Bug Fixes

Added `toString` override
# 0.1.2

# 0.1.8
Additional Updates to Documentation.

Support `Iterable` props
- Logo Added

# 0.1.9
# 0.1.1

equatable has 0 dependencies
Minor Updates to Documentation.

# 0.1.10
# 0.1.0

Enhancements to `toString` override
Initial Version of the library.

- Includes the ability to extend `Equatable` and not have to override `==` and `hashCode`.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,59 @@ class Person extends Equatable {
}
```

## 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 `EquatableMixinBase` and `EquatableMixin` like so:

```dart
class EquatableDateTime extends DateTime
with EquatableMixinBase, 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 get props {
return [year, month, day, hour, minute, second, millisecond, microsecond];
}
}
```

Now if we want to create a subclass of `EquatableDateTime`, we can continue to just use the `EquatableMixin` and override `props`.

```dart
class EquatableDateTimeSubclass extends EquatableDateTime with EquatableMixin {
final int century;
EquatableDateTime(
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 get props => super.props..addAll([century]);
}
```

## Performance

You might be wondering what the performance impact will be if you use `Equatable`.
Expand Down
36 changes: 36 additions & 0 deletions example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,53 @@ class Credentials extends Equatable {
Credentials({this.username, this.password}) : super([username, password]);
}

class EquatableDateTime extends DateTime
with EquatableMixinBase, 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 get props {
return [year, month, day, hour, minute, second, millisecond, microsecond];
}
}

void main() {
// Extending Equatable
final credentialsA = Credentials(username: 'Joe', password: 'password123');
final credentialsB = Credentials(username: 'Bob', password: 'password!');
final credentialsC = Credentials(username: 'Bob', password: 'password!');

print(credentialsA == credentialsA); // true
print(credentialsB == credentialsB); // true
print(credentialsC == credentialsC); // true
print(credentialsA == credentialsB); // false
print(credentialsB == credentialsC); // true

print(credentialsA); // [Joe, password123]
print(credentialsB); // [Bob, password!]
print(credentialsC); // [Bob, password!]

// Equatable Mixin
final dateTimeA = EquatableDateTime(2019);
final dateTimeB = EquatableDateTime(2019, 2, 20, 19, 46);
final dateTimeC = EquatableDateTime(2019, 2, 20, 19, 46);

print(dateTimeA == dateTimeA); // true
print(dateTimeB == dateTimeB); // true
print(dateTimeC == dateTimeC); // true
print(dateTimeA == dateTimeB); // false
print(dateTimeB == dateTimeC); // true

print(dateTimeA); // 2019-01-01 00:00:00.000
print(dateTimeB); // 2019-02-20 19:46:00.000
print(dateTimeC); // 2019-02-20 19:46:00.000
}
1 change: 1 addition & 0 deletions lib/equatable.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
library equatable;

export './src/equatable.dart';
export './src/equatable_mixin.dart';
31 changes: 4 additions & 27 deletions lib/src/equatable.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import './equatable_utils.dart';

/// A class that helps implement equality
/// without needing to explicitly override == and [hashCode].
/// Equatables override their own == and [hashCode] based on
Expand All @@ -18,35 +20,10 @@ abstract class Equatable {
identical(this, other) ||
other is Equatable &&
runtimeType == other.runtimeType &&
_equals(props, other.props);
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;
}

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;
}
int get hashCode => runtimeType.hashCode ^ mapPropsToHashCode(props);

@override
String toString() => props.isNotEmpty ? props.toString() : super.toString();
Expand Down
33 changes: 33 additions & 0 deletions lib/src/equatable_mixin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import './equatable_utils.dart';

/// You must define the [EquatableMixinBase] on the class
/// which you want to make Equatable.
/// `class EquatableDateTime extends DateTime with EquatableMixinBase, EquatableMixin { ... }`
/// This exposes the `props` getter which can then be overridden to include custom props in subclasses.
/// The `props` getter is used to override `==` and `hashCode` in the [EquatableMixin].
mixin EquatableMixinBase on Object {
List get props => [];

@override
String toString() => super.toString();
}

/// You must define the [EquatableMixin] on the class
/// which you want to make Equatable and the class
/// must also be a descendent of [EquatableMixinBase].
/// [EquatableMixin] does the override of the `==` operator as well as `hashCode`.
mixin EquatableMixin on EquatableMixinBase {
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is EquatableMixin &&
runtimeType == other.runtimeType &&
equals(props, other.props);
}

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

@override
String toString() => props.isNotEmpty ? props.toString() : super.toString();
}
24 changes: 24 additions & 0 deletions lib/src/equatable_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
int mapPropsToHashCode(List props) {
int hashCode = 0;

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

return hashCode;
}

bool equals(dynamic list1, dynamic 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;
}
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: equatable
description: An abstract class that helps to implement equality without needing to explicitly override == and hashCode.
version: 0.1.10
version: 0.2.0
author: felix.angelov <[email protected]>
homepage: https://github.com/felangel/equatable

environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
sdk: ">=2.1.0 <3.0.0"

dev_dependencies:
test: ">=1.3.0 <2.0.0"
Expand Down
Loading

0 comments on commit 8a5ca34

Please sign in to comment.