Flutter: Unable to get textfield value on first submission - flutter

I have form inside page, which contains four different fields and one button.
I have a strange issue, here's what I'm doing:
fill up form
hit Add button //variables are null
if I hit Add button again, I've correct values
I thought, maybe some library or code is causing issue so I reduced code to narrow down, now I've very simple code, only one field with material.dart import package. But the problem still exists. :(
What I've Noticed: Hitting Add button always return data from previous state than current.
Example:
Fill up form with value 1000
Hit Add button //prints null
Update value to 2000
Hit Add button //prints 1000
Update value to 3000
Hit Add button //prints 2000
and so on....
Here is complete code:
import 'package:flutter/material.dart';
class AddFees extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _AddFees();
}
class _AddFees extends State<AddFees> {
final addFeesFormKey = new GlobalKey<FormState>();
final addFeesScaffoldKey = new GlobalKey<ScaffoldState>();
String _fees;
TextFormField fees;
RaisedButton addFee;
#override
void initState() {
super.initState();
}
void _submit() {
final form = addFeesFormKey.currentState;
if (form.validate()) {
setState(() {
print(_fees);
form.save();
});
}
}
#override
Widget build(BuildContext context) {
fees = TextFormField(
keyboardType: TextInputType.number,
onSaved: (val) => _fees = val,
decoration: InputDecoration(labelText: 'Fees'),
validator: (value) {
if (value.isEmpty) {
return 'Fee cannot be empty';
} else {
return null;
}
},
);
addFee = RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
onPressed: () {
_submit();
},
padding: EdgeInsets.all(12),
color: Colors.redAccent,
child: Text('Add Payment',
style: TextStyle(color: Colors.white, fontSize: 24.0)),
);
return Scaffold(
key: addFeesScaffoldKey,
body: new Form(
key: addFeesFormKey,
child: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
SizedBox(
height: 25.0,
),
fees,
SizedBox(
height: 15.0,
),
addFee
])),
),
);
}
}
What is going on with this?

You need to put the _fees = val inside a setState - see example:
fees = TextFormField(
keyboardType: TextInputType.number,
onSaved: (val) => setState(() => _fees = val), // This is change
decoration: InputDecoration(labelText: 'Fees'),
validator: (value) {
if (value.isEmpty) {
return 'Fee cannot be empty';
} else {
return null;
}
},
);

Finally, I figured that out and the solution was pretty simple.
Solution: Calling form.save(); before trying to access variables solve the issue.
I've swapped two lines in void _submit() method to make it work, here is the updated code:
void _submit() {
final form = addFeesFormKey.currentState;
if (form.validate()) {
setState(() {
form.save(); //swapped
print(_fees);
});
}
}

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(),
),

Value change but UI don't

I want to show a button only if the new value is different from the old one. But its not working, the button isn't showing
class ViewPatientPage extends StatefulWidget {
final int status;
final String name;
const ViewPatientPage({required this.status, required this.name, super.key});
#override
State<ViewPatientPage> createState() => _ViewPatientPageState();
}
class _ViewPatientPageState extends State<ViewPatientPage> {
String name = '';
#override
void initState() {
super.initState();
name = widget.name;
}
SizedBox space() {
return const SizedBox(height: 15);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
child: Scaffold(
appBar: AppBar(
actions: [
(name != widget.name) ? TextButton(
onPressed: () {},
child: const Text(
'Editar',
style: TextStyle(color: Colors.white),
)): const SizedBox.shrink()
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
space(),
TextFormField(
initialValue: widget.name,
// keyboardType: keyboardType,
validator: (val) {
if (val.toString().isEmpty || val == null || val == '') {
return 'Fill field';
}
return null;
},
decoration: InputDecoration(
label: const Text('Name'),
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
border: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black),
borderRadius: BorderRadius.circular(20))),
onChanged: (value) {
name = value.trim();
print(name);
print('widget ${widget.name}');
},
// inputFormatters: inputFormatters,
),
space(),
],
),
),
),
);
}
}
#random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
You need to call setState() in onChanged.
Something like:
onChanged: (value) {
setState(() {
name = value.trim();
});
print(name);
print('widget ${widget.name}');
},
Call setState inside onChanged function
setState(() { name = value.trim(); });
To update the UI, you have to a call a setState() in the onChanged: (value) {}
setState(()
{
...
});

I am trying to extract the emailController and the passwordController but I keep getting an error

import 'package:flutter/material.dart';
import 'package:time_trackerpractice/common_widgets/form_submit_button.dart';
import 'package:time_trackerpractice/services/auth.dart';
enum EmailSignInFormType{signIn,register}
class EmailSignInForm extends StatefulWidget {
EmailSignInForm({#required this.auth});
final AuthBase auth;
#override
_EmailSignInFormState createState() => _EmailSignInFormState();
}
class _EmailSignInFormState extends State<EmailSignInForm> {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController =TextEditingController();
String get _email=>_emailController.text;
String get _password=> _passwordController.text;
EmailSignInFormType _formType = EmailSignInFormType.signIn;
void _submit() async {
try {
if (_formType == EmailSignInFormType.signIn) {
await widget.auth.signInWithEmailAndPassword(_email, _password);
} else {
await widget.auth.signInWithEmailAndPassword(_email, _password);
}
} catch (e) {
print(e.toString());
}
Navigator.of(context).pop();
}
void _toggleFormType(){
setState(() {
_formType = _formType == EmailSignInFormType.signIn?
EmailSignInFormType.register: EmailSignInFormType.signIn;
});
}
List<Widget> _buildChildren() {
final primaryText = _formType == EmailSignInFormType.signIn ? 'Sign in' : 'Create account';
final secondaryText = _formType == EmailSignInFormType.signIn
? 'Need an account? Register'
: 'Have an account? Sign in';
return [
TextField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'test#test.com',
),
autocorrect: false,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
),
SizedBox(height: 8.0),
TextField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
textInputAction: TextInputAction.done,
),
SizedBox(height: 8.0),
FormSubmitButton(
text: primaryText,
onPressed: _submit,
),
SizedBox(height: 8.0),
FlatButton(
child: Text(secondaryText),
onPressed: _toggleFormType,
),
];
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment:CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: _buildChildren(),
),
);
}
}
I'm working on customizing my text field for my email and password.When I go to extract the method from emailController and passwordController.I receive an error saying "the end of the selection contains characters that do not belong to the statement.I checked everything and the code seems correct to me but its still not working can you please help me out.I imported the all of the code into a code snippet so that you could see all of the code on the page.
I don't know if it's that, but you do have some random + in the end of line 47
Secondly, I assume you wanted to return Column with TextFields and other widgets in it, but instead there's bunch of widgets separated with commas.
So possibly you've lost the top part of your code

FormKey current state in flutter equals null

So i'm having a form like this:
Form(
key: formKey,
child: Column(
children: [
TextFormField(
validator: _validateForm,
cursorColor: Colors.black,
controller: _numberLocalsController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
left: 15, bottom: 11, top: 11, right: 15),
hintText: "numero di locali"),
),
TextFormField(
validator: _validateForm,
cursorColor: Colors.black,
controller: _numberRoomsController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
left: 15, bottom: 11, top: 11, right: 15),
hintText: "numero stanze da letto"),
),
TextFormField(
validator: _validateForm,
cursorColor: Colors.black,
keyboardType: TextInputType.number,
controller: _numberbathroomsController,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
left: 15, bottom: 11, top: 11, right: 15),
hintText: "n° bagni"),
),
],
)),
and i initialized the formKey outside the build method like this :
class _FilterPageState extends State<FilterPage> {
final formKey = GlobalKey<FormState>();}
The idea is that there's a button that's clicked that just does the following :
final isValid = formKey.currentState.validate();
if (isValid) {
Navigator.pop(context, filterModel);
}
Now I get the error
Validate() was called on null
The formkey current context only has a value the first time i open the form. but when the navigator pops and i try to access the form again it gives the error.
Salma,
I'm still not clear on where / why you're seeing the null error, since I don't have access to your full code.
So I'll just post my own code sample below and you can compare implementations and hopefully that leads you to an answer.
From my test code below, it should be possible to Navigator.pop from the form page back to a previous page and then Navigator.push back to the form page and use the formKey again, without a null error.
FormStateTestPage
Just a base page with a Scaffold
BasePage
StatefulWidget where you can click a button to Navigator.push to the form page (FilterPage).
FilterPage
the Form is here
with the code sample below the Form is using a locally defined form key (localKey)
you can push to this FilterPage, pop back to BasePage, and push again to FilterPage and there should be no null errors
if you need access to form state outside of FilterPage, then you must declare a form key above FilterPage and provide it as a constructor argument
for outside access, replace the foreignKey: null with foreignKey: foreignKey (which is defined in _BasePageState). Then you can access form state from BasePage.
the code below is capturing the return value from the Navigator.push / Navigator.pop to FilterPage & back. That is how the form value is being shown in BasePage. The key line is: nukeCodes = await Navigator.push<String>
if nukeCodes is not null after popping back to BasePage, a setState is called, rebuilding BasePage & displaying the nukeCodes value.
class FormStateTestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form State Test'),
),
body: BasePage(),
);
}
}
class BasePage extends StatefulWidget {
#override
_BasePageState createState() => _BasePageState();
}
class _BasePageState extends State<BasePage> {
Key foreignKey = GlobalKey<FormState>();
String nukeCodes;
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Nuke Codes:'),
Text(nukeCodes ?? 'awaiting'),
Center(
child: RaisedButton(
child: Text('Go to Form'),
onPressed: () async {
nukeCodes = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => FilterPage(foreignKey: null)
) // pass null to have FilterPage use its own, self-defined formKey
// pass in foreignKey to use a formKey defined ABOVE FilterPage
);
if (nukeCodes != null && nukeCodes.isNotEmpty) {
print('Codes returned');
setState(() {});
}
}
),
),
],
);
}
}
class FilterPage extends StatefulWidget {
final GlobalKey<FormState> foreignKey;
FilterPage({this.foreignKey});
#override
_FilterPageState createState() => _FilterPageState();
}
class _FilterPageState extends State<FilterPage> {
final localKey = GlobalKey<FormState>();
GlobalKey<FormState> formKey;
#override
void initState() {
super.initState();
if (widget.foreignKey != null) {
formKey = widget.foreignKey;
print('Form using foreignKey');
}
else {
formKey = localKey;
print('Form using localKey');
}
}
#override
Widget build(BuildContext context) {
String codes;
return Scaffold(
body: SafeArea(
child: Form(
key: formKey,
//key: widget.formKey,
child: ListView(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Enter Nuclear Codes'
),
onSaved: (val) => codes = val,
validator: (value) {
if (value.isEmpty) {
return 'A value is required';
}
return null;
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
Navigator.pop(context, codes);
}
},
)
],
),
),
),
);
}
}
Anyways, hopefully you can compare/contrast your code base with above and find out what's causing the null error.
Best of luck.

I am trying to create list of TextFormFields which takes numbers as inputs and I want to Sum all those numbers

I am trying to create list of TextFormFields which takes numbers as inputs and I want to Sum all those numbers. When I click on a button on app bar new textformfield appears and user enters value..validator is also working fine...But I am not able to do the Sum. When I used print in Onsaved method it displays all the entered values..If I use Controller, whatever the text we enter in formfield it is displaying same same in all the other textfields also..so controller is not working...I created TextFormField in different function and calling that function when button is pressed. I created another button to go to next screen at the same time to validate which works fine...
Below is the TextFormField code: Please help to Sum all the values entered in it:
child: TextFormField(
// controller: _childController,
decoration: InputDecoration(
hintText: 'Value $_count',
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 5, left: 20)),
keyboardType: TextInputType.number,
style: TextStyle(
color: Color.fromARGB(255, 0, 0, 0),
fontWeight: FontWeight.w400,
fontSize: 24,
),
validator: (String value) {
double sal = double.tryParse(value);
if (sal == null) {
return 'enter or delete row';
}
},
onSaved: (String value) {
// print(_childController.text);
// print(value);
_mVal = value;
double _mVal2 = double.tryParse(_mVal);
double _mVal3;
print(_mVal);
int k = 0;
_children.forEach((element) {
int y = int.tryParse(_mVal);
k=k+y;
print(k);
}
Here is a quick example of how you can achieve this:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Test(),
),
);
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
final _formKey = GlobalKey<FormState>();
List<TextEditingController> textFieldControllers = [];
int numberOfTextFields = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
addNewTextField();
},
),
body: Stack(
children: [
Form(
key: _formKey,
child: ListView.builder(
itemCount: numberOfTextFields,
itemBuilder: (BuildContext context, int index) {
return TextFormField(
validator: (String value) {
double sal = double.tryParse(value);
if (sal == null) {
return 'enter or delete row';
}
return null;
},
controller: textFieldControllers[index],
);
},
),
),
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: () {
if (_formKey.currentState.validate()) {
showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: Material(
child: Container(
padding: EdgeInsets.all(10.0),
child: Text(
'The sum is ${textFieldControllers.fold(0, (previousValue, element) => previousValue + int.parse(element.value.text))}'),
),
),
);
});
}
},
child: Container(
padding: EdgeInsets.all(10.0),
color: Colors.redAccent,
child: Text('Tap to sum'),
),
),
),
],
),
);
}
void addNewTextField() {
textFieldControllers.add(TextEditingController());
numberOfTextFields++;
setState(() {});
}
#override
void dispose() {
textFieldControllers.forEach((textFieldController) => textFieldController.dispose());
super.dispose();
}
}
You can expand on this idea to remove textField if needed. Just don't forget to dispose your textFields.
How does this work: Each time a TextField Widget is create, an associated TextEditingController is created and given to the TextField. When we want to sum, we just iterate on the TextEditingController list.