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.
Related
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.
I have a TextField like this. The additional code is necessary to show that in different situations, I do various focus manipulation.
final node = FocusScope.of(context);
Function cleanInput = () => {controller.text = controller.text.trim()};
Function onEditingComplete;
Function onSubmitted
TextInputAction textInputAction;
if (!isLast) {
onEditingComplete = () => {
cleanInput(),
node.nextFocus(),
};
onSubmitted = (_) => {cleanInput()};
textInputAction = TextInputAction.next;
} else {
onEditingComplete = () => {
cleanInput(),
};
onSubmitted = (_) => {
cleanInput(),
node.unfocus(),
};
textInputAction = TextInputAction.done;
}
Widget textInput = TextField(
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
As you can see, I have functions I want to run onEditingComplete. However, this only gets called when I press the Next or Done buttons on my keyboard (or the Enter key in an emulator). If I change focus by tapping on a different field, this function does not get called.
I have tried using a Focus or FocusNode to help with this, but when I do so, the onEditingComplete function itself no longer works.
How can I get the desired effect here while everything plays nicely together?
Focus widget
Wrapping fields in a Focus widget might do the trick.
The Focus widget will capture focus loss events for children. With its onFocusChange argument you can call arbitrary functions.
Meanwhile, the onEditingComplete argument of TextField is unaffected and will still be called on the software keyboard "Next/Done" keypress.
This should handle field focus loss for both "Next/Done" keypress and user tapping on another field.
import 'package:flutter/material.dart';
class TextFieldFocusPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// ↓ Add this wrapper
Focus(
child: TextField(
autofocus: true,
decoration: InputDecoration(
labelText: 'Name'
),
textInputAction: TextInputAction.next,
// ↓ Handle focus change on Next / Done soft keyboard keys
onEditingComplete: () {
print('Name editing complete');
FocusScope.of(context).nextFocus();
},
),
canRequestFocus: false,
// ↓ Focus widget handler e.g. user taps elsewhere
onFocusChange: (hasFocus) {
hasFocus ? print('Name GAINED focus') : print('Name LOST focus');
},
),
TextField(
decoration: InputDecoration(
labelText: 'Password'
),
),
],
),
),
),
);
}
}
Please add a focus node to your textfield and add a listener to your focus node to trigger when it unfocuses
final node = FocusScope.of(context);
node.addListener(_handleFocusChange);
void _handleFocusChange() {
if (node.hasFocus != _focused) {
setState(() {
_focused = node.hasFocus;
});
}
}
Widget textInput = TextField(
//you missed this line of code
focusNode: node,
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
And also you can validete automatically by adding autoValidate to your code like below:
Widget textInput = TextField(
//add this line of code to auto validate
autoValidate: true,
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
FocusNode _node;
bool _focused = false;
#override
void initState() {
super.initState();
_node.addListener(_handleFocusChange);
}
void _handleFocusChange() {
if (_node.hasFocus != _focused) {
setState(() {
_focused = _node.hasFocus;
});
}
}
#override
void dispose() {
_node.removeListener(_handleFocusChange);
_node.dispose();
super.dispose();
}
TextFormField(
focusNode: _node)
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.
I have a PageView having 4 children/pages/steps.
On the first page I have a TextFormField. The user is obligated to provide a value.
When following exactly these steps, wrongly it will result in an error message for the user 'Please provide a name':
page is created
type a value into the TextFormField
Tap 'Done' on the soft keyboard
Tap the Next-button to go to page 2 of the PageView
Tap the Back-button to go to page 1 of the PageView
Tap the Next-button to go to page 2 of the PageView: now 'Please provide a name' is shown to the user. The typed name is still visible on the page, but its value in its validator is empty.
Deviation from the above will not cause the error message. Relevant code:
class CreateWishlist extends StatefulWidget {
#override
_CreateWishlistState createState() => _CreateWishlistState();
}
class _CreateWishlistState extends State<CreateWishlist> {
...
final myTitleController = TextEditingController();
final _form1 = GlobalKey<FormState>();
Form(
key: _form1,
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(hintText: 'Name'),
textInputAction: TextInputAction.done,
onSaved: (value) {
_createWishlistData['title'] = value.trim();
},
controller: myTitleController,
validator: (value) {
if (value.trim().isEmpty) return 'Please provide a name';
return null;
},
),
],
),
),
next(int pageIndex) {
if (pageIndex == 0) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
if (!_form1.currentState.validate()) {
return;
}
_form1.currentState.save();
}
controller.animateToPage(pageIndex + 1,
duration: Duration(seconds: 1), curve: Curves.ease);
}
I tried as described in the comments above, but still the same. Eventually I managed to avoid the problem by overwriting the value on the validator and on onSaved by the controller-value.
TextFormField(
style: formFieldTextStyle,
decoration: InputDecoration(hintText: 'Name'),
textInputAction: TextInputAction.done,
onSaved: (value) {
value = myTitleController.text;
_createWishlistData['title'] = value.trim();
},
controller: myTitleController,
validator: (value) {
value = myTitleController.text;
if (value.trim().isEmpty) return 'Please provide a name';
return null;
},
),
Hi im new to flutter.
Im using TextFormField widget to validate the input if the textformfield is empty and wrapping it with GlobalKey Form widget.
Can i ask if its possible just only check atleast one field is not empty then its valid.
TextField A = empty & TextField B = not empty :: valid
TextField A = not empty & TextField B = empty :: valid
TextField A = empty & TextField B = empty :: not valid
This is the situation there are two textformfield A and textformfield B atleast one must not be empty so it could be A or B. But if both is empty then the user must fill one textfield. My objective is all my textformfield has a validation but its okay if atleast one is filled or not empty.
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(children: [
TextFormField(validator: (value) {
if (value.isEmpty) {
return "Please Fill";
}
}),
TextFormField(validator: (value) {
if (value.isEmpty) {
return "Please Fill";
}
}),
RaisedButton(
child: Text("Submit"),
onPressed: () async {
if (_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
//Some Codes
},
)
]),
),
I was planing to change it to TextField widget and use setState({}) to check if atleast 1 got filled but i dont want to use setState. Is there a way to solve my problem?. Thanks
Try this:
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(children: [
TextFormField(validator: (value) {
if (value.isEmpty) {
return "Please Fill";
}
return null;
}),
TextFormField(validator: (value) {
if (value.isEmpty) {
return "Please Fill";
}
return null;
}),
RaisedButton(
child: Text("Submit"),
onPressed: () async {
if (_formKey.currentState.validate()) {
// If the form is valid, display a Snackbar.
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Processing Data')));
}
_formKey.currentState.save();
//Some Codes
},
)
]),
),