String? _validatename(String value) {
final _nameExp = RegExp(r'^[A-Za-z]+$');
if (value.isEmpty) {
return 'This field is required';
} else if (!_nameExp.hasMatch(value)) {
return 'Only arithmetical characters allowed';
} else {
return null;
}
}
I tried to add validator to TextFormField to check validity of data, when ElevatedButton is pressed. I think i did everything alright, but returns an issue.
Terminal says it's because _namecontroller does not match to validator parameters
The argument type 'String? Function(String)' can't be assigned to the
parameter type 'String? Function(String?)?'.
If i try to delete ? sign in String?, it returns another issue, so i don't know what to do
class _RegisterFormPageState extends State<RegisterFormPage> {
final _namecontroller = TextEditingController();
final _formkey = GlobalKey<FormState>();
void dispose() {
_namecontroller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formkey,
child: ListView(
padding: EdgeInsets.all(16.0),
children: [
SizedBox(height: 20),
TextFormField(
controller: _namecontroller,
validator: _validatename, // issue
),
Padding(
padding: const EdgeInsets.all(20.0),
child: ElevatedButton(
onPressed: _submitform,
child: Text('Submit')),
)
],
),
),
);
}
void _submitform() {
if (_formkey.currentState!.validate()) {
print('Name: ${_namecontroller.text}');
}
}
The validator callback provides nullable string,
Change it like
String? _validatename(String? value) {
String? _validatename(String? value) {
final _nameExp = RegExp(r'^[A-Za-z]+$');
if (value == null) {
return "got null";
} else if (value.isEmpty) {
return 'This field is required';
} else if (!_nameExp.hasMatch(value)) {
return 'Only arithmetical characters allowed';
} else {
return null;
}
}
More about TextFormField
I've two TextFormFields, it focus on the password field on validation error, even if email field has error already & comes before password field.
Any idea what's going wrong here?
//Email
TextFormField(
controller: _emailController,
focusNode: _emailFocus,
validator: (value) {
String? err = validateEmail(value);
if (err != null) {
_emailFocus.requestFocus();
}
return err;
},
),
//Password
TextFormField(
controller: _passwordController,
focusNode: _passwordFocus,
validator: (value) {
String? err = validatePassword(value);
if (err != null) {
_passwordFocus.requestFocus();
}
return err;
},
),
String? validateEmail(String? value) {
String pattern = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]"
r"{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]"
r"{0,253}[a-zA-Z0-9])?)*$";
RegExp regex = RegExp(pattern);
if (value == null || value.isEmpty || !regex.hasMatch(value)) {
return 'Enter a valid email address';
} else {
return null;
}
}
String? validatePassword(String? value) {
String pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$!%*?&])[A-Za-z\d#$!%*?&]{8,}$";
RegExp regex = RegExp(pattern);
if (value == null || value.isEmpty) {
return 'Required';
}
if (value.length < 8) {
return "Length should be 8 or more";
}
if (!regex.hasMatch(value)) {
return "Must contain atleast 1 uppecase, 1 lowercase, 1 special character,";
}
return null;
}
Ignore this silly paragraph:(This is just bunch of text, to tell SO that I have added more question details even if it is NOT required and NOT available)
Wrap it with a form widget and validate it only on a button click like the following.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
const appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
),
body: const MyCustomForm(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({super.key});
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: const Text('Submit'),
),
),
],
),
);
}
}
Check this for a detailed explanation
https://docs.flutter.dev/cookbook/forms/validation
Edit
Please remove focus request if it's null. That will always keep the focus on password field if both are null
Problem
You have 2 validators and the last one will work the last. That means if your both TextFormField is not valid the last one always works last and your focus goes to the last one.
Solution
check the other focusNode inside of another and focus password area if email focusNode has not focused like below
//Password
TextFormField(
controller: _passwordController,
focusNode: _passwordFocus,
validator: (value) {
String? err = validatePassword(value);
if (err != null) {
if(!_emailFocus.hasFocus){
_passwordFocus.requestFocus();
}
}
return err;
},
),
I am building a Flutter application with multiple TextFormFields. I create a reusable TextFormfield Widget to keep things modular. The problem is, when the submit button is clicked, even though the Text Form Fields are not valid, it runs like it is valid.
My TextFormField widget:
class AndroidTextField extends StatelessWidget {
final ValueChanged<String> onChanged;
final String Function(String?)? validator;
const AndroidTextField(
{Key? key,
required this.onChanged,
this.validator})
: super(key: key);
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
width: size.width * .9,
child: TextFormField(
validator: validator,
onChanged: onChanged,
);
}
}
How I use it in the Scaffold
AndroidTextField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter a valid name';
}
return '';
},
onChanged: (val) {
setState(() {
lastName = val;
});
}),
The form
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
AndroidTextField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter a valid name';
}
return '';
},
onChanged: (val) {
setState(() {
firstName = val;
});
}),
TextButton(
child: Text('Press),
onPressed:(){
if (_formKey.currentState!.validate()){
//do something
}else{
//don't do the something
}
}
));
}
I feel a flutter validator should return null if valid, not an empty String.
So the code should be:
AndroidTextField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter a valid name';
}
return null;
}
...
Also, try:
final String? Function(String?) validator;
instead of
final String Function(String?)? validator;
I have 1 Form with 2 TextFormField, firstName and lastName.
I need to validate this two data with condition and regex, then pass data to the second page with Navigator.
Problem is, i want lastName is optional or can be empty.
But i can't remove validator because i still need to use it with regex or others.
TextEditingController firstName = TextEditingController();
TextEditingController lastName = TextEditingController();
GlobalKey<FormState> _key = GlobalKey();
bool _validate = false;
String first, last;
Form(
key: _key,
autovalidate: _validate,
child: Column(children: [
InputName(
controller: firstName,
placeholder: 'Nama depan',
validator: validateFirstName,
onSaved: (String val) {
first = val;
},
),
InputName(
controller: lastName,
placeholder: 'Nama belakang',
validator: validateLastName,
onSaved: (String val) {
last = val;
},
),
]),
),
Button(
text: 'Lanjut mengisi Email',
onPressed: () {
if (_key.currentState.validate()) {
_key.currentState.save();
String a = firstName.text.capitalize();
String b = lastName.text.capitalize();
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
Regist2Page([a, b]),
transitionDuration: Duration(seconds: 0),
),
);
} else {
setState(() {
_validate = true;
});
}
},
),
String validateLastName(String value) {
if (value.isNotEmpty) {
if (value.length > 20) {
return 'Maksimal 20 karakter';
}
return null;
}
return null;
}
Try this validator
String validateLastName(String value) {
if (value == "") {
// if value were null, is true and then return null
return null;
}
if (value.length > 20) {
return 'Maksimal 20 karakter';
}
return null;
}
Try this!
String validateLastName(String value,{bool isOptional = false})) {
if(isOptional && (value==null || value.isEmpty)){
return null;
}
if (value.isNotEmpty) {
if (value.length > 20) {
return 'Maksimal 20 karakter';
}
return null;
}
return null;
}
Solved by Agus Setya R.
String b = lastName.text == '' ? '' : lastName.text.capitalize();
In my code I validate phone number. If phone number is incorrect - I show error message. But, when user starts to edit number I want to hide this error message.
I've found the solution with currentState.reset(), but it seems not the good one. I have to handle issues with saving text and cursor position. And I still have one small artifact. Normally when I press and hold backspace - it deletes symbols one by one. If I do it when error message is shown - then error message disappears and only one symbol is deleted.
Does anybody know the right solution for this case?
final TextEditingController controller = TextEditingController();
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
TextSelection currentPosition;
return Column(
children: <Widget>[
Form(
key: _textKey,
child: TextFormField(
controller: controller,
validator: (str) {
isError = true;
if (str.isEmpty) {
return err_empty_field;
} else if (!_phoneRegex.hasMatch(str)) {
return err_invalid_phone;
}
isError = false;
},
),
onChanged: () {
if (controller.selection.start < 0 &&
controller.text.length > 0) {
TextSelection position =
controller.text.length > currentPosition.start
? currentPosition
: TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
controller.selection = position;
}
if (isError) {
isError = false;
currentPosition = controller.selection;
if (currentPosition.start > controller.text.length) {
currentPosition = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
}
String currentText = controller.text;
_textKey.currentState.reset();
controller.text = currentText;
controller.selection = currentPosition;
}
},
),
RaisedButton(
onPressed: () {
_textKey.currentState.validate();
},
child: Text(login),
)
],
);
EDIT (Nov 2020)
autovalidate was deprecated after v1.19.0.
Instead use autovalidateMode:
Form(
autovalidateMode: AutovalidateMode.onUserInteraction`.
...
)
Original post
here is a suitable solution to this problem.
You don't actually need to use onChanged or any tips causing side-effects, I solved it by creating a class property which is initialized to false:
bool _autovalidate = false;
The Form Widget has a named property autovalidate. You should pass it the previous boolean:
Form(
key: _textKey,
autovalidate: _autovalidate,
...
)
And in your Submit button onPressed() method, you should update the _autovalidate boolean to be true if the form is invalid, this will make the form to auto validate the TextFormField on every onChanged call:
RaisedButton(
onPressed: () {
if (_textKey.currentState.validate()) {
print('valid');
} else {
print('invalid');
setState(() => _autoValidate = true);
}
},
child: Text(login),
)
I hope it helped Somebody.
January 2021
...
AutovalidateMode _autoValidate = AutovalidateMode.disabled;
Form(
key: _textKey,
autovalidateMode: _autovalidate,
...
)
RaisedButton(
onPressed: () {
if (_textKey.currentState.validate()) {
print('valid');
} else {
print('invalid');
setState(() => _autoValidate = AutovalidateMode.always);
}
},
child: Text("login"),
)
The problem here is errorText is automatically managed by the validator field of the TextFormField. At the same time, the simple solution is to handle the errorText manually.
Step 1: Create
String field, _errorText initialised to null. The field will hold the error message that needs to be shown.
Boolean field, _error initialised to false. The filed is true if there is an error otherwise false.
Step 2:
Assign _errorText to TextFormField
Step 3 (Important):
Make sure that TextFormField validator returns a null value.
Handle the validation here and assign the proper error message to _errorText.
Update _error state correspondingly.
Step 4 (Important):
Reset _errorText and _error. This will remove the error from field soon as you start editing.
Step 5:
Trigger field validation in the onFieldSubmitted and manage your code flow...
import 'package:flutter/material.dart';
class WorkGround extends StatefulWidget {
#override
_WorkGroundState createState() => _WorkGroundState();
}
class _WorkGroundState extends State<WorkGround> {
final _formKey = GlobalKey<FormState>();
final _usernameFocusNode = FocusNode();
final _phoneNumberFocusNode = FocusNode();
/*
* Step 1.
* */
String _userNameErrorText;
bool _userNameError = false;
String _phoneNumberErrorText;
bool _phoneNumberError = false;
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
focusNode: _usernameFocusNode,
decoration: InputDecoration(
labelText: 'Username',
/*
* Step 2
* */
errorText: _userNameErrorText, // Handling error manually
),
textInputAction: TextInputAction.next,
/*
* Step 3
* */
validator: (value) {
setState(() {
if(value.isEmpty) {
_userNameError = true;
_userNameErrorText = 'Enter Username';
}
});
return null; // Return null to handle error manually.
},
/*
* Step 4
* */
onChanged: (value) {
setState(() {
_userNameError = false;
_userNameErrorText = null; // Resets the error
});
},
/*
* Step 5
* */
onFieldSubmitted: (value) {
_formKey.currentState.validate(); // Trigger validation
if(!_userNameError) {
FocusScope.of(context).requestFocus(_phoneNumberFocusNode);
}
},
),
TextFormField(
focusNode: _phoneNumberFocusNode,
decoration: InputDecoration(
labelText: 'Phone Number',
/*
* Step 2
* */
errorText: _phoneNumberErrorText, // Handling error manually
),
textInputAction: TextInputAction.done,
/*
* Step 3
* */
validator: (value) {
setState(() {
if(value.isEmpty) {
_phoneNumberError = true;
_phoneNumberErrorText = 'Enter Phone number';
} else if( value.length < 10) {
_phoneNumberError = true;
_phoneNumberErrorText = 'Invalid Phone number';
}
});
return null; // Return null to handle error manually.
},
/*
* Step 4
* */
onChanged: (value) {
setState(() {
_phoneNumberError = false;
_phoneNumberErrorText = null; // Resets the error
});
},
/*
* Step 5
* */
onFieldSubmitted: (value) {
_formKey.currentState.validate(); // Trigger validation
if(!_phoneNumberError) {
// submit form or whatever your code flow is...
}
},
),
],
),
),
);
}
}
I have achieved your both below functionality:
1) Hide error message when editing
2) validate input field when login button pressed
Note: i have commented phone number regex and put validation for
string length < 10 digit for testing.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
}
final TextEditingController controller = TextEditingController();
// final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
bool isWriting = false;
bool isLoginPressed = false;
int counter = 0;
String myErrorString = "";
TextSelection currentPosition;
final _textKey = GlobalKey<FormState>();
#override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('MapSample'),
),
body: Container(
child: Column(
children: <Widget>[
Form(
key: _textKey,
child: TextFormField(
controller: controller,
validator: (str) {
myErrorString = "";
if(isLoginPressed){
isError = true;
if (str.isEmpty) {
myErrorString = 'err_empty_field';
return myErrorString;
}
else if (str.length < 10) {
myErrorString = 'err_invalid_phone';
validateMe();
return myErrorString;
}
/*else if (!_phoneRegex.hasMatch(str)) {
myErrorString = 'err_invalid_phone';
validateMe();
return myErrorString;
}*/
isError = false;
myErrorString = "";
}else{
myErrorString = "";
}
},
),
onChanged: () {
counter++;
if(counter == 9){
counter = 0;
isLoginPressed = false;
}
if(isLoginPressed){
}else{
isWriting = true;
isLoginPressed = false;
myErrorString = "";
_textKey.currentState.validate();
}
},
),
RaisedButton(
onPressed: () {
counter = 1;
isWriting = false;
isLoginPressed = true;
_textKey.currentState.validate();
},
child: Text('login'),
)
],
),
),
);
}
void validateMe() {
if(isLoginPressed){
currentPosition = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
String currentText = controller.text;
_textKey.currentState.reset();
controller.text = currentText;
controller.selection = currentPosition;
isWriting = false;
isLoginPressed = true;
}
}
}
I've found working and easier way
final _textKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();
Widget _getPhoneInputForm() {
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10,17}");
bool isError = false;
bool isButtonPressed = false;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 36.0),
child: Form(
key: _textKey,
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: hint_enter_phone,
contentPadding: EdgeInsets.all(24.0),
fillColor: Colors.blueGrey.withOpacity(0.3),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
borderSide: BorderSide(color: Colors.blueGrey))),
controller: _controller,
validator: (str) {
if (!isButtonPressed) {
return null;
}
isError = true;
if (str.isEmpty) {
return err_empty_field;
} else if (!_phoneRegex.hasMatch(str)) {
return err_invalid_phone;
}
isError = false;
},
onFieldSubmitted: (str) {
if (_textKey.currentState.validate()) _phoneLogin();
},
),
onChanged: () {
isButtonPressed = false;
if (isError) {
_textKey.currentState.validate();
}
},
),
),
RaisedButton(
color: Colors.teal,
textColor: Colors.white,
onPressed: () {
isButtonPressed = true;
if (_textKey.currentState.validate()) _phoneLogin();
},
child: Text(login),
)
],
);
}
This is an exemple , i think its not necessary to do onchange() , the function validate name do the work ...
String validateName(String value) {
String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Name is Required";
} else if (!regExp.hasMatch(value)) {
return "Name must be a-z and A-Z";
}
return null;
}
TextFormField(
controller: _lastname, validator: validateName ,
//initialValue: widget.contact.last_name,
decoration:
InputDecoration(labelText: 'Last name'),
),
void Save() {
if (_keyForm.currentState.validate()) {
// No any error in validation
_keyForm.currentState.save();
................
}
i have found that using a combination of FocusNode and AtuoValidateMode.onUserInteraction does the trick.
class _TextAutoValidateModeExampleState extends State<TextAutoValidateModeExample> {
FocusNode node = FocusNode();
#override
Widget build(BuildContext context) {
return Container(
child: TextFormField(
focusNode: node,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if(node.hasFocus) return null;
if (value!.isEmpty) return "value cannot be empty";
if (!value.isEmail) return "not a valid email";
},
),
);
}
}
// Call this method inside onChanged() and when its focusnode hasFocus
void formReset(GlobalKey<FormState> formKey, TextEditingController controller) {
String stringValue = controller.text;
TextPosition textPosition = controller.selection.base;
formKey.currentState.reset();
controller.text = stringValue;
controller.selection = TextSelection.fromPosition(textPosition);
}
this format worked for me, Hope it helps someone....
validator: (value){
bool emailValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(value);
isError = true;
if(value.isEmpty){
return "Provide an email";
}else if(!emailValid){
return "Enter a valid email";
}
isError = false;
return null;
},