Reusing input validator - flutter

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');}}),
],
);

Related

Don't execute assignment if value is null

I am still coming up to speed with dart and wanted to know if there was an easier way to not execute a statement if the value is null. See example below:
I can always do the if statements below for setting field3 and field4, but felt like something like field5 should work. But when I try to do that, it complains that a null check operator is used on a null value.
Also I don't want to change the Map to have a dynamic value.
Is there a single one liner to do what I am trying to do, or do I just need to check for null before setting the value.
Map<String, Object> myMap = {};
print('running now');
try {
myMap['field1'] = DummyClass.getString('hello');
myMap['field2'] = DummyClass.getString('good');
//Is there a more concise way to do this than the 2 options below?
if (DummyClass.getOptionalString('goodbye') != null) {
myMap['field3'] = DummyClass.getOptionalString('goodbye')!;
}
String? temp = DummyClass.getOptionalString('go');
if (temp != null) {
myMap['field4'] = temp;
}
// This gives an error 'null check operator used on a null value'
// myMap['field5'] ??= DummyClass.getOptionalString('to')!;
} catch (e) {
print('error condition, $e');
}
print(myMap);
}
class DummyClass {
static String getString(String? strParam) {
String? retString = getOptionalString(strParam);
if (retString == null) {
throw ('nulls are not allowed');
}
return retString;
}
static String? getOptionalString(String? strParam) {
if (strParam == null || strParam.length < 3) {
return null;
}
return strParam;
}
}
There's no built-in way to do what you want, but you could write a function (or extension method) to do it. For example:
extension MapTrySet<K, V> on Map<K, V> {
void trySet(K key, V? value) {
if (value != null) {
this[key] = value;
}
}
}
and then you could do:
myMap.trySet('field3', DummyClass.getOptionalString('goodbye'));
myMap.trySet('field4', DummyClass.getOptionalString('go'));
Alternatively, if you really want to use normal Map syntax, you could create your own Map class that has a void operator []=(K key, V? value) override and does nothing when the value is null, but that probably would not be worth the effort.
The issue is that the ??= operator assigns to the left if it is null. Expanded, it would look something like this:
a ??= b;
// Equivalent to:
if (a == null) {
a = b;
}
Which is not something that you're trying to achieve. AFAIK, there is no such operator yet in Dart. However, you can try this:
final possiblyNullValue = '';
final myMap = <String, String>{};
myMap['key'] = possiblyNullValue ?? myMap['key'];
// Equivalent to:
if (possiblyNullValue != null) {
myMap['key'] = possiblyNullValue;
}
// or:
myMap['key'] = possiblyNullValue != null? possiblyNullValue : myMap['key'];
Which would work in your case as a one-liner.
You could create your map with all entries, even null, and then filter the null values out:
void main() {
try {
final myMap = <String, dynamic>{
'field1': DummyClass.getString('hello'),
'field2': DummyClass.getString('good'),
'field3': DummyClass.getOptionalString('goodbye'),
'field4': DummyClass.getOptionalString('go'),
}..removeWhere((k, v) => v == null);
print(myMap);
} catch (e) {
print('error condition, $e');
}
}

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

Dart equality for different types

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.

Using a string to access a get method in themes

So basically, I want to be able to use a line of code like:
body: Container(
height: getDimension(context, true, 'homeContainer');
)
but I don't know how I would go about using the String type to access the data in my theme extension.
extension CustomDimensions on MaterialTapTargetSize {
//double get 'name' => 'dimension';
double get 'homeContainer' => 1.0;
}
double getDimension(BuildContext context, bool isHeight, String type) {
double value;
isHeight ? value = MediaQuery.of(context).size.height * Theme.of(context).textTheme.$type : null;
return value;
}
extension CustomDimensions on TextTheme {
double type({String data}) {
if(data == 'homeContainer')
return 1.0;
else
return 0; // else code
}
}
double getDimension(BuildContext context, bool isHeight, String type) {
double value;
isHeight ? value = MediaQuery.of(context).size.height * Theme.of(context).textTheme.type(data: type) : null;
return value;
}

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) {}