How to change TextFormField initialValue dynamically? - flutter

I'm trying to use a value from a provider model to update the initialValue of a TextFormField, but the initialValue doesn't change.
import 'package:flutter/material.dart';
import 'package:new_app/models/button_mode.dart';
import 'package:provider/provider.dart';
class EditWeightTextField extends StatelessWidget {
const EditWeightTextField(
{Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<ButtonMode>(builder: (context, buttonMode, child) {
return TextFormField(
initialValue: buttonMode.weight.toString(),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter weight';
}
return null;
},
);
});
}
}
if instead of a TextFormField I use a Text(${buttonMode.weight}') then the text is updated properly. What can I do to make it work with the TextFormField?

You can use TextEditingController in this case.
TextEditingController _controller = TextEditingController();
Consumer<ButtonMode>(builder: (context, buttonMode, child) {
if(buttonMode.weight != null && _controller.text != buttonMode.weight){ // or check for the null value of button mode.weight alone
_controller.text = buttonMode.weight ?? '' ;
}
return TextFormField(
controller : _controller,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter weight';
}
return null;
},
);
});

Related

i have a ListView Builder and i want to place a List of TextEditingController in a List

i have a ListView Builder and i want to place a List of TextEditingController in a List.But it give an Error,"Expected to find an Identifier".Im confused.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ListViewBuilder extends StatefulWidget {
const ListViewBuilder({super.key});
#override
State<ListViewBuilder> createState() => _ListViewBuilderState();
}
class _ListViewBuilderState extends State<ListViewBuilder> {
final _formKey = GlobalKey<FormState>();
// final TextEditingController controller;
List validators = [
(value) {
if (value == null || value.isEmpty) {
return 'Please enter the number';
}
return null;
},
(value) {
if (value == null || value.isEmpty) {
return 'Please enter the email';
}
return null;
},
(value) {
if (value == null || value.isEmpty) {
return 'Please enter the password';
}
return null;
},
];
List formatters = [
FilteringTextInputFormatter.digitsOnly,
FilteringTextInputFormatter.allow(RegExp('[a-zA-Z]')),
FilteringTextInputFormatter.deny(RegExp(r'[/\\]'))
];
///Error happens here!!
List controller = [
final _name = TextEditingController();
final _age = TextEditingController();
final _height = TextEditingController();
final _color = TextEditingController();
final _dateofBirt = TextEditingController();
final _Nationality = TextEditingController();
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
child: ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return TextFormField(
controller: controller[index],
validator: validators[index],
inputFormatters: [formatters[index]],
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Text('Click'),
onPressed: () {
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(SnackBar(content: Text('Processing Data')));
}
},
),
);
}
}
i made a controller a variable.and also tried assigning them individually but still the error appears.
If you like to use list of TextEditingController you dont need to assign it on separate variable.
List controller = [
TextEditingController(),
TextEditingController(),
TextEditingController(),
TextEditingController(),
TextEditingController(),
TextEditingController(),
];
You can also use List.generate for it.
Now to get _name as you've define on 1st item, you need to use index like controller[0] for _name. _age for controller[1] ,, so on.
You can get text like
controller[index].text

Form validation in flutter passes despite not meeting conditions

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;

How can I use Listview builder inside a Flutter Form widget?

I have a Form widget that has about 30 TextFormField widgets. This is intended to be used as a web app. I'm trying to do the best to have the maximum possible speed. The TextFormField widgets have different text input formatters, controllers and some are required and some are optional. How can I use the ListView.builder inside the Form so the the TextFormField widgets are loaded as needed?
If I could understand, you need to make Form with ListView.builder that will build 30 TextFormField, if that right, you only need to put the ListView.builder inside The Form like this:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
List validators = [
(value) {
if (value == null || value.isEmpty) {
return 'Please enter the number';
}
return null;
},
(value) {
if (value == null || value.isEmpty) {
return 'Please enter the email';
}
return null;
},
(value) {
if (value == null || value.isEmpty) {
return 'Please enter the password';
}
return null;
},
];
List formatters = [
FilteringTextInputFormatter.digitsOnly,
FilteringTextInputFormatter.allow(RegExp('[a-zA-Z]')),
FilteringTextInputFormatter.deny(RegExp(r'[/\\]'))
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
child: ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return TextFormField(
validator: validators[index],
inputFormatters: [formatters[index]],
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Text('Click'),
onPressed: () {
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(SnackBar(content: Text('Processing Data')));
}
},
),
);
}
}
This will check if all the 30 TextFormField are not empty.

Flutter Form Validation with nested Widgets

So for my Registration Screen, I have two TextFormField the user has to fill out: email and password. Because I use the same Styling etc I wanted to refactor the TextFormField into a seperate Widget. In the Main Widget, when the user presses the Register Button, I want to validate all three Fields. I have tried it with a GlobalKey, but I get the Error message "Multiple widgets used the same GlobalKey."
Here is my Registration Screen code:
class RegistrationScreen extends StatefulWidget {
#override
_RegistrationScreenState createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
String email;
String password;
final formKey = GlobalKey<FormState>();
void handleRegisterEmail(){
if (formKey.currentState.validate()){
print('Handling Register');
}
}
void handleEmailChanged(String value) {
setState(() {
email = value;
});
}
void handlePasswordChanged(String value) {
setState(() {
password = value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
AuthTextField(
key: formKey,
type: 'Email',
onChanged: handleEmailChanged,
),
AuthTextField(
key: formKey
type: 'Password',
onChanged: handlePasswordChanged,
),
AuthButton(
text: 'Register',
onPressed: handleRegisterEmail,
),
]
),
);
}
}
And here is the code for the AuthTextField:
class AuthTextField extends StatefulWidget {
final String emailOrPassword;
final Function onChanged;
final GlobalKey<FormState> key; //Here passing the key
AuthTextField({this.onChanged, this.emailOrPassword, this.key})
: super(key: key);
#override
_AuthTextFieldState createState() => _AuthTextFieldState();
}
class _AuthTextFieldState extends State<AuthTextField> {
String validateForm(String value) { //two different validators
if (widget.emailOrPassword == 'Email') {
validateFormEmail(value);
} else {
validateFormPassword(value);
}
}
String validateFormEmail(String value) {
if (!RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value)) {
return 'Please enter a valid email address';
}
return null;
}
String validateFormPassword(String value) {
if (value.length < 6) {
return 'Password must be at least 6 characters.';
}
return null;
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 30.0),
child: TextFormField(
key: widget.key, //Passing the key
validator: validateForm,
keyboardType: (widget.emailOrPassword == 'Email')
? TextInputType.emailAddress
: TextInputType.text,
onChanged: widget.onChanged,
//plus some more styling
),
),
);
}
}
And as I said I get and error message. Any suggestion how to solve this? I tried a bunch of different things (e.g. Wrapping the column in RegistrationScreen in a Form and passing the key only to that form) but nothing work.
The issue here is that you are setting the key of the AuthTextFields instead of storing it in as a separate field on the object. This is setting it as the key for both the AuthTextField and the Form object.
This is what you would want to do instead:
class AuthTextField extends StatefulWidget {
final String emailOrPassword;
final Function onChanged;
final GlobalKey<FormState> formKey; //Here passing the key
AuthTextField({Key key, this.onChanged, this.emailOrPassword, this.formKey})
: super(key: key);
#override
_AuthTextFieldState createState() => _AuthTextFieldState();
}
class _AuthTextFieldState extends State<AuthTextField> {
String validateForm(String value) { //two different validators
if (widget.emailOrPassword == 'Email') {
validateFormEmail(value);
} else {
validateFormPassword(value);
}
}
String validateFormEmail(String value) {
if (!RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value)) {
return 'Please enter a valid email address';
}
return null;
}
String validateFormPassword(String value) {
if (value.length < 6) {
return 'Password must be at least 6 characters.';
}
return null;
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 30.0),
child: TextFormField(
//key: widget.key, //Don't pass the key to the TextFormField only the form
validator: validateForm,
keyboardType: (widget.emailOrPassword == 'Email')
? TextInputType.emailAddress
: TextInputType.text,
onChanged: widget.onChanged,
//plus some more styling
),
),
);
}
}
Then you need wrap your widgets in a Form widget:
class RegistrationScreen extends StatefulWidget {
#override
_RegistrationScreenState createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
String email;
String password;
final formKey = GlobalKey<FormState>();
void handleRegisterEmail(){
if (formKey.currentState.validate()){
print('Handling Register');
}
}
void handleEmailChanged(String value) {
setState(() {
email = value;
});
}
void handlePasswordChanged(String value) {
setState(() {
password = value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: formKey, //Wrap your column in this form widget and set the key
child: Column(
children: <Widget>[
AuthTextField(
key: formKey,
type: 'Email',
onChanged: handleEmailChanged,
),
AuthTextField(
key: formKey
type: 'Password',
onChanged: handlePasswordChanged,
),
AuthButton(
text: 'Register',
onPressed: handleRegisterEmail,
),
]
),
),
);
}
}

Flutter Forms: Get the list of fields in error

Can we get the list of fields in error from Flutter forms after validation ? This will help developers use focus-nodes to redirect the attention to the field in error.
I don't think it is possible to get this kind of information from a Form object or a FormState.
But here is a way around to obtain the result you want (focus on the field in error) :
class _MyWidgetState extends State<MyWidget> {
FocusNode _fieldToFocus;
List<FocusNode> _focusNodes;
final _formKey = GlobalKey<FormState>();
final _numberOfFields = 3;
String _emptyFieldValidator(String val, FocusNode focusNode) {
if (val.isEmpty) {
_fieldToFocus ??= focusNode;
return 'This field cannot be empty';
}
return null;
}
#override
void initState() {
super.initState();
_focusNodes =
List<FocusNode>.generate(_numberOfFields, (index) => FocusNode());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(actions: [
IconButton(
icon: Icon(Icons.check),
onPressed: () {
if (_formKey.currentState.validate()) {
print('Valid form');
} else {
_fieldToFocus?.requestFocus();
_fieldToFocus = null;
}
},
),
]),
body: Form(
key: _formKey,
child: Column(children: [
...List<TextFormField>.generate(
_numberOfFields,
(index) => TextFormField(
decoration: InputDecoration(hintText: "Field $index"),
focusNode: _focusNodes[index],
validator: (val) => _emptyFieldValidator(val, _focusNodes[index]),
),
),
]),
),
);
}
}
You simply need to create a FocusNode for each one of your fields, thanks to that you will be abla to call requestFocus on a precise field (in your case a field considered as invalid). Then in the validator property of your form field, as it is the method called by the FormState.validate(), you need to set a temporary variable which will contains the right FocusNode. In my example I only set the variable _fieldToFocus if it was not already assigned using the ??= operator. After requesting the focus on the node I set _fieldToFocus back to null so it will still works for another validation.
You can try the full test code I have used on DartPad.
Sorry if I have derived a bit from your question but I still hope this will help you.
Expanding on Guillaume's answer, I've wrapped the functionality into a reusable class.
You can view a working example on DartPad here: https://www.dartpad.dev/61c4ccddbf29a343c971ee75e60d1038
import 'package:flutter/material.dart';
class FormValidationManager {
final _fieldStates = Map<String, FormFieldValidationState>();
FocusNode getFocusNodeForField(key) {
_ensureExists(key);
return _fieldStates[key].focusNode;
}
FormFieldValidator<T> wrapValidator<T>(String key, FormFieldValidator<T> validator) {
_ensureExists(key);
return (input) {
final result = validator(input);
_fieldStates[key].hasError = (result?.isNotEmpty ?? false);
return result;
};
}
List<FormFieldValidationState> get erroredFields =>
_fieldStates.entries.where((s) => s.value.hasError).map((s) => s.value).toList();
void _ensureExists(String key) {
_fieldStates[key] ??= FormFieldValidationState(key: key);
}
void dispose() {
_fieldStates.entries.forEach((s) {
s.value.focusNode.dispose();
});
}
}
class FormFieldValidationState {
final String key;
bool hasError;
FocusNode focusNode;
FormFieldValidationState({#required this.key})
: hasError = false,
focusNode = FocusNode();
}
To use it, create your forms as usual, but add a FormValidationManager to your state class, and then use that instance to wrap your validation methods.
Usage:
class _MyWidgetState extends State<MyWidget> {
final _formKey = GlobalKey<FormState>();
final _formValidationManager = FormValidationManager();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
focusNode: _formValidationManager.getFocusNodeForField('field1'),
validator: _formValidationManager.wrapValidator('field1', (value) {
if (value.isEmpty) {
return 'Please enter a value';
}
return null;
})),
TextFormField(
focusNode: _formValidationManager.getFocusNodeForField('field2'),
validator: _formValidationManager.wrapValidator('field2', (value) {
if (value.isEmpty) {
return 'Please enter a value';
}
return null;
})),
ElevatedButton(
onPressed: () {
if (!_formKey.currentState.validate()) {
_formValidationManager.erroredFields.first.focusNode.requestFocus();
}
},
child: Text('SUBMIT'))
],
),
);
}
#override
void dispose() {
_formValidationManager.dispose();
super.dispose();
}
}