https://www.qyh.me/forme3/index.html
flutter pub add forme
flutter pub add forme_base_fields
FormeKey key = FormeKey();// formekey is a global key , also used to control form
Widget child = FormeTextField(name:'username',decoration:const InputDecoration(labelText:'Username'));
Widget forme = Forme(
key:key,
child:child,
)
Attribute | Required | Type | Description |
---|---|---|---|
key | false | FormeKey |
a global key, also used to control form |
child | true | Widget |
form content widget |
onFieldStatusChanged | false | FormeFieldStatusChanged |
listen form field's status change |
initialValue | false | Map<String,dynamic> |
initialValue , will override FormField's initialValue |
onWillPop | false | WillPopCallback |
Signature for a callback that verifies that it's OK to call Navigator.pop |
quietlyValidate | false | bool |
if this attribute is true , will not display error text |
autovalidateMode | false | AutovalidateMode |
auto validate form mode |
autovalidateByOrder | false | bool |
whether auto validate form by order |
onFieldsRegistered | false | function |
listen registered fields |
onFieldsUnregistered | false | function |
listen unregistered fields |
onInitialized | false | function |
typically used to register visitors |
Attribute | Required | Type | Description |
---|---|---|---|
name | false | String |
field's id,should be unique in form |
readOnly | false | bool |
whether field should be readOnly,default is false |
builder | true | FormeFieldBuilder |
build field content |
enabled | false | bool |
whether field is enabled or not default |
quietlyValidate | true | bool |
false will not display error text |
initialValue | true | T |
default value of field , will overwritten by Forme initialValue |
asyncValidatorDebounce | false | Duration |
async validate debounce , default is 500ms |
autovalidateMode | false | AutovalidateMode |
autovalidate mode |
onStatusChanged | false | FormeFieldStatusChanged |
listen value |
onInitialized | false | FormeFieldInitialized |
typically used to register visitors |
onSaved | false | FormeFieldSetter |
triggered when form saved |
validator | false | FormeValidator |
sync validator |
asyncValidator | false | FormeAsyncValidator |
async validator |
decorator | false | FormeFieldDecorator |
used to decorate a field |
order | false | int |
order of field |
requestFocusOnUserInteraction | false | bool |
whether request focus when field value changed by user interaction |
validationFilter | false | function |
used to determine whether perform a validation or not |
focusNode | false | FocusNode |
FocusNode |
you can specify an asyncValidator
on FormeField
, the unique difference
between validator
and asyncValidator
is asyncValidator
return a Future<String>
and validator
return a String
if FormeField.autovalidateMode
is AutovalidateMode.disabled
, asyncValidator will never be performed unless you call validate
on FormeFieldState
manually.
if you specify both validator
and asyncValidator
, asyncValidator
will only be performed after validator
return null.
if validationFilter
is specified and not passed. asyncValidator
will not be performed
you can specify a debounce on FormeField
, debounce will not worked when you manually call validate
on FormeFieldState
in some cases,when an async validation is performing , another validation on same field is performed,in this case ,previous validation is invalid , so if you want to update UI before return validation result in async validator , you need to validate it first,eg:
asyncValidator:(field,value,isValid){
return Future.delayed(const Duration(seconds:2),(){
if(isUnexceptedValue(value)) {
if(isValid()){
updateUI();
}
return 'invalid';
}
return null;
});
}
you can use FormeValidates
to simplify your validators
Validator Name | Support Type | When Valid | When Invalid |
---|---|---|---|
notNull |
dynamic |
value is not null | value is null |
size |
Iterable Map String |
1. value is null 2. max & min is null 3. String's length or Collection's size is in [min,max] | String's length or Collection's size is not in [min,max] |
min |
num |
1. value is null 2. value is bigger than min | value is smaller than min |
max |
num |
1. value is null 2. value is smaller than max | value is bigger than max |
notEmpty |
Iterable Map String |
1. value is not null 2. String's length or Collection's size is bigger than zero | 1. value is null 2. String's length or Collection's size is zero |
notBlank |
String |
1. value is null 2. value.trim()'s length is not null | value'length is zero after trimed |
positive |
num |
1. value is null 2. value is bigger than zero | value is smaller than or equals zero |
positiveOrZero |
num |
1. value is null 2. value is bigger than or equals zero | value is smaller than zero |
negative |
num |
1. value null 2. value is smaller than zero | value is bigger than or equals zero |
negativeOrZero |
num |
1. value null 2. value is smaller than or equals zero | value is bigger than zero |
pattern |
String |
1. value null 2. value matches pattern | value does not matches pattern |
email |
String |
1. value null 2. value is a valid email | value is not a valid email |
url |
String |
1. value is null 2. value is empty or value is a valid url | value is not a valid url |
range |
num |
1. value null 2. value is in range | value is out of range |
equals |
dynamic |
1. value null 2. value is equals target value | value is not equals target value |
any |
T |
any validators is valid | every validators is invalid |
all |
T |
all validators is valid | any validators is invalid |
when you use validators from FormeValidates
, you must specify at least one errorText , otherwise errorText is an empty string
bool hasField = formeKey.hasField(String name);
T field = formeKey.field<T extends FormeFieldState>(String name);
Map<String, dynamic> data = formeKey.value;
formeKey.value = Map<String,dynamic> value;
you can use FormeValidateSnapshot.isValueChanged
to check whether form value is changed duration this validation ,
if is changed , typically means this validation is invalid , you should not submit your form even though validation is passed
Future<FormeValidateSnapshot> future = formKey.validate({
bool quietly = false,
Set<String> names = const {},
bool clearError = false,
bool validateByOrder = false,
});
FormeValidation validation = formKey.validation;
formeKey.reset();
formeKey.save();
bool quietlyValidate = formKey.quietlyValidate;
bool isValueChanged = formeKey.isValueChanged
List<FormeFieldState> fields = formeKey.fields;
formeKey.addVisitor(FormeVisitor visitor);
formeKey.removeVisitor(FormeVisitor visitor);
rebuild all widgets in Forme
formeKey.rebuildForm();
String name = field.name
bool readOnly = field.readOnly;
field.readOnly = bool readOnly;
bool enabled = field.enabled;
field.enabled = bool enabled;
bool hasFocusNode = field.hasFocusNode
FocusNode focusNode = field.focusNode;
bool hasFocus = field.hasFocus;
field.requestFocus();
field.unfocus();
T value = field.value;
field.value = T data;
field.reset();
Future<FormeFieldValidateSnapshot> future = field.validate({bool quietly = false});
FormeFieldValidation validation = field.validation;
if value changed , you can use this method to get previous value
FormeOptional<T>? value = field.oldValue;
if value is null , means field's value is never changed before
bool isValueChanged = field.isValueChanged
Type type = field.type;
bool isNullable = field.isNullable;
FormeState? form = field.form;
FormeFieldStatus<T> get status = field.status;
whether current validation is set via errorText
bool isCustomValidation = field.isCustomValidation;
typically used in display.
if FormeField.quietlyValidate
or Forme.quietlyValidate
is true , you'll always get null
String? errorText = field.errorText;
if errorText
is null , reset validation.
field will rebuild after this method called. if field has validators , a new validation maybe performed , in this case ,custom validation will be overwritten by new validation. use FormeField.validationFilter
to avoid this
will not worked on disabled fields
field.errorText = 'custom error';
field.addVisitor(FormeFieldVisitor visitor);
field.removeVisitor(FormeFieldVisitor visitor);
listeners is helpful when you build widgets which depends on status of FormeField
or Forme
, you must used them inside in Forme
or FormeField
will rebuild whenever field's status changed , use filter to avoid unnecessary rebuild.
eg:
Forme(
child:Column(children:[
FormeFieldStatusListener(
filter:(status) => status.isValueChanged,/// if you only want to rebuild when value changed
name:'name',
builder:(context,status,child){
return Text('current value:${status.value}')
}
),
FormeTextField(name:'name'),
]),
)
will rebuild whenever validation of any field changed
eg:
FormeFieldsValidationListener(
names: const {'password', 'confirm'},
builder: (context, validation) {
if (validation == null) {
return const SizedBox();
}
if (validation.isInvalid) {
return Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
validation.validations.values
.where((element) => element.isInvalid)
.first
.error!,
style: _getErrorStyle(),
),
);
}
return const SizedBox.shrink();
},
),
will rebuild whenever form validation changed , useful when you want to create a submit button which only clickable when form validation passed
eg:
Forme(
child:Column(children:[
...
FormeValidationListener(
builder:(context,validation,child){
return TextButton(
onPressed:validation.isValid ? submit:null,
child:const Text('submit'),
);
}
)
]),
)
will rebuild whenever form value changed , useful when you want to create a reset button which depends on form value changed or not
eg:
Forme(
child:Column(children:[
...
FormeIsValueChangedListener(
builder:(context,isValueChanged,child){
return TextButton(
onPressed:isValueChanged ? reset:null,
child:const Text('reset'),
);
}
)
]),
)
will rebuild whenever form value changed , typically used in debug .
eg:
Forme(
child:Column(children:[
...
FormeValueListener(
builder:(context,value,child){
return Text(value.toString());
}
)
]),
)
FormeField<String>(
name: 'customField',
initialValue: 'currentValue',
builder: (FormeFieldState<String> state) {
return TextButton(
onPressed: () {
state.readOnly? null: state.didChange('newValue');
},
child: Text(state.value),
);
},
),