I have Form and TextFormField inside it :
new Expanded(
child: TextFormField(
style: new TextStyle(color: Colors.white),
keyboardType: TextInputType.text,
validator: (String value) {
if (value.length <= 5) {
//Show error as a Snackbar
}
},
onSaved: (String value) {},
),
)
On a Buttom press I am checking if all the fields are validate :
if (_formKey.currentState.validate()) {
_submit();
}
Now the issue is when you call validate() and don't return any text in validate() method then it will consider it return true.
I don't want to show error below the textField but as a Snackbar.
Also, I tried setting an extra flag and setting in each validator Method but it gets complex if there are multiple fields in the form.
Can anyone tell me how can I handle this situation where _formKey.currentState.validate() should return false and validator method
inside TextFormField need not to return error text.
You shouldn't be using Form widget and TextFormField for displaying error in TextField.
Do validation by controllers instead
For Example
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() {
return new MyHomePageState();
}
}
class MyHomePageState extends State<MyHomePage> {
final _text = TextEditingController();
bool _validate = false;
#override
void dispose() {
_text.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Error Showed if Field is Empty on Submit button Pressed'),
TextField(
controller: _text,
decoration: InputDecoration(
labelText: 'Enter the Value',
),
),
RaisedButton(
onPressed: () {
if(_text.text.length<=5){
// open dialog
}
},
child: Text('Submit'),
textColor: Colors.white,
color: Colors.blueAccent,
)
],
),
),
);
}
}
Related
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(),
),
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(()
{
...
});
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 have textformfield i want to get access to what it shows
like when i print 10000 i want it to show and separate the int like 10,000 , i can print it with regex but i want to show it in text form field too
here us what i got in terminal (left) and what it shows in text field (right)
enter image description here
and all i wanna do is show what is in terminal to text field\
.
if you need more information please let me know
here is my code
import 'package:flutter/material.dart';
class AddTransication extends StatefulWidget {
#override
_AddTransicationState createState() => _AddTransicationState();
}
class _AddTransicationState extends State<AddTransication> {
RegExp reg_ex = new RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))');
Function mathFunc = (Match match) => '${match[1]},';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test Screen"),
actions: <Widget>[
FlatButton(
textColor: Colors.white,
onPressed: () {},
child: Text("Save"),
shape: CircleBorder(side: BorderSide(color: Colors.transparent)),
),
],
),
body: SafeArea(
child: Form(
child: Column(
children: [emailField(reg_ex, mathFunc)],
),
),
),
);
}
Widget emailField(reg_ex, mathFunc) {
return TextFormField(
onChanged: (str) {
String result = str.replaceAllMapped(reg_ex, mathFunc);
print(' $result');
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email Address',
hintText: 'you#example.com',
),
);
}
}
You have to use TextEditingController class.
A controller for an editable text field.
Whenever the user modifies a text field with an associated TextEditingController, the text field updates value and the controller notifies its listeners. Listeners can then read the text and selection properties to learn what the user has typed or how the selection has been updated.
Refer : https://api.flutter.dev/flutter/widgets/TextEditingController-class.html
Please check the code, below I have updated it to work as per your question.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: AddTransication(),
);
}
}
class AddTransication extends StatefulWidget {
#override
_AddTransicationState createState() => _AddTransicationState();
}
class _AddTransicationState extends State<AddTransication> {
final _controller = TextEditingController();
RegExp regex = new RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))');
Function mathFunc = (Match match) => '${match[1]},';
void initState() {
super.initState();
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test Screen"),
actions: <Widget>[
FlatButton(
textColor: Colors.white,
onPressed: () {},
child: Text("Save"),
shape: CircleBorder(side: BorderSide(color: Colors.transparent)),
),
],
),
body: SafeArea(
child: Form(
child: Column(
children: [emailField(regex, mathFunc)],
),
),
),
);
}
Widget emailField(regex, mathFunc) {
return TextFormField(
controller: _controller,
onChanged: (str) {
String text = str.replaceAll(",", "").replaceAllMapped(regex, mathFunc);
print(' $text');
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email Address',
hintText: 'you#example.com',
),
);
}
}
i would like to change the color of my submit button when the user starts to edit the textformfield, here is what i have tried to do :
final _text = TextEditingController();
the textformfield where my controller is implemented
TextFormField(
controller: _text,
and finally my submit button
(_text.text.isNotEmpty) ?
new RaisedButton(
color: mmpataColorBlue,
child: new Text(
"Valider",
style: TextStyle(color: Colors.white),
),
onPressed: () async {
await createOrder(vm);
},
)
:
new RaisedButton(
color: Colors.grey,
child: new Text(
"Valider",
style: TextStyle(color: Colors.white),
),
onPressed: () async {
await createOrder(vm);
},
)
and i got this error while running the code : Another exception was thrown: 'package:flutter/src/material/text_form_field.dart': Failed assertion: line 176
pos 15: 'initialValue == null || controller == null': is not true.
from the message error, i think you did set up an initialvalue for the textforifield, this is not possible since you have a controller, instead use :
_text.text = 'your initial value';
The way this will work is by adding a listener to the controller. Every time the controller value changes, it will trigger the function on the listener (which in this case calls setState). The RaisedButton will then check is the controller value is empty and proceed to display the corresponding color.
class TestPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestPageState();
}
}
class TestPageState extends State<TestPage> {
TextEditingController _myController = TextEditingController();
#override
void initState() {
super.initState();
_myController.text = '<initial value>';
_myController.addListener(() {
setState(() {}); // setState every time text changes
});
}
#override
void dispose() {
_myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Row(
children: <Widget>[
SizedBox(
height: 32.0,
width: 200.0,
child: TextFormField(
controller: _myController,
),
),
RaisedButton(
onPressed: () {
print('pressed');
},
color: (_myController.text.isEmpty) ? Colors.blue : Colors.green,
child: Text('test'),
)
],
),
),
);
}
}
I think you could use validator for this. You could use a variable to define the color of the button and use setState in the validator to change it.