TextFormField Validator not showing validator message - forms

I tried to create this form with validation, so it shows the errors when the user returns each field. But for some reason it doesn't work. I have no reason why. I'm just stuck now.
Here's the code:
import 'package:flutter/material.dart';
import 'package:validate/validate.dart';
void main() => runApp(new MaterialApp(
title: 'Forms in Flutter',
home: new LoginForm(),
theme: ThemeData.dark(),
));
class LoginForm extends StatefulWidget {
String email;
String password;
final Function saveEmail;
final Function savePassword;
final Function attemptLogin;
LoginForm({this.email, this.password, this.saveEmail, this.savePassword,
this.attemptLogin});
#override
LoginFormState createState(){
return new LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
final loginFormKey = GlobalKey<FormState>();
final emailController = new TextEditingController();
final passController = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Login'),
),
body: new Container(
padding: new EdgeInsets.all(10.0),
child: new Form(
key: loginFormKey,
child: new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Container(
width: 2.0,
height: 18.0,
color: Colors.white,
),
new Container(
width: 5.0,
height: 0.0
),
new Expanded(child: new TextFormField(
decoration: new InputDecoration.collapsed(
hintText: "EMAIL",
),
validator: (String value) {
if (!Validate.isEmail(value)) {
return 'Please enter Email';
}
},
onFieldSubmitted: (val) {
print(loginFormKey.currentState.validate());
if (loginFormKey.currentState.validate()) {
widget.email = val;
widget.saveEmail(val);
}
},
controller: emailController,
),)
],
),
new Row(
children: <Widget>[
new Container(
width: 2.0,
height: 18.0,
color: Colors.white,
padding: const EdgeInsets.fromLTRB(0.0, 0.0, 5.0, 0.0),
),
new Container(
width: 5.0,
height: 0.0
),
new Expanded(child: new TextFormField(
obscureText: true,
decoration: new InputDecoration.collapsed(
hintText: 'PASSWORD',
),
validator: (val) =>
val.length < 6 ?
'Still too short' : '',
onFieldSubmitted: (val) {
if (loginFormKey.currentState.validate()) {
widget.email = emailController.text;
print(widget.email);
widget.saveEmail(emailController.text);
widget.password = val;
print(widget.password);
widget.savePassword(val);
widget.attemptLogin();
}
},
controller: passController,
),)
],
)
],
),
),
)
);
}
}
I really don't know what's causing this. It seems like everything in the onfieldSubmitted part of the fields don't work. If I remove the If statements, they work okay, but once it's added it gives no response.
Seems like something simple but I'm just missing the point. Any help would be greatly appreciated. Thanks.

am having the same issue now. I think the !Validate.isEmail(value) is not working.
I commented it out and my code ran well. Try writing your own custom email validation instead of using !Validate.isEmail(value)

The onFieldSubmitted property works when clicking enter or submit on the keyboard. I think, you should add a submit button for submitting because your validations works for form, not an field or input. So, It means if a user entered the email address but this user didn't enter any password, it will take validation error message for password on email field when clicked the enter button. It's not a good feedback. If you use a submit button, it should shows more good feedback for validation messages.
// The button widget
new FlatButton(
onPressed: () => this._submit(),
child: new Text('Login')
);
// The submit function
void _submit() {
if (this.loginFormKey.currentState.validate()) {
this.loginFormKey.currentState.save();
// Do your jobs with the validated form data.
}
}

Related

How to validate the TextFormField as we type in the input in Flutter

I have created a login screen with textformfield for email id and password using flutter. Also, I have added the validation to check these fields. The code is as below;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
theme: ThemeData(
brightness: Brightness.dark,
),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var _formKey = GlobalKey<FormState>();
var isLoading = false;
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
}
_formKey.currentState.save();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Form Validation"),
leading: Icon(Icons.filter_vintage),
),
//body
body: Padding(
padding: const EdgeInsets.all(16.0),
//form
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text(
"Form-Validation In Flutter ",
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
//styling
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
TextFormField(
decoration: InputDecoration(labelText: 'E-Mail'),
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (value) {
//Validator
},
validator: (value) {
if (value.isEmpty ||
!RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value)) {
return 'Enter a valid email!';
}
return null;
},
),
//box styling
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
//text input
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (value) {},
obscureText: true,
validator: (value) {
if (value.isEmpty) {
return 'Enter a valid password!';
}
return null;
},
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
RaisedButton(
padding: EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 15.0,
),
child: Text(
"Submit",
style: TextStyle(
fontSize: 24.0,
),
),
onPressed: () => _submit(),
)
],
),
),
),
);
}
}
The issue I am facing is, I want to validate the fields as soon as the user starts typing the input(dynamically) rather than clicking on the submit button to wait for the validation to happen. I did a lot of research yet could not find a solution. Thanks in advance for any help!
Flutter Form Validation with TextFormField
Here's an alternative implementation of the _TextSubmitWidgetState that uses a Form:
class _TextSubmitWidgetState extends State<TextSubmitForm> {
// declare a GlobalKey
final _formKey = GlobalKey<FormState>();
// declare a variable to keep track of the input text
String _name = '';
void _submit() {
// validate all the form fields
if (_formKey.currentState!.validate()) {
// on success, notify the parent widget
widget.onSubmit(_name);
}
}
#override
Widget build(BuildContext context) {
// build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Enter your name',
),
// use the validator to return an error string (or null) based on the input text
validator: (text) {
if (text == null || text.isEmpty) {
return 'Can\'t be empty';
}
if (text.length < 4) {
return 'Too short';
}
return null;
},
// update the state variable when the text changes
onChanged: (text) => setState(() => _name = text),
),
ElevatedButton(
// only enable the button if the text is not empty
onPressed: _name.isNotEmpty ? _submit : null,
child: Text(
'Submit',
style: Theme.of(context).textTheme.headline6,
),
),
],
),
);
}
}
source : https://codewithandrea.com/articles/flutter-text-field-form-validation/
May be this can help someone. Inside the TextFormField use this line of code:
autovalidateMode: AutovalidateMode.onUserInteraction
use autovalidateMode in your Form widget
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: FormUI(),
),

The following _CastError was thrown building NoteAdder(dirty, state: _NoteAdder#76214):Null check operator used on a null value

The error (given in the title) was thrown when I ran the app.
Here is my code
class NoteAdder extends StatefulWidget {
#override
_NoteAdder createState() => _NoteAdder();
}
class _NoteAdder extends State<NoteAdder> {
Note? note;
TextEditingController titleController = TextEditingController();
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
titleController.text = note!.title!;
descriptionController.text = note!.description!;
return AlertDialog(
backgroundColor: Colors.lime,
content: Column(
children: [
const Text(
'ADD NOTE',
style: TextStyle(fontSize: 25),
),
const SizedBox(height: 30),
Container(
alignment: Alignment.topLeft,
child: const Text('Title:'),
),
TextField(
controller: titleController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
),
),
const SizedBox(height: 30),
Container(
alignment: Alignment.topLeft,
child: const Text('Description:'),
),
TextField(
controller: descriptionController,
maxLines: 13,
decoration: InputDecoration(
border: UnderlineInputBorder(),
),
),
const SizedBox(height: 35),
Container(
alignment: Alignment.center,
child: ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.red),
onPressed: () {
setState(() {
save();
});
},
child: const Text('Save')))
],
));
}
void save() async {
note?.date = DateFormat.yMMMd().format(DateTime.now()) as DateTime?;
if (note?.id != null) {
await NoteDatabaseHelper.update(note!)??0;
} else {
await NoteDatabaseHelper.insert(note!)??0;
}
}
}
I am a bit new to flutter. Please help me to solve this problem
Link to my complete project: https://github.com/SayanBanerjee09082002/Daily_Utility
Note: The add screen appears when I press a floating action button. The app runs ok until I hit that button.
Since you wrote Note? note;, note == null so trying to use it with null check like this note?.date = DateFormat.yMMMd().format(DateTime.now()) as DateTime?; will throw error. Now I don't know what the constructor of your class Note look like so my answer may not be accurate; but as answer, I will advice you to do either:
Note? note = Note(); //I don't know the structure of the constructor, so you have to deal with that part
or inside save()
if(note != null) {
note.date = DateFormat.yMMMd().format(DateTime.now()) as DateTime;
}

How to pass user's TextFormField input to a button in a different class in flutter

I am creating a flutter app. For the code reusability, I need to differentiate Email and password forms and Login Button, I am not sure how to properly to pass the input from textformfield to the button for the form to be validated, when clicking it. Here's my code. Note that im a beginner in flutter.
//This is my EmailTextForm class:
class EmailTextForm extends StatelessWidget {
String email;
EmailTextForm({Key key, this.email}) : super(key: key);
Widget build(BuildContext context) {
return Container(
width: 370.0,
height: 54.0,
child: TextFormField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
//DEFAULT STATE OF THE BORDER(FOCUSED BORDER DOWN BELOW TO HAVE MORE CONTROL OF THE FORM)
borderSide: BorderSide(
width: 1.0, color: Color.fromRGBO(16, 25, 53, 0.1)),
borderRadius: BorderRadius.circular(12.0)),
focusedBorder: OutlineInputBorder(
//ON FOCUSED BORDER TO NOT CHANGE STATE WHILE BEING PRESSED ON
borderSide: BorderSide(
width: 1.0, color: Color.fromRGBO(16, 25, 53, 0.1)),
borderRadius: BorderRadius.circular(12.0),
),
prefixIcon: Icon(Icons.mail, color: Color(0xFF9FA3AE)),
hintText: 'El.Paštas',
hintStyle: TextStyle(
fontFamily: 'Sora',
fontSize: 16.0,
color: Color(0xFF9FA3AE),
),
),
validator: (input) =>
!input.contains('#') ? 'Please enter a valid email' : null,
onSaved: (input) => email = input,
));
}
}
//This is the button class.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class LoginButton extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
String email;
String password;
_submit() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print('validated');
//logging in the user
}
}
#override
Widget build(BuildContext context) {
//Container to manipulate button design
return Container(
width: 370.0,
height: 54.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular((12.0)),
gradient: LinearGradient(
//change gradient, wrong value, maybe something in AdobeXD.
colors: <Color>[Color(0xFF00BAFF), Color(0xFF448CFA)],
stops: [0.0, 1.0],
begin: Alignment(-1.0, 0.0),
end: Alignment(1.0, 0.0),
transform: GradientRotation(math.pi / 2),
),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(48, 183, 241, 1.0),
offset: Offset(0.0, 4.0),
blurRadius: 12.0,
),
],
),
//#### WHEN BUTTON IS PRESSED ####
child: ElevatedButton(
onPressed: _submit,
child: Text(
'Prisijungti',
),
style: ElevatedButton.styleFrom(
//COLOR OF THE TEXT INSIDE THE BUTTON
onPrimary: Color(0xFFFFFFFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
primary: Colors.transparent,
textStyle: TextStyle(
//Text inside button style
fontSize: 16.0,
fontWeight: FontWeight.w600,
fontFamily: 'Sora',
)),
));
}
}
Accessing Field Data
The code sample at bottom shows two different ways to get access to the email field value using:
FormFieldState
TextEditingController
These two methods don't rely on having a Form wrapping your fields (although it's often convenient to do so, giving you more options for handling form data & showing validation errors.)
Why use Form?
A Form widget wrapping fields is useful for handling/manipulating several fields together as a group for things such as form resetting, validation, and submitting.
We access these Form functions via a GlobalKey<FormState> that we give to the Form when we declare it.
child: Form(
key: formKey, // declared above as a field in our State object
For example, TextFormField has a validator: argument (takes a function). If our field is inside a Form, we can ask the Form call all validator functions to "validate" our form:
formKey.currentState.validate();
The validator: will display any non-null String you return to it:
Code Sample
import 'package:flutter/material.dart';
class FormValuesPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form Values'),
),
body: FormValuesExample(),
);
}
}
class FormValuesExample extends StatefulWidget {
#override
_FormValuesExampleState createState() => _FormValuesExampleState();
}
class _FormValuesExampleState extends State<FormValuesExample> {
GlobalKey<FormState> formKey = GlobalKey<FormState>();
GlobalKey<FormFieldState> emailFieldKey = GlobalKey();
TextEditingController emailController = TextEditingController();
#override
void dispose() {
emailController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Form(
key: formKey, // declared above as a field in our State object
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextFormField(
key: emailFieldKey,
controller: emailController,
decoration: InputDecoration(
labelText: 'Email'
),
validator: (val) => validateEmail(val),
),
LoginButton(formKey: formKey, fieldKey: emailFieldKey, controller: emailController,)
],
),
),
);
}
String validateEmail(String email) {
if (email == null || email.isEmpty)
return 'Email cannot be empty';
return null;
}
}
class LoginButton extends StatelessWidget {
final GlobalKey<FormState> formKey;
final GlobalKey<FormFieldState> fieldKey;
final TextEditingController controller;
LoginButton({this.formKey, this.fieldKey, this.controller});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
formKey.currentState.validate();
print('from FormFieldState ${fieldKey.currentState.value}');
print('from controller: ${controller.text}');
},
child: Text('Submit'));
}
}
You need to wrap them in a Form widget and pass the key, for example:
Form(
key: _keyForm
child: Column(
children: <Widget>[
EmailTextFieldForm(),
PasswordTextFieldForm(),
FormButton(),
],
)
)
you need to wrap all the TextFormFields in a form to get it like this
Column(
children: [
TextFormField(),
....
TextFormField(),
])
TextFormFields can be wrapped or moved to another widget, as long as it is a child of the Form.
if everything is in one widget
Form(
child:
Column(
children: [
TextFormField(),
....
TextFormField(),
Button(
onTap: () {},
),
])
you need to wrap the button in the Builder so that the context of the current element in the tree is available
Builder(
builder: (context) => Button(
onTap: () {},
),
),
and after that, you can do Form.of(context).validate(). This entry will find the first form higher in the tree and validate all text fields.
in this way you should get out like this
Builder(
builder: (context) => Button(
onTap: () {
Form.of(context).validate()
},
),
)
if the button is placed in a separate widget, then there is no need to wrap it in the Builder, you can simply call the validation, since the context below the form is available to you
Button(
onTap: () {
Form.of(context).validate()
},
),
also, you can create GlobalKey
and use validation with a key. You can pass key, for example, through the constructor(if needed)
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey
child: Column(
children: [
TextFormField(),
....
Button(
onTap: () {
_formKey.currentState!.validate ()
}
)
],
),
)

I have a Form where two text inputs are there. when user enter text in one input and goes to other the text vanishes . what to do?

I have a Form where two text inputs are there. when user enter text in one input and goes to other the text vanishes . what to do ?there are four dart files main.dart,new_transaction.dart,
the form module
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final titleController = TextEditingController();
final amountController=TextEditingController();
final Function addTx;
NewTransaction(this.addTx);
void submitData()
{
final enterTitle =titleController.text;
final enterAmount=double.parse(amountController.text);
if (enterTitle.isEmpty||enterAmount<=0)
{return;}
addTx(enterTitle,enterAmount);
}
#override
Widget build(BuildContext context) {
return
Card(
elevation: 7,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'title'),
controller: titleController,
onSubmitted:(_)=>submitData(),
),
TextField(
decoration: InputDecoration(labelText: 'amount'),
controller: amountController,
keyboardType: TextInputType.number,
onSubmitted:(_)=>submitData(),
),
FlatButton(
onPressed: submitData,
child: Text('Add Transaction'),
textColor: Colors.purple,
),
],
),
));
}
}
i am calling this from Main like this
main.dart
Its because NewTransaction is StatelessWidget. When setState is called from its parent titleController and amountController will be recreated. So the value will be empty.
Solution:
Make NewTransaction as StatefulWidget.
Explanation:
StatefulWidget have state for them (Kind of separate runtime memory block storage to store values of the variables). So even if the parent widget rebuilds, this State of the StatefulWidget won't be recreated. It will be just reused with previous persisted values.
But StatelessWidget don't have State (Won't maintain values of the variable). So if parent widget get rebuilds, then this StatelessWidget also rebuild. which means all the variable like titleController and amountController will be deleted and recreated(with empty values).
Try this code.
class _NewTransactionState extends State<NewTransaction> {
String title = "";
int amount = 0;
void submitData() {
if (title == "" || amount <= 0) {
print("Invalid");
//TODO:Handle invalid inputs
return;
}
widget.addTx(title, amount);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Card(
elevation: 7,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'title'),
onChanged: (value) {
title = value;
},
onSubmitted: (_) => submitData(),
),
TextField(
decoration: InputDecoration(labelText: 'amount'),
keyboardType: TextInputType.number,
onSubmitted: (_) => submitData(),
onChanged: (value) {
amount = int.parse(value);
},
),
FlatButton(
onPressed: submitData,
child: Text('Add Transaction'),
textColor: Colors.purple,
),
],
),
)),
),
);
}
}
Hope this will work for you if not then tell me know in comment.

Flutter Textform Validation not showing up

I have watched a couple of tutorials on how to make validators work but none of them seemed to work. Can anyone help me with this? This is the code of a simple sign in page. My validators don't show up on screen if there's any sort of error it should be detecting. I've watched tutorials where it shows up in red but in my app, it doesn't show up at all.
class UserLogin extends StatefulWidget {
UserLogin({this.auth,this.onSignedIn});
final BaseAuth auth;
final VoidCallback onSignedIn;
#override
State<StatefulWidget> createState()=> _UserLoginState();
}
class _UserLoginState extends State<UserLogin> {
final formkey = GlobalKey<FormState>();
bool _validateAndSave()
{
final form = formkey.currentState;
if(form.validate())
{
form.save();
return true;
}
else
return false;
}
static final incorrect_icon = Icon(
Icons.error,
color: Colors.pink,
);
void _validateAndSubmit() async
{
if(_validateAndSave()) {
try {
String userId = await widget.auth.signIn(emailid, password);
print('Signed in! $userId');
//widget.onSignedIn();
Navigator.push(context, MaterialPageRoute(builder: (context)=>Feed()));
}
catch (e) {
print('Error: $e');
}
}
}
static final TextEditingController emailContr = new TextEditingController();
static final TextEditingController passwordContr = new TextEditingController();
static String get emailid => emailContr.text;
static String get password => passwordContr.text;
final _email = Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
controller: emailContr,
autofocus: false,
validator: (input) {
if(input.isEmpty)
{
return 'Email cannot be empty';
}
return null;
},
//onSaved: (input)=> emailid = input,
decoration: InputDecoration(
hintText: 'Enter Email Address',
suffixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)
),
),
),
);
final _pass = Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: TextFormField(
controller: passwordContr,
obscureText: true,
autofocus: false,
validator: (input) {
if(input.length <= 6)
{
return 'Password should be at least 6 characters';
}
return null;
},
decoration: InputDecoration(
hintText: 'Enter password',
suffixIcon: Icon(Icons.lock),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)
),
),
),
);
/*final login_button =
},
);
*/
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.yellow,
body: Container(
child: Form(
key: formkey,
child: Column(
children: <Widget>[
SizedBox(height: 200,),
Text('Vibing',
style:TextStyle(
fontWeight: FontWeight.bold,
fontSize: 64,
),
),
SizedBox(height: 100,),
_email,
SizedBox(height: 20,),
_pass,
SizedBox(height:30),
RaisedButton(
color: Colors.yellow,
elevation: 5,
child: Text('Login'),
onPressed: (){
_validateAndSubmit();
formkey.currentState.reset();
}
),
SizedBox(height:10),
FlatButton(
child: Text('Forgot password'),
onPressed: ()=> Navigator.push(context, MaterialPageRoute(builder:(context)=>ForgotPassword()),)
),
SizedBox(height:10),
FlatButton(
child: Text('New? Register here!'),
onPressed: ()=> Navigator.push(context, MaterialPageRoute(builder:(context)=>UserReg()),)
),
],
),
),
) ,
);
}
}
The problem is that you're resetting the form after validation so any error shown will reset. Just remove this line from your login button callback:
formkey.currentState.reset();
And voila:
reset():
Resets every [FormField] that is a descendant of this [Form] back to its
[FormField.initialValue].
In your case, the initialValue is empty string "" and that's why when you called reset() method of Form, it's setting an empty string, that will not show any error, as nothing is there.