Implement the API modifications
Connection with issue(s)
Close #128
Connected to #119 #116
Solution description
Screenshots or Videos
To Do
- [x] Read contributing guide
- [ ] Check the original issue to confirm it is fully satisfied
- [ ] Add solution description to help guide reviewers
- [ ] Add unit test to verify new or fixed behaviour
- [ ] If apply, add documentation to code properties and package readme
Codecov Report
Attention: Patch coverage is 97.79559% with 11 lines in your changes missing coverage. Please review.
Project coverage is 98.74%. Comparing base (
e8da6fc) to head (1834d8e). Report is 96 commits behind head on refactor_2024_11.
Additional details and impacted files
@@ Coverage Diff @@
## refactor_2024_11 #129 +/- ##
=====================================================
+ Coverage 86.44% 98.74% +12.29%
=====================================================
Files 109 111 +2
Lines 1483 1510 +27
=====================================================
+ Hits 1282 1491 +209
+ Misses 201 19 -182
| Flag | Coverage Δ | |
|---|---|---|
| unittests | 98.74% <97.79%> (+12.29%) |
:arrow_up: |
Flags with carried forward coverage won't be shown. Click here to find out more.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
@ArturAssisComp before implement all validators, I think that is better create a documentation on readme to be discuss about. I don't sure about the "duplicate" all validators to add a Elementary behaviour
@deandreamatias ok, I will do that. I implemented only the bool validators. I am using the version already implemented to make use of the tests, but today I will implement a prototype of the FormBuilderValidator using the new API to discuss the idea. Thanks for the feedback.
I added a section at the end of the readme for the discussion. But it can also be discussed here. I made a video documentation about what I did too.
https://youtu.be/zgzHehmsFd0?si=AE-zE4ESETPFP9xU
I taken a look on this propouse. My thougths:
-
- Is really hard to catch all cases and logic on the API propouse. Make sense the abstract concept, but I think that the implementation can be more simple for developer experience.
Maybe can use FormBuilderValidators.compose to add required and optional validators. Like
FormBuilderValidators.compose(required: [validators], optional: [another validators])
- Is really hard to catch all cases and logic on the API propouse. Make sense the abstract concept, but I think that the implementation can be more simple for developer experience.
Maybe can use FormBuilderValidators.compose to add required and optional validators. Like
- The name
ValidatorBuilderis really necessary? On Flutter, Builder word usually is used to widgets that have a builder property. - The types validators like
IsNumericElementaryValidator, I think like a nice thing to have, is an improvement to reuse code. - Thinks like
ValidatorBuilder.numeric(
errorText: 'La edad debe ser numérica.',
and: <BaseElementaryValidator<num, dynamic>>[
ValidatorBuilder.max(70),
])
could be simplify. A option is use ValidatorBuilder.numeric().and(ValidatorBuilder.max(70)) or even improve andc reate something like ValidatorBuilder.numeric() && ValidatorBuilder.max(70)
- Is really hard to catch all cases and logic on the API propouse. Make sense the abstract concept, but I think that the implementation can be more simple for developer experience. Maybe can use FormBuilderValidators.compose to add required and optional validators. Like
FormBuilderValidators.compose(required: [validators], optional: [another validators])
I will investigate that with a prototype.
- The name
ValidatorBuilderis really necessary? On Flutter, Builder word usually is used to widgets that have a builder property.
That is right. I forgot about the builder semantics that is specific to Flutter. I will think about another name.
- Thinks like
ValidatorBuilder.numeric( errorText: 'La edad debe ser numérica.', and: <BaseElementaryValidator<num, dynamic>>[ ValidatorBuilder.max(70), ])could be simplify. A option is use
ValidatorBuilder.numeric().and(ValidatorBuilder.max(70))or even improve andc reate something likeValidatorBuilder.numeric() && ValidatorBuilder.max(70)
I think that would be a good idea. I will investigate with prototypes too.
Tomorrow I will give a better comment on the new API prototype. But it is already implemented for the examples. Fell free to take a look at the new_api_prototype.dart if you want. Otherwise, wait for my comments. I also did a draft benchmark on a password checker comparing the new api with the old one.
Conventions: I will call the API that is being used as "original api", the previous proposal as "initial prototype" and this prototype as "new prototype". About the new prototype:
- The idea was to simply decouple the required, optional and type validators from the other validators. Those validators are considered intermediate validators, thus they receive as an argument the next validator. There are 'and' and 'or' compositions that make it easier to create complex logical combination of multiple validators.
- It maintains the functionality of the original and the initial prototype
// All the use cases of the example/lib/main.dart were reproduced using the new prototype. They are in example/lib/app_refactoring_main.dart
// Some examples are
// Original api
FormBuilderValidators.compose(<FormFieldValidator<String>>[
/// Makes this field required
FormBuilderValidators.required(),
/// Ensures the value entered is numeric - with custom error message
FormBuilderValidators.numeric(
errorText: 'La edad debe ser numérica.',
),
/// Sets a maximum value of 70
FormBuilderValidators.max(70),
/// Include your own custom `FormFieldValidator` function, if you want
/// Ensures positive values only. We could also have used `FormBuilderValidators.min( 0)` instead
(String? val) {
if (val != null) {
final int? number = int.tryParse(val);
if (number == null) return null;
if (number < 0) return 'We cannot have a negative age';
}
return null;
}
]);
// New prototype
/// Makes this field required (req is an alias for isRequired)
req(
/// Ensures the value entered is numeric - with custom error message
isNum(
and([
/// Sets a maximum value of 70
max(70),
/// Include your own custom `FormFieldValidator` function, if you want
(num value) {
if (value < 0) return 'We cannot have a negative age';
return null;
},
]),
isNumMessage: 'La edad debe ser numérica.',
),
);
- It removes redundancy of computations for composition of validators. Most of them recalculate if it is null/empty and check the type. As a direct result, it improves the response time of composed validators.
- It maintains the simplicity of the original api.
- It is completely compatible with the original api
/// The previous example but using the compose method from the original api
FormBuilderValidators.compose(<FormFieldValidator<String>>[
req(isNum(max(70), isNumMessage: 'La edad debe ser numérica.')),
/// Include your own custom `FormFieldValidator` function, if you want
/// Ensures positive values only. We could also have used `FormBuilderValidators.min( 0)` instead
(String? val) {
if (val != null) {
final int? number = int.tryParse(val);
if (number == null) return null;
if (number < 0) return 'We cannot have a negative age';
}
return null;
}
])
- It maintains the easiness for creating custom validators like it was for the original api
- It makes it easier for composing validators using
andandorlogical operators.
// Composition with and
// Original api
FormBuilderValidators.compose(<FormFieldValidator<String>>[
FormBuilderValidators.required(),
FormBuilderValidators.numeric(),
FormBuilderValidators.min(0),
FormBuilderValidators.max(120),
]),
// New prototype
isRequired(isNum(and([min(0), max(120)])));
// Composition with or and and: The error message may be improved in the future with composition functions like: prefix, suffix, etc. There is already a composition validator called message that replaces the inner message with another one.
//(optional) Choose a value that is either a num in the set: (-10,5] U {7, 8} U (100, +inf) or an even integer
isOptional(or([
isNum(or([
and([gt(-10), ltE(5)]),
equal(7),
equal(8),
gt(100),
])),
isInt(
(int value) =>value % 2 == 0 ? null : 'The input must be even.',
),
]));
I also explored possibilities using operator overloading which would allow things like: req() * isNum() * (gt(10) * lt(15) + gtE(20)), using boolean logic notation as example. But it would become very complex to handle some edge cases and the error message composition, and the compiler would not help too much during the composition. It would be necessary to rely on some runtime check or to maintain the generality of each validator, maintaining the redundancy.
I want to know if the parameter regex in some of the validators is really necessary. There is an example below of a validator that checks if the input has a minimum number of lowercase chars. It already has the default regex: RegExp('[a-z]') which I think is enough. I am not seeing any use case for that parameter.
The parameter is also in other validators. The only one that I can see a use case is the regex validator. If there is a use case, please, clarify for me.
/// {@template has_lowercase_chars_template}
/// [HasLowercaseCharsValidator] extends [TranslatedValidator] to validate if a string
/// contains a specified minimum number of lowercase characters.
///
/// ## Parameters:
///
/// - [atLeast] The minimum number of lowercase characters required.
/// - [regex] The regular expression used to identify lowercase characters.
/// - [errorText] The error message returned if the validation fails.
///
/// {@macro lower_case_template}
/// {@endtemplate}
class HasLowercaseCharsValidator extends TranslatedValidator<String> {
/// Constructor for the lowercase characters validator.
HasLowercaseCharsValidator({
this.atLeast = 1,
/// {@macro lower_case_template}
RegExp? regex,
/// {@macro base_validator_error_text}
super.errorText,
/// {@macro base_validator_null_check}
super.checkNullOrEmpty,
}){...}
I want to know if the parameter
regexin some of the validators is really necessary. There is an example below of a validator that checks if the input has a minimum number of lowercase chars. It already has the default regex: RegExp('[a-z]') which I think is enough. I am not seeing any use case for that parameter.The parameter is also in other validators. The only one that I can see a use case is the regex validator. If there is a use case, please, clarify for me.
/// {@template has_lowercase_chars_template} /// [HasLowercaseCharsValidator] extends [TranslatedValidator] to validate if a string /// contains a specified minimum number of lowercase characters. /// /// ## Parameters: /// /// - [atLeast] The minimum number of lowercase characters required. /// - [regex] The regular expression used to identify lowercase characters. /// - [errorText] The error message returned if the validation fails. /// /// {@macro lower_case_template} /// {@endtemplate} class HasLowercaseCharsValidator extends TranslatedValidator<String> { /// Constructor for the lowercase characters validator. HasLowercaseCharsValidator({ this.atLeast = 1, /// {@macro lower_case_template} RegExp? regex, /// {@macro base_validator_error_text} super.errorText, /// {@macro base_validator_null_check} super.checkNullOrEmpty, }){...}
Is necesary in some cases. In some languages, the regex need to be increased. For example in spanish, that need to add ñ on regex (example).
There are some considerations about collection validators below:
- I moved the
containsElementvalidator from the 'collections' group to a new group that I called 'generic validators', which are validators that handle generic type T input, but are not necessarily collections. The problem is thatcontainsElementdoes not handle necessarily a collection input from the user. The "collection" part is only the reference list of elements that will be used internally by the validator to check if the user input (which is not necessarily a collection) is in that list. What do you think? - The same with the
uniqueValidator. It does not check something about a user input collection necessarily. What do you think? - Other than moving uniqueValidator from collections category to generic type category, I cannot find an use case for this validator. Maybe, a validator like: allowed/notAllowed, which checks if the user input is inside a list of allowed/not allowed elements would be better. What do you think?
- I think the range validator can be split into 2 validators: betweenLength and between (that already exists). The former will check if the length of the input collection is between a min and a max. The latter will do a numeric comparison. In my opinion, merging both in the same validator make it more confusing and I do not see a clear use case for that. What do you think?
Static vs dynamic values in validators
I will use the isEqual validator to show some issues that I found, but it is valid for other validators like: isGreaterThan, isNotEqual, etc.
This validator has the following signatures:
Current API: static FormFieldValidator<T> equal<T>(Object value, {String? errorText, bool checkNullOrEmpty = true,})
New API: Validator<T> isEqual<T extends Object?>(T? value, { T Function()? dynValue, String Function(String)? equalMsg,})
Both has a parameter value that will be used to compare with the user input.
The issue starts when we want to use a dynamic value as value. I created an example (example/lib/new_api_examples/static_vs_dynamic_value_example_main.dart) to illustrate that situation. I also recorded a video showing the situation:
https://github.com/user-attachments/assets/89f8d04a-82bc-4727-ba87-2e3497917689
Basically, what happens is that inside the FormBuilder from flutter_form_builder, the update for the error message is delayed by 1 frame. I don't know exactly what happens under the hood, but, as you can see in the example, when I increment the value used by the isEqual/equal validator, it updates properly when the text field is outside the FormBuilder, but not when it is inside it. The provisory solution that I found for that was to add a value parameter as a callback. Below is the implementation:
Validator<T> isEqual<T extends Object?>(
T? value, {
T Function()? dynValue, // This is the dynamic value
String Function(String)? equalMsg,
}) {
assert(
(value != null && dynValue == null) ||
(value == null && dynValue != null),
'Exactly one of "value" or "dynValue" must be provided');
return (T input) {
final T? currentValue = value ?? dynValue?.call();
final String valueString = currentValue!.toString();
return currentValue == input
? null
: equalMsg?.call(valueString) ??
FormBuilderLocalizations.current.equalErrorText(valueString);
};
}
In the example, I called isEqual (static) the one that uses the value parameter (does not work inside FormBuilder) and I called isEqual (dynamic) the one that uses the dynValue parameter (works inside the FormBuilder). Their code is illustrated below:
// The state variable 'referenceValue' is used by the validator isEqual/equal
class _HomePageState extends State<_HomePage> {
int referenceValue = 0;
//(...)
// The static example (does not work well inside FormBuilder)
FormBuilderTextField(
name: 'isEqual (static)',
autovalidateMode: AutovalidateMode.always,
decoration:
const InputDecoration(labelText: 'isEqual (static)'),
validator: isRequired(isInt(isEqual(referenceValue))),
),
//The dynamic example (works well inside FormBuilder)
FormBuilderTextField(
name: 'isEqual (dynamic)',
autovalidateMode: AutovalidateMode.always,
decoration:
const InputDecoration(labelText: 'isEqual (dynamic)'),
validator: isRequired(
isInt(isEqual(null, dynValue: () => referenceValue))),
),
// The current API (does not work well inside FormBuilder)
FormBuilderTextField(
name: 'isEqual (old API)',
autovalidateMode: AutovalidateMode.always,
decoration:
const InputDecoration(labelText: 'isEqual (old API)'),
validator: FormBuilderValidators.equal(
referenceValue.toString()),
),
Questions
- What do you think about this situation? Is this a bug or is there any solution to solve this issue?
- Will it be necessary to have the dynamic parameter for all the validators that depend on external dynamic values?
Complete code for the example
The complete code for the example is below:
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import '../localization/intl/app_localizations.dart';
final GlobalKey<FormBuilderState> _formKey = GlobalKey<FormBuilderState>();
void main() {
runApp(const _App());
}
class _App extends StatelessWidget {
const _App();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Static vs Dynamic - Example',
theme: ThemeData(primarySwatch: Colors.blue),
home: const _HomePage(),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
...GlobalMaterialLocalizations.delegates,
// Placed in front of `FormBuilderLocalizations.delegate`
...AppLocalizations.localizationsDelegates,
FormBuilderLocalizations.delegate,
],
);
}
}
class _HomePage extends StatefulWidget {
const _HomePage();
@override
State<_HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<_HomePage> {
int referenceValue = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Static vs Dynamic - Example')),
body: Padding(
padding: const EdgeInsets.all(8),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () => setState(() {
referenceValue--;
}),
icon: const Icon(Icons.remove)),
Text(referenceValue.toString()),
IconButton(
onPressed: () => setState(() {
referenceValue++;
}),
icon: const Icon(Icons.add)),
],
),
const SizedBox(height: 8),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
const Text('With flutter TextFormField:'),
TextFormField(
decoration:
const InputDecoration(labelText: 'isEqual (static)'),
autovalidateMode: AutovalidateMode.always,
validator: isRequired(isInt(isEqual(referenceValue))),
),
const SizedBox(height: 5),
TextFormField(
decoration:
const InputDecoration(labelText: 'isEqual (dynamic)'),
autovalidateMode: AutovalidateMode.always,
validator: isRequired(
isInt(isEqual(null, dynValue: () => referenceValue))),
),
const SizedBox(height: 5),
TextFormField(
decoration:
const InputDecoration(labelText: 'isEqual (old API)'),
autovalidateMode: AutovalidateMode.always,
validator: FormBuilderValidators.equal(
referenceValue.toString()),
),
const SizedBox(height: 8),
const Text(
'With flutter_form_builder outside a FormBuilder:'),
FormBuilderTextField(
name: 'isEqual (static)',
autovalidateMode: AutovalidateMode.always,
decoration:
const InputDecoration(labelText: 'isEqual (static)'),
validator: isRequired(isInt(isEqual(referenceValue))),
),
const SizedBox(height: 5),
FormBuilderTextField(
name: 'isEqual (dynamic)',
autovalidateMode: AutovalidateMode.always,
decoration:
const InputDecoration(labelText: 'isEqual (dynamic)'),
validator: isRequired(
isInt(isEqual(null, dynValue: () => referenceValue))),
),
const SizedBox(height: 5),
FormBuilderTextField(
name: 'isEqual (old API)',
autovalidateMode: AutovalidateMode.always,
decoration:
const InputDecoration(labelText: 'isEqual (old API)'),
validator: FormBuilderValidators.equal(
referenceValue.toString()),
),
const SizedBox(height: 8),
const Text(
'With flutter_form_builder inside a FormBuilder:'),
FormBuilder(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: Column(
children: <Widget>[
FormBuilderTextField(
name: 'isEqual (static)',
decoration: const InputDecoration(
labelText: 'isEqual (static)'),
validator:
isRequired(isInt(isEqual(referenceValue))),
),
const SizedBox(height: 5),
FormBuilderTextField(
name: 'isEqual (dynamic)',
decoration: const InputDecoration(
labelText: 'isEqual (dynamic)'),
validator: isRequired(isInt(
isEqual(null, dynValue: () => referenceValue))),
),
const SizedBox(height: 5),
FormBuilderTextField(
name: 'isEqual (old API)',
decoration: const InputDecoration(
labelText: 'isEqual (old API)'),
validator: FormBuilderValidators.equal(
referenceValue.toString()),
),
],
),
)
],
),
),
),
],
),
),
);
}
}
There are some considerations about collection validators below:
1. I moved the `containsElement` validator from the 'collections' group to a new group that I called 'generic validators', which are validators that handle generic type T input, but are not necessarily collections. The problem is that `containsElement` does not handle necessarily a collection input from the user. The "collection" part is only the reference list of elements that will be used internally by the validator to check if the user input (which is not necessarily a collection) is in that list. What do you think? 2. The same with the `uniqueValidator`. It does not check something about a user input collection necessarily. What do you think? 3. Other than moving uniqueValidator from collections category to generic type category, I cannot find an use case for this validator. Maybe, a validator like: allowed/notAllowed, which checks if the user input is inside a list of allowed/not allowed elements would be better. What do you think? 4. I think the range validator can be split into 2 validators: betweenLength and between (that already exists). The former will check if the length of the input collection is between a min and a max. The latter will do a numeric comparison. In my opinion, merging both in the same validator make it more confusing and I do not see a clear use case for that. What do you think?
- I think that this is a minor change and don't make a lot difference on DX.
- The same
- Maybe remove this validator
- OK. Make sense create and add to collection group
Static vs dynamic values in validators
I will use the isEqual validator to show some issues that I found, but it is valid for other validators like:
isGreaterThan,isNotEqual, etc.This validator has the following signatures: Current API:
static FormFieldValidator<T> equal<T>(Object value, {String? errorText, bool checkNullOrEmpty = true,})New API:Validator<T> isEqual<T extends Object?>(T? value, { T Function()? dynValue, String Function(String)? equalMsg,})Both has a parametervaluethat will be used to compare with the user input.The issue starts when we want to use a dynamic value as
value. I created an example (example/lib/new_api_examples/static_vs_dynamic_value_example_main.dart) to illustrate that situation. I also recorded a video showing the situation: demo.mp4Basically, what happens is that inside the
FormBuilderfrom flutter_form_builder, the update for the error message is delayed by 1 frame. I don't know exactly what happens under the hood, but, as you can see in the example, when I increment thevalueused by theisEqual/equalvalidator, it updates properly when the text field is outside theFormBuilder, but not when it is inside it. The provisory solution that I found for that was to add a value parameter as a callback. Below is the implementation:Validator<T> isEqual<T extends Object?>( T? value, { T Function()? dynValue, // This is the dynamic value String Function(String)? equalMsg, }) { assert( (value != null && dynValue == null) || (value == null && dynValue != null), 'Exactly one of "value" or "dynValue" must be provided'); return (T input) { final T? currentValue = value ?? dynValue?.call(); final String valueString = currentValue!.toString(); return currentValue == input ? null : equalMsg?.call(valueString) ?? FormBuilderLocalizations.current.equalErrorText(valueString); }; }In the example, I called isEqual (static) the one that uses the
valueparameter (does not work insideFormBuilder) and I called isEqual (dynamic) the one that uses thedynValueparameter (works inside theFormBuilder). Their code is illustrated below:// The state variable 'referenceValue' is used by the validator isEqual/equal class _HomePageState extends State<_HomePage> { int referenceValue = 0; //(...) // The static example (does not work well inside FormBuilder) FormBuilderTextField( name: 'isEqual (static)', autovalidateMode: AutovalidateMode.always, decoration: const InputDecoration(labelText: 'isEqual (static)'), validator: isRequired(isInt(isEqual(referenceValue))), ), //The dynamic example (works well inside FormBuilder) FormBuilderTextField( name: 'isEqual (dynamic)', autovalidateMode: AutovalidateMode.always, decoration: const InputDecoration(labelText: 'isEqual (dynamic)'), validator: isRequired( isInt(isEqual(null, dynValue: () => referenceValue))), ), // The current API (does not work well inside FormBuilder) FormBuilderTextField( name: 'isEqual (old API)', autovalidateMode: AutovalidateMode.always, decoration: const InputDecoration(labelText: 'isEqual (old API)'), validator: FormBuilderValidators.equal( referenceValue.toString()), ),Questions
* What do you think about this situation? Is this a bug or is there any solution to solve this issue? * Will it be necessary to have the dynamic parameter for all the validators that depend on external dynamic values?Complete code for the example
The complete code for the example is below:
import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import '../localization/intl/app_localizations.dart'; final GlobalKey<FormBuilderState> _formKey = GlobalKey<FormBuilderState>(); void main() { runApp(const _App()); } class _App extends StatelessWidget { const _App(); @override Widget build(BuildContext context) { return MaterialApp( title: 'Static vs Dynamic - Example', theme: ThemeData(primarySwatch: Colors.blue), home: const _HomePage(), supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: const <LocalizationsDelegate<dynamic>>[ ...GlobalMaterialLocalizations.delegates, // Placed in front of `FormBuilderLocalizations.delegate` ...AppLocalizations.localizationsDelegates, FormBuilderLocalizations.delegate, ], ); } } class _HomePage extends StatefulWidget { const _HomePage(); @override State<_HomePage> createState() => _HomePageState(); } class _HomePageState extends State<_HomePage> { int referenceValue = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Static vs Dynamic - Example')), body: Padding( padding: const EdgeInsets.all(8), child: Column( children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ IconButton( onPressed: () => setState(() { referenceValue--; }), icon: const Icon(Icons.remove)), Text(referenceValue.toString()), IconButton( onPressed: () => setState(() { referenceValue++; }), icon: const Icon(Icons.add)), ], ), const SizedBox(height: 8), Expanded( child: SingleChildScrollView( child: Column( children: [ const Text('With flutter TextFormField:'), TextFormField( decoration: const InputDecoration(labelText: 'isEqual (static)'), autovalidateMode: AutovalidateMode.always, validator: isRequired(isInt(isEqual(referenceValue))), ), const SizedBox(height: 5), TextFormField( decoration: const InputDecoration(labelText: 'isEqual (dynamic)'), autovalidateMode: AutovalidateMode.always, validator: isRequired( isInt(isEqual(null, dynValue: () => referenceValue))), ), const SizedBox(height: 5), TextFormField( decoration: const InputDecoration(labelText: 'isEqual (old API)'), autovalidateMode: AutovalidateMode.always, validator: FormBuilderValidators.equal( referenceValue.toString()), ), const SizedBox(height: 8), const Text( 'With flutter_form_builder outside a FormBuilder:'), FormBuilderTextField( name: 'isEqual (static)', autovalidateMode: AutovalidateMode.always, decoration: const InputDecoration(labelText: 'isEqual (static)'), validator: isRequired(isInt(isEqual(referenceValue))), ), const SizedBox(height: 5), FormBuilderTextField( name: 'isEqual (dynamic)', autovalidateMode: AutovalidateMode.always, decoration: const InputDecoration(labelText: 'isEqual (dynamic)'), validator: isRequired( isInt(isEqual(null, dynValue: () => referenceValue))), ), const SizedBox(height: 5), FormBuilderTextField( name: 'isEqual (old API)', autovalidateMode: AutovalidateMode.always, decoration: const InputDecoration(labelText: 'isEqual (old API)'), validator: FormBuilderValidators.equal( referenceValue.toString()), ), const SizedBox(height: 8), const Text( 'With flutter_form_builder inside a FormBuilder:'), FormBuilder( key: _formKey, autovalidateMode: AutovalidateMode.always, child: Column( children: <Widget>[ FormBuilderTextField( name: 'isEqual (static)', decoration: const InputDecoration( labelText: 'isEqual (static)'), validator: isRequired(isInt(isEqual(referenceValue))), ), const SizedBox(height: 5), FormBuilderTextField( name: 'isEqual (dynamic)', decoration: const InputDecoration( labelText: 'isEqual (dynamic)'), validator: isRequired(isInt( isEqual(null, dynValue: () => referenceValue))), ), const SizedBox(height: 5), FormBuilderTextField( name: 'isEqual (old API)', decoration: const InputDecoration( labelText: 'isEqual (old API)'), validator: FormBuilderValidators.equal( referenceValue.toString()), ), ], ), ) ], ), ), ), ], ), ), ); } }
I think that has a lot probability that the issue is inside FormBuilder. I will search some issues there to link here
So, I think that this is related with value on autoValidateMode: AutovalidateMode.always.
Maybe can try remove the AutovalidateMode.always and validate with _formKey.currentState?.saveAndValidate(). This should work if the problem is the autoValidateMode
I would continue with refactor and use the new approach isEqual (static), as it does not work within FormBuilder is the current behaviour so doesn't change anything
Ok, I will continue implementing the other validators that suffer from the same problem in the FormBuilder without using the dynamic value parameter. Given that it is probably a bug in FormBuilder. I will try to test the autoValidateMode approach too.
So, I think that this is related with value on autoValidateMode:
AutovalidateMode.always. Maybe can try remove theAutovalidateMode.alwaysand validate with_formKey.currentState?.saveAndValidate(). This should work if the problem is the autoValidateModeI would continue with refactor and use the new approach
isEqual (static), as it does not work within FormBuilder is the current behaviour so doesn't change anything
By the way, I cannot answer directly on your answers like we were doing in the previous threads. I don't know why.
I think that the changes that are being made reached a huge size. Can we push them to a branch to finish this PR and then open other PRs to continue the refactoring? This would make it easier for the review. I already implemented the main validators. OBS.: I should have asked that before, but I underrated the size of the changes.
@ArturAssisComp changed to a branch refactor_2024_11 when MR will ready for review, remove the draft state
I started to expose the new api functions. I noticed that 2 ways are being practiced by the current api:
- exposing them by using the aggregator class FormBuilderValidators. This avoids the identifier collision but adds the verbosity.
Example:
FormBuilderValidators.hasNumericChars - exposing directly the name of each validator class. This also does not avoid the verbosity because we need to use the suffix: "Validator.validate"
Example:
HasNumericCharsValidator.validate
I think of some possible options:
OP1. keep exposing the aggregator (I changed its name to "Validators" only to avoid collision with the current api, because some validators have the same name but do not have the same signature) and also expose the validators individually. Given that some of them may generate collisiion (and or or, for instance), the user would have the possibility to add the namespace himself.
Example: Import 'package...' as val or import 'package' as validators or import 'package' as v.
OP2. Expose only the aggregator.
OP3. (I do not find this options interesting) Expose only the functions
What do you think, @deandreamatias ?
@ArturAssisComp I think OP2 is better. Thinking in the future, there will be only one single API so we will avoid the collision
Ok, I will keep with op2.
Flutter Team add a validateGranually on version 3.22 https://github.com/flutter/flutter/pull/135578
I don't know if this can help in some way on this new API
I added some examples using this new feature (validateGranularly).
There are some things remaining:
- Translate the new localized messages
- Add more examples
- Add some validators (email, uuid, etc) and their tests I will do them if the current changes are approved.