Dart equality for different types - flutter

I want to implement class-like enum
#immutable
abstract class Enum<T> {
final T? value;
#literal
const Enum([this.value]);
List<Object?> get values => value != null ? [value] : const [];
#override
bool operator ==(Object? other) {
if (other is T) {
return value == other;
}
return other is Enum && other.value == value;
}
#override
int get hashCode => super.hashCode;
}
class EnumClass extends Enum<int> {
#literal
const EnumClass(int value) : super(value);
static const zero = const EnumClass(0);
static const one = const EnumClass(1);
// You can have any functions you want
}
so that I can do the following operations
if(EnumClass.zero == 0) {
// This works!
}
but my test failed
void main() {
test('Compare EnumClass.zero with 0', () {
if (EnumClass.zero == 0) {
print('same'); // This is printed
}
expect(EnumClass.zero, 0); // But the test failed
});
}
The comparison with 0 works as expected but the expect function doesn't work.
What should I do to make the test pass?

Note that:
expect(EnumClass.zero, 0);
fails, but:
expect(0, EnumClass.zero);
succeeds. expect(actual, expected) by default uses an equals Matcher, and the implementation for equals checks expected == actual instead of actual == expected.
Arguably it'd be more intuitive if expect(A, B) matched the original order by comparing A == B instead of B == A, but having non-symmetric equality is going to be error-prone and probably is not a good idea anyway.
Also see https://github.com/dart-lang/matcher/issues/94.

Related

How can you return null from orElse within Iterable.firstWhere with null-safety enabled?

Prior to null-safe dart, the following was valid syntax:
final list = [1, 2, 3];
final x = list.firstWhere((element) => element > 3, orElse: () => null);
if (x == null) {
// do stuff...
}
Now, firstWhere requires orElse to return an int, opposed to an int?, therefore I cannot return null.
How can I return null from orElse?
A handy function, firstWhereOrNull, solves this exact problem.
Import package:collection which includes extension methods on Iterable.
import 'package:collection/collection.dart';
final list = [1, 2, 3];
final x = list.firstWhereOrNull((element) => element > 3);
if (x == null) {
// do stuff...
}
You don't need external package for this instead you can use try/catch
int? x;
try {
x = list.firstWhere((element) => element > 3);
} catch(e) {
x = null;
}
A little bit late but i came up with this:
typedef FirstWhereClosure = bool Function(dynamic);
extension FirstWhere on List {
dynamic frstWhere(FirstWhereClosure closure) {
int index = this.indexWhere(closure);
if (index != -1) {
return this[index];
}
return null;
}
}
Example use:
class Test{
String name;
int code;
Test(code, this.name);
}
Test? test = list.frstWhere(t)=> t.code==123);
An alternative is that you set a nullable type to the list.
Instead of just [1, 2, 3], you write <int?>[1, 2, 3], allowing it to be nullable.
void main() {
final list = <int?>[1, 2, 3];
final x = list.firstWhere(
(element) => element != null ? (element > 3) : false,
orElse: () => null);
print(x);
}
This should work, and it's a better solution:
extension IterableExtensions<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T element) comparator) {
try {
return firstWhere(comparator);
} on StateError catch (_) {
return null;
}
}
}
To add to #Alex Hartfords answer, and for anyone who doesn't want to import a full package just for this functionality, this is the actual implementation for firstWhereOrNull from the collection package that you can add to your app.
extension FirstWhereExt<T> on List<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (final element in this) {
if (test(element)) return element;
}
return null;
}
}

why I can't run my code where is the problem of this code?

void main() {
const list = [1, 2, 3, 4];
final odd = where(list, (value) => value % 2 == 1);
print(odd);
}
List<T> where<T>(List<T> items, bool Function(T) f) {
var results = <T>[];
for (var item in items) {
if (f(item)) {
results.add(item);
}
}
return results;
}
this kind of error show in my terminal
lib/exercises/18-implement-where-function.dart:3:44: Error: The operator '%' isn't defined for the class 'Object?'.
'Object' is from 'dart:core'.
Try correcting the operator to an existing operator, or defining a '%' operator.
final odd = where(list, (value) => value % 2 == 1);
void main() {
const list = [1, 2, 3, 4];
final odd = where(list, (value) => value! % 2 == 1);
print(odd);
}
List<T> where<T>(List<T> items, bool Function(T) f) {
var results = <T>[];
for (var item in items) {
if (f(item)) {
results.add(item);
}
}
return results;
}
The operator '%' isn't defined for the type 'Object'.
Try defining the operator '%'
Possible Dart analyzer is not able to infer type.
It's better to wait for the answer from the developers.
A workaround as below:
void main() {
const list = [1, 2, 3, 4];
final odd = where(list, (int value) => value % 2 == 1);
print(odd);
}
List<T> where<T>(List<T> items, bool Function(T) f) {
var results = <T>[];
for (var item in items) {
if (f(item)) {
results.add(item);
}
}
return results;
}
P.S.
I also encounter this issue from time to time in Dart.
On the one hand, the type is not explicitly specified (for parameter value) and must be inferred, on the other hand, it is not clear why it is not inferred from another type (aslo T), which is inferred from the value (in our case. list) wuth the same type parameter (T).
I've run your code on https://dartpad.dev/dart, it's working though. I think there is an issue with your import classes.

Reusing input validator

I'm trying to create a logic for reusing multiple input validator at once. Here is DartPad code
void main() {
String value = 'pppppp';
print(InputValidator([InputValidator.minCharacters(value, 2),
InputValidator.maxCharacters(value, 5)
]).validate().toString());
}
class InputValidator {
final List<String> validators;
InputValidator(this.validators);
List<String> validate() {
List<String> result = [];
validators.where((s) => s != null).forEach(result.add);
return result;
}
static String maxCharacters(String value, int limit) {
if (value.length > limit) {
return 'Max $limit characters allowed';
}
return null;
}
static String minCharacters(String value, int limit) {
if (value.length < limit) {
return 'Min $limit characters required';
}
return null;
}
}
the logic works great however I'm trying to make it even easier where I wouldn't need to pass value into each method. I want to make value parameter in InputValidator class and take that value when running a method. Problem is that each method is static so I can not use that parameter in method.. Is there any other way?
Here is how I would like to use it
validator: InputValidator(value, [InputValidator.minCharacters(2),
InputValidator.maxCharacters(5)
]).validate()
Once again thanks to #pskink where he pointed out on FormFieldValidator. With that it is very simple to reuse validating of TextFormField.
Here is validator class
FormFieldValidator<String> all(Iterable validators) {
return (String s) {
var error = validators
.map((v) => v(s))
.where((error) => error != null)
.join(' AND ');
return error.isEmpty? null : error;
};
}
FormFieldValidator<String> uppercase() {
return (String s) => s.startsWith(RegExp('[A-Z]'))? null : 'does not start with uppercase' ;
}
FormFieldValidator<String> limit(min, max) {
return (String s) => (min <= s.length && s.length <= max)? null : 'length not in range [$min..$max]';
}
and here how to use it
Column(
children: [
Form(key: _formKey, child: TextFormField(validator:all([limit(2,5), uppercase()]))),
FlatButton(child:Text('Validate'), onPressed: (){if(_formKey.currentState.validate())
{print('OK');}}),
],
);

Can object properties be used as function parameters?

I have a class with several Boolean properties:
class aTest {
String name;
bool twoGroups;
bool continuous;
bool parametric;
bool covariates;
bool paired;
aTest(
{this.name,
this.twoGroups,
this.continuous,
this.parametric,
this.covariates,
this.paired});
} //end aTest
I also have a list with instances of aTest:
List<aTest> testList = [
aTest(
name: "independent samples t-test",
twoGroups: true,
continuous: true,
parametric: true,
covariates: false,
paired: false),
//followed by a bunch of similar objects
]
Elsewhere in my app I filter the List<aTest> with procedures like:
void isParametric(bool value) {
List<aTest> newList = [];
for (aTest test in testList) {
if (test.parametric == value || test.parametric == null) {
newList.add(test);
}
}
testList = newList;
}
void isTwoGroups(bool value) {
List<aTest> newList = [];
for (aTest test in testList) {
if (test.twoGroups == value || test.twoGroups == null) {
newList.add(test);
}
}
testList = newList;
}
(I don't know whether this is the best way to filter and remove objects from the List.) All that differs among these procedures is an object property, e.g., test.parametric and test.twoGroups in the code above.
Is there a way to refactor the code? Something like
void filter (aBooleanPropertyGoesHere, bool value)
You can simply filter with the lists with one liner by where method.
var parametricList = testList.where((i) => (i.continuous && i.parametric == null)).toList()
var twoGroupsList = testList.where((i) => (test.twoGroups == value || test.twoGroups == null)).toList()
Something like this https://dartpad.dev/79a7e9aa5882af745b6ff2cb55815921
For detailed explanation check out the documentation

Check whether a list contain an attribute of an object in dart

I need to check whether myItemsList contains myitem.itemId or not, If it exists need to add itemQuantity, if it not exists need to add myitem object to myItemsList.
List<MyItem> myItemsList = new List();
MyItem myitem = new MyItem (
itemId: id,
itemName: name,
itemQuantity: qty,
);
if (myItemsList.contains(myitem.itemId)) {
print('Already exists!');
} else {
print('Added!');
setState(() {
myItemsList.add(myitem);
});
}
MyItem class
class MyItem {
final String itemId;
final String itemName;
int itemQuantity;
MyItem ({
this.itemId,
this.itemName,
this.itemQuantity,
});
}
above code is not working as expected, please help me to figure out the issue.
Contains() compares the whole objects.
Besides overriding == operator or looping over, you can use list's singleWhere method:
if ((myItemsList.singleWhere((it) => it.itemId == myitem.itemId,
orElse: () => null)) != null) {
Edit:
As Dharaneshvar experienced and YoApps mentioned in the comments .singleWhere raises StateError when more elements are found.
This is desired when you expect unique elements such as in the case of comparing IDs.
Raised error is the friend here as it shows that there is something wrong with the data.
For other cases .firstWhere() is the right tool:
if ((myItemsList.firstWhere((it) => it.itemName == myitem.itemName,
orElse: () => null)) != null) {
// EO Edit
Whole example:
List<MyItem> myItemsList = new List();
​
class MyItem {
final String itemId;
final String itemName;
int itemQuantity;
​
MyItem({
this.itemId,
this.itemName,
this.itemQuantity,
});
}
​
void main() {
MyItem myitem = new MyItem(
itemId: "id00",
itemName: "name",
itemQuantity: 50,
);
​
myItemsList.add(myitem);
​
String idToCheck = "id00";
​
if ((myItemsList.singleWhere((it) => it.itemId == idToCheck,
orElse: () => null)) != null) {
print('Already exists!');
} else {
print('Added!');
}
}
As already said before, contains compares two Objects with the == operator. So you currently compare MyItem with String itemId, which will never be the same.
To check whether myItemsList contains myitem.itemId you can use one of the following:
myItemsList.map((item) => item.itemId).contains(myitem.itemId);
or
myItemsList.any((item) => item.itemId == myitem.itemId);
You're using contains slightly wrong.
From: https://api.dartlang.org/stable/2.2.0/dart-core/Iterable/contains.html
bool contains(Object element) {
for (E e in this) {
if (e == element) return true;
}
return false;
}
You can either override the == operator, see: https://dart-lang.github.io/linter/lints/hash_and_equals.html
#override
bool operator ==(Object other) => other is Better && other.value == value;
Or you can loop over your list and search the normal way one by one, which seems slightly easier.
One more way to check does list contain object with property or not
if (myList.firstWhereOrNull((val) => val.id == someItem.id) != null) {}