How to stick validation text inside TextFormField. By default it goes below
the text field
I want like this
But it shows like this
I have looked in various sources, but did not find a suitable answer
is there any way to show the validation text inside the textbox
You can update text from TextEditingController if validation fails for a certain text field and also can remove text from controller in "onTap" property.
TextEditingController _passwordController = TextEditingController();
if(condition)
{
//success call
}
else
{
setState((){
_passwordController.text="Password Does not match
});
}
you should validate your form in the Following way
class MyForm extends StatefulWidget {
#override
MyFormState createState() {
return MyFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyFormState extends State<MyForm> {
// 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<MyFormState>.
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: <Widget>[
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.
// sendData();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
}
},
child: Text('Submit'),
),
),
],
),
);
}
}
Related
How to view validation error in the top of screen using textform field in Flutter
the validation only viewed under the text field and I need to view them in the top of the screen
There's no default way to show a validation error of a form field on the top of the page. However, you can add some code to the validation function to show a snackBar or a toast in case validation fails.
You can do something like this, define two variables, say _error one of a boolean type to store error flag for field validation and the other one, say errorMsg of String type to store the error message. And in the validation method of the form, change the values depending on the user inputted value, if the input is empty change the value of the error flag to true and the errorMsg to some error text of your choice. Now, in your UI part show the errorMsg text at a position in your UI if the error flag is true.
I have given a complete example below:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: 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({Key? key}) : super(key: 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>();
bool _error = false;
late String _errorMsg;
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Column(children: [
(_error ? Text(_errorMsg) : Container()), // show the error message
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) {
setState(() {
_error = true;
_errorMsg = 'Please enter some text';
});
return '';
}
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'),
),
),
],
))],
);
}
}
I have a Posts screen where list of posts are displayed. I want to add a textformfield at the bottom area of each post so that users can enter their comments on particular post. As we know that we cannot create a TextEditingController for multiple textformfields.
How can I create, show and use textformfield with the each post for entering users comments?
I am sure there are multiple ways to do this, but here is one fairly simple approach.
Create a Map where the key is a TextFormField and the value is a TextEditingController.
Map<TextFormField, TextEditingController> fields = new Map<TextFormField, TextEditingController>();
Create a new controller and text field for each post and add them to the Map.
fields[text] = controller;
When you want to get the values, you can pass the text field into a method that gets the controller for that field from the Map.
Here is a very basic example of how this can work:
import 'package:flutter/material.dart';
class DynamicTextFieldsWithControllers extends StatefulWidget {
#override
State<StatefulWidget> createState() => DynamicTextFieldsWithControllersState();
}
class DynamicTextFieldsWithControllersState extends State<DynamicTextFieldsWithControllers> {
Map<TextFormField, TextEditingController> fields = new Map<TextFormField, TextEditingController>();
#override
Widget build(BuildContext context) {
List<Widget> widgets = [];
for(int i = 0; i < 5; i++) {
TextEditingController controller = new TextEditingController();
TextFormField text = TextFormField(
controller: controller,
);
fields[text] = controller;
widgets.add(Container(
margin: EdgeInsets.symmetric(horizontal: 8.0, vertical: 15.0),
child: Column(children: [
text,
FlatButton(
color: Colors.blue,
child: Text("Submit", style: TextStyle(color: Colors.white)),
onPressed: () {
getTextValue(text);
},
)
],)
));
}
return Scaffold(
body: Column(children: widgets),
);
}
getTextValue(TextFormField text) {
TextEditingController controller = fields[text];
String value = controller.text;
print(value);
return value;
}
}
I wish to validate if the values within 2 TextFormFields matches.
I could validate them individually.
But how could I capture both those values to validate by comparing?
import 'package:flutter/material.dart';
class RegisterForm extends StatefulWidget {
#override
_RegisterFormState createState() => _RegisterFormState();
}
class _RegisterFormState extends State<RegisterForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Stack(
children: [
Container(
padding: EdgeInsets.all(20),
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: 'Password'
),
validator: (value) {
// I want to compare this value against the TextFormField below.
if(value.isEmpty){
return 'is empty';
}
return value;
},
),
TextFormField(
decoration: InputDecoration(
hintText: 'Confirm Password'
),
validator: (value) {
if(value.isEmpty){
return 'is empty';
}
return value;
},
),
RaisedButton(
onPressed: (){
if (_formKey.currentState.validate()) {
print('ok');
} else {
print('not ok');
}
},
),
],
),
)
],
),
);
}
}
One possible solution as follows.
I could store them as values within _RegisterFormState and retrieve them within the validate blocks. But is there a cleaner way to achieve this?
class _RegisterFormState extends State<RegisterForm> {
final _formKey = GlobalKey<FormState>();
String password;
String confirmPassword;
.....
TextFormField(
decoration: InputDecoration(
hintText: 'Password'
),
validator: (value) {
// I want to compare this value against the TextFormField below.
if(value.isEmpty){
setState(() {
password = value;
});
performValidation(password, confirmPassword); // some custom validation method
return 'is empty';
}
return value;
},
),
.....
}
P.S: If there would be a better way to do it via a state management tool, I am using Provider. Not looking for Bloc solutions.
There are few steps I have performed to achieve this. You can refer that to achieve your goal.
Create a stateful widget and return a InputBox from there
add a property named as callback and set it's datatype as
ValueSetter callback;
assign this callback to onChanged Event of the input box
onChanged: (text) {
widget.callback(text);
},
Use your custom widget in the class where you want to use the input box
while using your widget pass a callback to it
InputWithLabel(
callback: (value) {
password = value;
},
),
InputWithLabel(
callback: (value) {
confirmPassword = value;
},
),
and at last, we have to compare those values,
you can bind a key to your form add use it in saved event of it. You can wrap it in Button's callback
if (InputValidation.validatePasswordandConfirm(password, cpassword)) {
// your after form code
}
To have it a real time comparison mark one of the input's callback in setState(){} and create a new property in your Custom Text Widget named as compareTxt;
and on validator check for compare text and return the error message
validator: (text) {
if (widget.comaparetext != text) {
return 'Password does not match';
}
I had the same problem but I was using custom TextFormField widgets.
So this is how I achieved it:
Problem:
I had two TextFormField widgets in a Form and I wanted to compare the values
of those two TextFormWidgets, to check whether the value of one filed is greater
then the other.
Solution:
I defined two controllers globally, because the TextFormWidgets were in a separate class.
final _startingRollNumberController = TextEditingController();
final _endingRollNumberController = TextEditingController();
Passed these controllers to the instances of TextFormField in the Form class.
CustomTextField('Starting R#', _startingRollNumberController),
CustomTextField('Ending R#', _endingRollNumberController),
And validated them in the TextFormField class.
class CustomTextField extends StatelessWidget {
// *===== CustomTextField class =====* //
CustomTextField(this._hintText, this._controller);
final String _hintText;
final _controller;
#override
Widget build(BuildContext context) {
return TextFormField(
validator: (value) {
final _startingRollNumber = _startingRollNumberController.text;
final _endingRollNumber = _endingRollNumberController.text;
// Starting roll-number needs to be smaller then the ending roll-number.
if (!_startingRollNumber.isEmpty && !_endingRollNumber.isEmpty) {
if (int.parse(_startingRollNumber) >= int.parse(_endingRollNumber)) {
return 'starting roll number must be smaller.';
}
}
},
);
}
}
This may not be the best approach but this is how I achieved it. There is
also, this flutter_form_builder package which can be used to achieve this easily using the form key.
A related question: Flutter: Best way to get all values in a form
Currently, when a user fills in a TextField the text is lost if they navigate away and then return. How can I get the text to stay in the field upon their return?
Here's the stateful widget I'm using;
class _EditPageState extends State<EditPage> {
final _formKey = GlobalKey<FormState>();
String audiotitle;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
child: Container(
child: TextField(
decoration: new InputDecoration(
hintText: widget.oldaudiotitle,
),
keyboardType: TextInputType.text,
onChanged: (titleText) {
setState(() {
this.audiotitle = titleText;
});
},
),
),
),
);
}
}
What am I doing wrong here?
you have two ways :
store the Data of the text field and set the data in init method
(use sharedpreferences or any other database as per your requirements)
TextEditingController controller = TextEditingController();
#override
void initState() {
// TODO: implement initState
// retrive the Data
if(data != null) {
controller = new TextEditingController(text: data);
}
}
or if the first screen is navigating in the second Screen than just pop that screen
Navigator.pop(context);
I am quite new to Flutter, and I am struggling a bit to create a custom Form Field. The issue is that neither the validator nor the onSaved method from my custom FormField are called. I really am clueless on why they get ignored when I trigger a formKey.currentState.validate() or formKey.currentState.save().
This is a pretty simple widget for now, with an input text and a button.
The button will fetch the current location of the user, and update the text field with the current address.
When the user inputs an address in the text field, it will fetch the location for that address on focus lost (I have also integration with Google Maps, but I simplified it to isolate the issue).
Here is the constructor of my form field :
class LocationFormField extends FormField<LocationData> {
LocationFormField(
{FormFieldSetter<LocationData> onSaved,
FormFieldValidator<LocationData> validator,
LocationData initialValue,
bool autovalidate = false})
: super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidate: autovalidate,
builder: (FormFieldState<LocationData> state) {
return state.build(state.context);
});
#override
FormFieldState<LocationData> createState() {
return _LocationFormFieldState();
}
}
As I need to handle state in my custom FormField, I build it in the FormFieldState object. The location state is updated when the button is pressed :
class _LocationFormFieldState extends FormFieldState<LocationData> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextField(
focusNode: _addressInputFocusNode,
controller: _addressInputController,
decoration: InputDecoration(labelText: 'Address'),
),
SizedBox(height: 10.0),
FlatButton(
color: Colors.deepPurpleAccent,
textColor: Colors.white,
child: Text('Locate me !'),
onPressed: _updateLocation,
),
],
);
}
void _updateLocation() async {
print('current value: ${this.value}');
final double latitude = 45.632;
final double longitude = 17.457;
final String formattedAddress = await _getAddress(latitude, longitude);
print(formattedAddress);
if (formattedAddress != null) {
final LocationData locationData = LocationData(
address: formattedAddress,
latitude: latitude,
longitude: longitude);
_addressInputController.text = locationData.address;
// save data in form
this.didChange(locationData);
print('New location: ' + locationData.toString());
print('current value: ${this.value}');
}
}
This is how I instantiate it in my app. Nothing special here; I put it in a Form with a form key. There is another TextFormField to verify that this one is working fine:
main.dart
Widget _buildLocationField() {
return LocationFormField(
initialValue: null,
validator: (LocationData value) {
print('validator location');
if (value.address == null || value.address.isEmpty) {
return 'No valid location found';
}
},
onSaved: (LocationData value) {
print('location saved: $value');
_formData['location'] = value;
},
); // LocationFormField
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Container(
margin: EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
child: Column(
children: <Widget>[
_buildTitleTextField(),
SizedBox(
height: 10.0,
),
_buildLocationField(),
SizedBox(
height: 10.0,
),
_buildSubmitButton(),
],
),
),
),
),
),
);
}
The submit method triggered by the form submit button will just try to validate then save the form.
Just printing the data saved in the form:
void _submitForm() {
print('formdata : $_formData');
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
print('formdata : $_formData');
}
But _formData['location'] always returns null, and the validator is never called (no 'validator location' or 'location saved' printed in logs).
I created a sample repo to reproduce this issue. You can try running the project, click first on the Locate me ! button, then the Save button at https://github.com/manumura/flutter-location-form-field
Answer 1: Put the build method for the Builder
Replace the FormField's builder
builder: (FormFieldState<LocationData> state) {
return state.build(state.context);
});
with your custom builder function
builder: (FormFieldState<LocationData> state) {
return Column(
children: <Widget>[
TextField(
focusNode: _addressInputFocusNode,
controller: _addressInputController,
decoration: InputDecoration(labelText: 'Address'),
),
SizedBox(height: 10.0),
FlatButton(
color: Colors.deepPurpleAccent,
textColor: Colors.white,
child: Text('Locate me !'),
onPressed: _updateLocation,
),
],
});
Answer 2: Pseudo CustomFormFieldState
You can't extend the FormFieldState because overriding the "build" function causes errors (explained below)
However you can create a Widget that takes the FormFieldState as a parameter to make it a separate class to act like it extends the FormFieldState (which seems a bit cleaner to me then the above method)
class CustomFormField extends FormField<List<String>> {
CustomFormField({
List<String> initialValue,
FormFieldSetter<List<String>> onSaved,
FormFieldValidator<List<String>> validator,
}) : super(
autovalidate: false,
onSaved: onSaved,
validator: validator,
initialValue: initialValue ?? List(),
builder: (state) {
return CustomFormFieldState(state);
});
}
class CustomFormFieldState extends StatelessWidget {
FormFieldState<List<String>> state;
CustomFormFieldState(this.state);
#override
Widget build(BuildContext context) {
return Container(), //The Widget(s) to build your form field
}
}
Explanation
The reason why extending the FormFieldState doesn't work is because overriding the build method in the FormFieldState object causes the FormFieldState to not be registered with the Form itself.
Below is a list of functions that I followed to get my explanation
1) Your _LocationFormFieldState overrides the build method which means the build method of the FormFieldState never executes
#override
Widget build(BuildContext context)
2) The build method the FormFieldState registers itself to the current FormState
///function in FormFieldState
Widget build(BuildContext context) {
// Only autovalidate if the widget is also enabled
if (widget.autovalidate && widget.enabled)
_validate();
Form.of(context)?._register(this);
return widget.builder(this);
}
3) The FormState then saves the FormFieldState in a List
void _register(FormFieldState<dynamic> field) {
_fields.add(field);
}
4) Then when the FormState saves/validates it loops through the list of FormFieldStates
/// Saves every [FormField] that is a descendant of this [Form].
void save() {
for (FormFieldState<dynamic> field in _fields)
field.save();
}
By overriding the build method you cause the FormField to not be registered with the Form, which is why saving and loading the Form doesn't call the methods of your custom FormField.
If the FormState._register() method was public instead of private you could call this method in your _LocationFormFieldState.build method to register your app to the form, but sadly since it is a private function you cannot.
Also note that if you were to call the super.build() function in your CustomFormFieldState's build method it leads to a StackOverflow
#override
Widget build(BuildContext context) {
super.build(context); //leads to StackOverflow!
return _buildFormField(); //anything you want
}
This is happening because you have overridden the build() method in _LocationFormFieldState. When you override this method, out of the box mechanism for registering the custom form field & validation with the form is also overridden. Therefore, the field is not getting registered and the onSaved()and validate() methods are not called automatically.
In your _LocationFormFieldState class, copy the contents of the Widget build(BuildContext context) methods into a new method. Let's call it Widget _constructWidget(). Since, we are in a stateful class, the context object will be implicitly present.
Next, remove the Widget build(BuildContext context) entirely from the _LocationFormFieldState class. Since we have removed the override, the superclass build method will be called, which will do the registration of this custom form field with the parent form for us.
Now, in the LocationFormFieldState constructor, replace:
builder: (FormFieldState<LocationData> state) {
return state.build(state.context);
});
With
builder: (FormFieldState<LocationData> state) {
return (state as _LocationFormFieldState)._constructWidget();
});
Here, we typecasted the FormFieldState to _LocationFormFieldState using the as operator and called our custom _constructWidget() method which returns the widget tree (which was previously in the overridden build())
Had the same problem. For me it worked when I replaced
return state.build(state.context);
With actual code from the build method and removed the build method override from the state.
For all viewing this question late like me, here's an actual simple and clean solution:
Put all the TextFields you want to validate and save inside a Form Widget and then use a GlobalKey to save and validate all fields together:
final _formKey = GlobalKey<FormState>();
//our save function that gets triggered when pressing a button for example
void save() {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
//add your code here
}
}
//code inside buildMethod:
Form(
key: _formKey,
child: TextFormField(
onSaved: (String value) { //code that gets executed with _formkey.currentState.save()
setState(() {
text = value;
});
},
),
)
This solution won't trigger unwanted rebuilds of the StatefulWidget because the values only get updated once the user triggers via the button.