equatable
equatable copied to clipboard
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.
Hey @ThinkDigitalSoftware 👋 Thanks for the feature request!
Can you provide an example of what the diff would look like? Thanks!
Hey. I would guess that it would be something like... of course wildly open for suggestions.
{
paramName: Diff(presentIn: obj1, value: value)
}
class Diff<T>{
T presentIn;
dynamic value;
}
@ThinkDigitalSoftware What type of obj1 and obj2? If you want to compare between collection objects as List, Map,..etc, you can use the following way:
Assume obj1 and obj2 are Map.
const DeepCollectionEquality _equality = DeepCollectionEquality();
_equality.equals(obj1, obj2);
@phamnhuvu-dev it would be a custom class that extends equatable. From what I know, your methods return a boolean value. This is going to have a Set.difference-like functionality
This feature can be misleading. Equatable can only differentiate between objects based on the props you pass to it (+ staticly available info). Therefore, the diff can only state what was the prop based on which Equatable would consider 2 objects unequal.
Since props
getter is now public, I can image this to be a separate "utility function" that ships with provider but I would not make it part of the core class/functionality. Use case seems very specialised. I don't know where I'd use it in the projects I'm developing other than for debugging/logging (hence the utility nature).
Yes, definitely for debugging. And yes, since it's a getter now, it wont be as easy
in flutter_bloc, we have Transition that shows you the currentState and nextState which base on Equatable, so I need a way to compare the diff between curentState and nextState, could someone help me with this? or at least can we do something like convert Object to JSON string by default without writing Map<String, dynamic> toJson() => { 'name': name, 'email': email, };
so if later we want to change the default format we could override it
thanks
Well you can write specifically,
if (previousState.name!= nextState.name) ....
@ThinkDigitalSoftware , your example is too simple, if we have nested object it will too hard to check
@ThinkDigitalSoftware , your example is too simple, if we have nested object it will too hard to check
Unless it's not really. You're only comparing objects
like we have state contain Post object which has array of comments, so you need to compare all the comments objects right?. so I want to see the diff in this case
@hunght can you please provide a sample of what you would like the diff to look like?
Since equatable compares objects anyway, then, in the for loop (or however it's implemented) when you're comparing objects,
if(obj1 == obj2){
diffMap[fieldName] = true;
else {
diffMap[fieldName] = false;
}
this could return, for example a Map<String, bool> {} // not sure on implementation,
or a custom class like... EquatableDiff(List<Object> equalValues, List<Object> unequalValues);
For the map, field name can be runtimeType, or anything that can identify the object.
The second option seems preferable though.
This is just beneficial for debugging purposes so we don't have to manually inspect the variables to see what's different.
@hunght
like we have state contain Post object which has array of comments, so you need to compare all the comments objects right?. so I want to see the diff in this case
I see what you mean. Say it was done with a custom class, a diff would be
EquatableDiff(
List<Object> equalValues = [postListVariable, commentListVariable],
List<Object> unEqualValues = [userListVariable],
)
Since it's only checking lists and maps to see if are or are not equal using DeepEquality, then it's a single true or false value for the entire list or map. If you want to go deeper, then we'd be discussing Set
differences, which can get complex and would, IMO be beyond the scope of this library and this issue.
@felangel @ThinkDigitalSoftware this is what I want to achieve, it is written in js, pls check the answer with sample output https://stackoverflow.com/a/8596559/723160
this is something similar to redux devtool that they can detect the diff between the current state and next state, and show the detail of the diffs
@hunght it's a lot easier to create arbitrary objects in js. You can just add things to them similar to how we add entries to maps in Dart. So the alternative would be returning a map
@ThinkDigitalSoftware yes, return a map is work for me too
this is something similar to redux devtool that they can detect the diff between the current state and next state, and show the detail of the diffs
Also when working with bloc, I'd love to see my BlocObserver print the fields that differ from the last state only.
I also ran into a situation where this would be handy. As a proof of concept, something like this may work. Maybe a custom EquatableValueDifference
to describe the difference between the two compared values.
Equatable Implementation
mixin EquatableDifference on Equatable {
Iterable<Map<int, Object?>> difference(Equatable other) sync* {
for (var i = 0; i < props.length; i++) {
final thisProp = props[i];
final otherProp = other.props[i];
if (otherProp != thisProp) yield {i: otherProp};
}
}
}
Example Class
class Credentials extends Equatable with EquatableDifference {
const Credentials({
required this.username,
required this.password,
});
final String username;
final String password;
@override
List<Object> get props => [username, password];
}
Test
void main() {
group('EquatableDifference', () {
test('empty for identical Equatable objects', () {
const cred1 = Credentials(username: 'username', password: 'password');
const cred2 = Credentials(username: 'username', password: 'password');
expect(cred1.difference(cred2), isEmpty);
});
test('not empty for identical Equatable objects', () {
const cred1 = Credentials(username: 'username', password: 'password');
const cred2 = Credentials(username: 'username2', password: 'password');
expect(
cred1.difference(cred2).first,
{0: 'username2'},
);
});
test('not empty for identical Equatable objects [1]', () {
const cred1 = Credentials(username: 'username1', password: 'password1');
const cred2 = Credentials(username: 'username2', password: 'password2');
expect(
cred1.difference(cred2).toList(),
[
{0: 'username2'},
{1: 'password2'},
],
);
});
});
}