How to validate a text field on value changed? - forms

How to autovalidate a text form field when its value changes?
i tried
bool _autoValidate = false;
TextFormField(
autovalidate: _autoValidate ,
onChanged: (value) {
setState(() {
_autoValidate = true;
});
},
validator: (value) {
if (value.length < 5) {
return 'False';
} else {
return 'True';
}
},
),
But not working, TextFormField still doesn't show errors on validation.
I need a way to turn on the validation on text changed.

Flutter has nows an API to validate a form field only when the content changes. You just need to use the autovalidateMode parameter and set it to AutovalidateMode.onUserInteraction.
The following text field will only validate when the user changes its content:
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
),
),
);
}
}
See AutovalidateMode docs for more options in when to validate.
This API is now available on the latest stable channel. Let me know if it solves your problem.

I think,
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
)
is the best way to solve this issue.

Related

Validation event for custom widget

In flutter, a TextFormField for an example, has a validator which can be used for validation:
TextFormField(validator: validator ...
Then in your form, you can call validate on the Form widget to make all children with these validators validate:
_formKey.currentState!.validate()
If a validation fails, the TextFormField will display an error text along with a position and color transition.
I have my own custom photo widget, and I would like to make it able to support the same validation functionality. That is, give it a validator, hook it up to the validate() event, and if the user hasn´t added any photo, the validation fails and shows the error text the validator returns.
But I cannot figure out how to implement the validate eventlistener on a custom widget. So how would you go around this?
Update:
#user18309290 pointed me in the direction of extending my widget from FormField. But the problem is that my widget has internal functions and properties I need to access in the instance/layout tree. But I can´t figure out the right way to do it. I could put all the stuff in the build method, but that means that all of my "heavy" logic and properties would be reinstantiated every time the widget rebuilds if I understand correctly. So how do I extend from FormField to have validation support (validation fails if image list is empty), but still have access to my methods and properties?
This is my simplified widget:
class MyPhotoComponent extends FormField<List<File>> {
late String title;
List<File> images = [];
openCamera() {
print('This and other methods, has alot of logic');
images.add(File('filepath'));
}
String internalTitle = 'Internal title';
MyPhotoComponent({required String title, required FormFieldSetter<List<File>> onSaved, FormFieldValidator<List<File>>? validator, required List<File> initialValue, Key? key})
: super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
key: key,
builder: (FormFieldState<List<File>> state) {
return Column(
children: [
Builder(builder: (BuildContext context) {
return Column(
children: [
Text(internalTitle), //Error: The instance member 'internalTitle' can't be accessed in an initializer.
MyOtherPhotoGalleryComponent(images: images), //Error: The instance member 'images' can't be accessed in an initializer.
ElevatedButton.icon(
onPressed: openCamera, //Error: The instance member 'openCamera' can't be accessed in an initializer.
icon: Icon(Icons.add_a_photo),
label: Text('Take photo'),
),
],
);
}),
if (state.hasError) Builder(builder: (BuildContext context) => Text('Validation error'))
],
);
},
);
}
Inherit a custom widget from FormField. Each individual form field should be wrapped in a FormField widget like TextFormField.
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
CustomFormField(
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Please select something';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {}
},
child: const Text('Submit'),
),
],
),
);
There are three ways to accomplish this
State management which will be complicated for this scenario
SetState() which will update your whole UI and will be expensive
ValueNotifier and ValueListableBuilder which I recommend
First define a valuenotifier
late ValueNotifier<bool> _isValid;
Then in initState initialize it and add a listener to it which will be your validator
...
_isValid = ValueNotifier(true);
_isValid.addListener((){
If(){ //your validation
_isValid.value = true;
} else {
_isValid.value = false;
}
})
Then in your UI add ValueListableBuilder and put your widget inside it to listen to its changes and change accordingly
ValueListableBuilder(
listenableValue: _isValid,
builder: (context, bool yourValue, child){
// return your widget and use yourValue to update your UI
}
)
Sorry if there is any misspelling. I wrote it on my mobile

Flutter,I want my textfield with focus but hiding keyboard?

I need to scan data through a physical device using flutter textfield, but I don't have a solution yet I have gone through many and nothing works for me, please someone helps with this.
TextFormField(
focusNode: loginTextfieldFocus,
style: const TextStyle(fontSize: 15),
controller: loginTextController,
decoration: decoration(context, loginTextController),
onChanged: (val) {
if (val.contains('\n') || val.contains('\r')) {
Log.d("LoginTextfield Entered");
}
},
),
This is not the proper way to do , but somehow i managed to do it.
TextFormField(
autofocus: true,
enabled: isEditable,
showCursor: isEditable,
readOnly: true,
keyboardType: TextInputType.none,
focusNode: textfieldFocus,
controller: textfieldCtr
),
//Called this under widget build
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback((Duration _) {
FocusScope.of(context).requestFocus( textfieldFocus);
});
To receive scan data from device i used RawKeyboardListener. For scanning you can refer it here (flutter Enter event not found on zebra tc77?)
Yes, I have the same issue. I have solve by changing onChanged to validator because onChange is keep reloading your state thatswhy your keyboard is automatically dismiss every time.
validator: (value) {
if (value != null && value.trim().length < 10) {
return 'This field requires a minimum of 10 characters';
}
return null;
},
Try this
Widget build(BuildContext context) {
Future.delayed(const Duration(), () => SystemChannels.textInput.invokeMethod('TextInput.hide'));
return Scaffold(
body: TextField(
autofocus: true,
),
);
}

Best way to validate a form with flutter provider package?

I need separate logic with UI.
I used the following example:
1.- Use a class validation item to show a string value and error.
class ValidationItem {
final String value;
final String error;
ValidationItem(this.value, this.error);
}
2.- Use the next code for provider class.
class SignupValidation with ChangeNotifier {
ValidationItem _firstName = ValidationItem(null,null);
//Getters
ValidationItem get firstName => _firstName;
bool get isValid {
if (_firstName.value != null){
return true;
} else {
return false;
}
}
//Setters
void changeFirstName(String value){
if (value.length >= 3){
_firstName=ValidationItem(value,null);
} else {
_firstName=ValidationItem(null, "Must be at least 3 characters");
}
notifyListeners();
}
void submitData(){
print("FirstName: ${firstName.value}");
}
}
3.- Use the next widget to show text field and validate
class Signup extends StatelessWidget {
#override
Widget build(BuildContext context) {
final validationService = Provider.of<SignupValidation>(context);
return Scaffold(
appBar: AppBar(
title: Text('Signup'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
TextField(
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
onChanged: (String value) {
validationService.changeFirstName(value);
},
),
RaisedButton(
child: Text('Submit'),
onPressed: (!validationService.isValid) ? null : validationService.submitData,
)
],
),
),
);
}
}
The problem is the performance for example every time the text is changed the notifyListener() Is calles.
My question: Is there a cost to performance?
you can use TextFormField instead of TextField.
Best way to validate fields is that you can use validator property TextFormField property as bellow
TextFormField(
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
validator: (e){
if(e!.trim().isEmpty) return "String is empty";
return null;
},
onChanged: (String value) {
validationService.changeFirstName(value);
},
),
The TextField itself gives you the ability to validate the form, then why to make it complex by implementing notifier, instead you want to make it common you can make the validater global function for it. Nad user it in the validate function.
Void validateEmail(String value){ // your logic}
Use this function as follow
TextFormField(
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
validator: validateEmail(),
onChanged: (String value) {
validationService.changeFirstName(value);
},
Secondly to get the value of inputted string you have a TextEditingController which directly give you the string you inputted.
Declare TextEditingController as follow
TextEditingController emailCont = TextEditingController();
Use this controller in TextField as follow
TextFormField(
controller: emailCont,
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
validator: validateEmail(),
onChanged: (String value) {
validationService.changeFirstName(value);
},
Now to get the value from this controller you can get it this way.
emailCont.text
This way it will be easy to manage and less complexity.

FLUSHBAR: anyone familiar with this package, how to create a form within?

maybe I am overstressing this package, but the last example shows a form. However, my implementation shows no sign of life. Not even an error or warning.
My approach is a little different. I want to create a form with multiple, different input widgets (text, button, selector) and then return those with a bloc event. The returning/saving part is not yet implemented.
If someone has implemented a form with flushbar, I would appreciate any advice.
This is my flushbar code
Widget otherActionsSlider() {
bool paused;
int priority;
bool supply;
return Flushbar(
userInputForm : Form(
key: _formKey,
child: Container(
child: Column(
children: <Widget> [
Text("progress"),
_DelayStatusFormField(
onSaved: (value) => this.delayStatus = value,
),
Row(
children: [
_DelayFormField(
onSaved: (value) => this.delay = value,
enabled: this.delayStatus == DelayStatus.unrecoverable ? true : false
)
],
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (this._formKey.currentState.validate()) {
setState(() {
this._formKey.currentState.save();
});
}
},
),
],),)),
);
}
and the form field classes
class _DelayStatusFormField extends FormField<DelayStatus> {
_DelayStatusFormField({
FormFieldSetter<DelayStatus> onSaved,
FormFieldValidator<DelayStatus> validator,
DelayStatus initialValue,
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<DelayStatus> state) {
return
CupertinoSlidingSegmentedControl(
groupValue: initialValue == DelayStatus.recoverable ? 1 :
initialValue == DelayStatus.unrecoverable ? 2 : 0,
children: {
0: Text('on time'),
1: Text('recoverable'),
2: Text('unrecoverable')
},
onValueChanged: (int val) => state.didChange(val == 0 ? DelayStatus.onTime :
val == 1 ? DelayStatus.recoverable :
DelayStatus.unrecoverable)
);
}
);
}
class _DelayFormField extends FormField<int> {
_DelayFormField({
FormFieldSetter<int> onSaved,
FormFieldValidator<int> validator,
int initialValue,
bool enabled
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<int> state) {
TextEditingController inputTec = TextEditingController(text: initialValue.toString());
return
CupertinoTextField(
controller: inputTec,
placeholder: 'delay in days',
enabled: enabled
);
}
);
}
While I haven't yet fully implemented my solution, I finally received some signs of life. My issue was, the function returning the Flushbar has to be of type Flushbar<List<String>> given the example of the package readme. I returned Widget instead.
If someone tries to work with this example, there quite a few brackets and more importantly, it is not (as written in the readme) Flushbar(userInputForm = Form( but Flushbar(userInputForm: Form(
Though the quality of the example given leaves room for improvement, the widget itself is quite nice, especially when implementing more simple use cases (I have a CupertinoApp, so I cannot use snackbar)

How to reset error message in TextFormField?

I know this is a simple question but i did not find how to do this. How can i reset error message in TextFormField when the user input something in the field. Something like onchange listener. I notice onChanged is available in TextField but not TextFormField. How can i do this?
final _email = Container(
child: TextFormField(
decoration: InputDecoration(labelText: email),
keyboardType: TextInputType.emailAddress,
controller: emailController,
validator: _validateEmail,
onSaved: (input) => _stringEmail = input.toLowerCase().trim(),
),
);
UPDATE: I am using controller to add listener for user input. But i just want to reset the error message instead of validating the form again. Is this possible? How can i do this?
_resetEmailErrorMessage() {
print("Second text field: ${emailController.text}");
//replace clear to something that can reset the validation error message
emailController.clear();
}
#override
void initState() {
super.initState();
//start listening to changes
emailController.addListener(_resetEmailErrorMessage);
}
You can use auto-validation feature of Form
If you looking for this solution,
"As soon as you type it validates your input and show/hide error"
Flutter provides auto-validation feature, you just need to enable it at form level.
Default
_autoValidate = false;
Form
body: Form( key: _formKey,
autovalidate: _autoValidate,....)
Enable it when user presses submit once,
if (_formKey.currentState.validate()) {
// All Good
} else {
// start auto validate
setState(() {
_autoValidate = true;
});
}
Update :-
Now Form.autovalidate is deprecated. So Use
Form.autovalidateMode = AutovalidateMode.onUserInteraction
Form.autovalidate has been deprecated. Use Form.autovalidateMode = AutovalidateMode.onUserInteraction to have the errors automatically clear once a user begins to interact with the form field again without having to manage any state yourself.
The TextFormField also has the option of onChanged in that you can try to reset the form validation through the FormKey
final _email = Container(
child: TextFormField(
decoration: InputDecoration(labelText: email),
keyboardType: TextInputType.emailAddress,
controller: emailController,
validator: _validateEmail,
onChanged: (value){
formKey.currentState.reset();
},
onSaved: (input) => _stringEmail =
input.toLowerCase().trim(),
),
);
// Somewhere in `State` declaration
final formKey = GlobalKey<FormState>();
bool autovalidate = false;
#override
void build(BuildContext context) {
return Form(
key: formKey,
autovalidate: autovalidate,
child: Column(
children: [
TextFormField(
...
validator: (value) {
return (value == null || value.isEmpty) ? 'Cannot be empty' : null;
}
onChanged: (value) {
// Call this to refresh autovalidation result
setState(() {});
}
),
],
),
);
}
void validate() {
if(formKey.currentState.validate()) {
// data is valid!
return true;
} elsee {
// Activate autovalidation
autovalidate = true;
}
return false;
}
I would suggest to use the controller and listen to the User input . and whenever he types something you can validate() your form again
I don't see any answers to your question. I also ran into a situation like this I wanted the fields of the authentication form to reset their validation error once the user changes to Login or Signup. So I added this logic:
class _AuthFormState extends State<AuthForm> {
...
var _formFlipped = false; // THIS RIGHT HERE
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
...
}
#override
Widget build(BuildContext context) {
return
...
child: Form(
...
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (_formFlipped) return null; // THIS
if (value.isEmpty || !value.contains('#')) {
return 'Enter a valid email';
}
return null;
},
...
RaisedButton(
child: Text(_isLogin ? 'Login' : 'Signup'),
onPressed: () {
setState(() {
_formFlipped = false; // AND THIS
});
_trySubmit();
},
),
FlatButton(
child: Text(_isLogin
? 'Create new account'
: 'I already have an account'),
onPressed: () { // THIS TOO
setState(() {
_isLogin = !_isLogin;
_formFlipped = true;
_formKey.currentState.validate();
});
},
),
...
Before & After.
Edit:
So basically you just force pass null on all your field validators to clear the message. Alternatively as Yogesh pointed out you can achieve this by simply _formKey.currentState.reset();
But if you need more granular control, this method serves you good since you can set if (_formFlipped) return null; or similar condition to different fields according to your need.
This one worked form. You can try to validate form like this:
_formKey.currentState.validate()
You need to define validate field in your TextFormField.