TextFormField loses value - flutter

I have a PageView having 4 children/pages/steps.
On the first page I have a TextFormField. The user is obligated to provide a value.
When following exactly these steps, wrongly it will result in an error message for the user 'Please provide a name':
page is created
type a value into the TextFormField
Tap 'Done' on the soft keyboard
Tap the Next-button to go to page 2 of the PageView
Tap the Back-button to go to page 1 of the PageView
Tap the Next-button to go to page 2 of the PageView: now 'Please provide a name' is shown to the user. The typed name is still visible on the page, but its value in its validator is empty.
Deviation from the above will not cause the error message. Relevant code:
class CreateWishlist extends StatefulWidget {
#override
_CreateWishlistState createState() => _CreateWishlistState();
}
class _CreateWishlistState extends State<CreateWishlist> {
...
final myTitleController = TextEditingController();
final _form1 = GlobalKey<FormState>();
Form(
key: _form1,
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(hintText: 'Name'),
textInputAction: TextInputAction.done,
onSaved: (value) {
_createWishlistData['title'] = value.trim();
},
controller: myTitleController,
validator: (value) {
if (value.trim().isEmpty) return 'Please provide a name';
return null;
},
),
],
),
),
next(int pageIndex) {
if (pageIndex == 0) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
if (!_form1.currentState.validate()) {
return;
}
_form1.currentState.save();
}
controller.animateToPage(pageIndex + 1,
duration: Duration(seconds: 1), curve: Curves.ease);
}

I tried as described in the comments above, but still the same. Eventually I managed to avoid the problem by overwriting the value on the validator and on onSaved by the controller-value.
TextFormField(
style: formFieldTextStyle,
decoration: InputDecoration(hintText: 'Name'),
textInputAction: TextInputAction.done,
onSaved: (value) {
value = myTitleController.text;
_createWishlistData['title'] = value.trim();
},
controller: myTitleController,
validator: (value) {
value = myTitleController.text;
if (value.trim().isEmpty) return 'Please provide a name';
return null;
},
),

Related

Best way to validate a form with flutter provider package?

I need separate logic with UI.
I used the following example:
1.- Use a class validation item to show a string value and error.
class ValidationItem {
final String value;
final String error;
ValidationItem(this.value, this.error);
}
2.- Use the next code for provider class.
class SignupValidation with ChangeNotifier {
ValidationItem _firstName = ValidationItem(null,null);
//Getters
ValidationItem get firstName => _firstName;
bool get isValid {
if (_firstName.value != null){
return true;
} else {
return false;
}
}
//Setters
void changeFirstName(String value){
if (value.length >= 3){
_firstName=ValidationItem(value,null);
} else {
_firstName=ValidationItem(null, "Must be at least 3 characters");
}
notifyListeners();
}
void submitData(){
print("FirstName: ${firstName.value}");
}
}
3.- Use the next widget to show text field and validate
class Signup extends StatelessWidget {
#override
Widget build(BuildContext context) {
final validationService = Provider.of<SignupValidation>(context);
return Scaffold(
appBar: AppBar(
title: Text('Signup'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
TextField(
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
onChanged: (String value) {
validationService.changeFirstName(value);
},
),
RaisedButton(
child: Text('Submit'),
onPressed: (!validationService.isValid) ? null : validationService.submitData,
)
],
),
),
);
}
}
The problem is the performance for example every time the text is changed the notifyListener() Is calles.
My question: Is there a cost to performance?
you can use TextFormField instead of TextField.
Best way to validate fields is that you can use validator property TextFormField property as bellow
TextFormField(
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
validator: (e){
if(e!.trim().isEmpty) return "String is empty";
return null;
},
onChanged: (String value) {
validationService.changeFirstName(value);
},
),
The TextField itself gives you the ability to validate the form, then why to make it complex by implementing notifier, instead you want to make it common you can make the validater global function for it. Nad user it in the validate function.
Void validateEmail(String value){ // your logic}
Use this function as follow
TextFormField(
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
validator: validateEmail(),
onChanged: (String value) {
validationService.changeFirstName(value);
},
Secondly to get the value of inputted string you have a TextEditingController which directly give you the string you inputted.
Declare TextEditingController as follow
TextEditingController emailCont = TextEditingController();
Use this controller in TextField as follow
TextFormField(
controller: emailCont,
decoration: InputDecoration(
labelText: "First Name",
errorText: validationService.firstName.error,
),
validator: validateEmail(),
onChanged: (String value) {
validationService.changeFirstName(value);
},
Now to get the value from this controller you can get it this way.
emailCont.text
This way it will be easy to manage and less complexity.

onEditingComplete is not called after unfocus

I have a TextField like this. The additional code is necessary to show that in different situations, I do various focus manipulation.
final node = FocusScope.of(context);
Function cleanInput = () => {controller.text = controller.text.trim()};
Function onEditingComplete;
Function onSubmitted
TextInputAction textInputAction;
if (!isLast) {
onEditingComplete = () => {
cleanInput(),
node.nextFocus(),
};
onSubmitted = (_) => {cleanInput()};
textInputAction = TextInputAction.next;
} else {
onEditingComplete = () => {
cleanInput(),
};
onSubmitted = (_) => {
cleanInput(),
node.unfocus(),
};
textInputAction = TextInputAction.done;
}
Widget textInput = TextField(
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
As you can see, I have functions I want to run onEditingComplete. However, this only gets called when I press the Next or Done buttons on my keyboard (or the Enter key in an emulator). If I change focus by tapping on a different field, this function does not get called.
I have tried using a Focus or FocusNode to help with this, but when I do so, the onEditingComplete function itself no longer works.
How can I get the desired effect here while everything plays nicely together?
Focus widget
Wrapping fields in a Focus widget might do the trick.
The Focus widget will capture focus loss events for children. With its onFocusChange argument you can call arbitrary functions.
Meanwhile, the onEditingComplete argument of TextField is unaffected and will still be called on the software keyboard "Next/Done" keypress.
This should handle field focus loss for both "Next/Done" keypress and user tapping on another field.
import 'package:flutter/material.dart';
class TextFieldFocusPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// ↓ Add this wrapper
Focus(
child: TextField(
autofocus: true,
decoration: InputDecoration(
labelText: 'Name'
),
textInputAction: TextInputAction.next,
// ↓ Handle focus change on Next / Done soft keyboard keys
onEditingComplete: () {
print('Name editing complete');
FocusScope.of(context).nextFocus();
},
),
canRequestFocus: false,
// ↓ Focus widget handler e.g. user taps elsewhere
onFocusChange: (hasFocus) {
hasFocus ? print('Name GAINED focus') : print('Name LOST focus');
},
),
TextField(
decoration: InputDecoration(
labelText: 'Password'
),
),
],
),
),
),
);
}
}
Please add a focus node to your textfield and add a listener to your focus node to trigger when it unfocuses
final node = FocusScope.of(context);
node.addListener(_handleFocusChange);
void _handleFocusChange() {
if (node.hasFocus != _focused) {
setState(() {
_focused = node.hasFocus;
});
}
}
Widget textInput = TextField(
//you missed this line of code
focusNode: node,
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
And also you can validete automatically by adding autoValidate to your code like below:
Widget textInput = TextField(
//add this line of code to auto validate
autoValidate: true,
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
FocusNode _node;
bool _focused = false;
#override
void initState() {
super.initState();
_node.addListener(_handleFocusChange);
}
void _handleFocusChange() {
if (_node.hasFocus != _focused) {
setState(() {
_focused = _node.hasFocus;
});
}
}
#override
void dispose() {
_node.removeListener(_handleFocusChange);
_node.dispose();
super.dispose();
}
TextFormField(
focusNode: _node)

TextFormField enters value backwards [Flutter]

As displayed in the GIF below, the TextFormField i am using is entering the values backwards. I have set the TextDirection property to ltr as well but it did not change anything. Other TextFormFields dont seem to be having this issue. The entered text is being sent back to another screen using Navigator.pop and it is sent in the same backwards manned.
Weird TextFormField
Code for the textFormField causing the issue:
TextFormField(
// validator: (String value) {
// return value.isEmpty ? "task must have a name" : null;
// },
textDirection: TextDirection.ltr,
maxLength: 100,
controller: newcontroller, // Just an ordinary TextController
onChanged: (value) {
setState(() {
newcontroller.text = value;
});
},
decoration: InputDecoration(
errorText: _validate // Just a boolean value set to false by default
? 'Value Can\'t Be Empty' : null,
labelText: "name of task"
),
style: TextStyle(height: 1.2, fontSize: 20, color: Colors.black87)
)
You don't have to set text in newcontroller.text when onChanged is called.
text enter in your TextFormField is assigned by default to newcontroller.
You are getting this error because for this piece of code,
So, try to remove below code
setState(() {
newcontroller.text = value;
});
you can send whatever you want in Navigator.pop(context,whtever you want to pass)
TextFormField(
// validator: (String value) {
// return value.isEmpty ? "task must have a name" : null;
// },
textAlign: TextAlign.end,
maxLength: 100,
controller: newcontroller, // Just an ordinary TextController
onChanged: (value) {
print(value);
},
decoration: InputDecoration(
errorText:
_validate // Just a boolean value set to false by default
? 'Value Can\'t Be Empty'
: null,
labelText: "name of task"),
style:
TextStyle(height: 1.2, fontSize: 20, color: Colors.black87),
),
MaterialButton(
onPressed: () {
Navigator.pop(context, newcontroller.text);
},
child: Text("GO BACK!"),
),
replace onChanged with onEditingComplete.
onEditingComplete: (value) {
newController.text = value;
FocusScope.of(context).unfocus(); //use this to dismiss the screen keyboard
}
Just remove the onChanged function. There is no need for it.
onChanged: (val) {
if (val.isNotEmpty) {
setState(() {
// remember to not add your controller value in onchanged
_messageController.text = val;
isShowSendButton = true;
});
} else {
setState(() {
// remember to not add your controller value in onchanged
_messageController.text = val;
isShowSendButton = false;
});
}
Unfortunately, onEditingComplete doesn't always fire (i.e. on a focus change), so if you are editing a text variable you can still use onChanged as the most reliable way to update changes, but you should not use the onChanged value parameter, nor should you use setState (which results in the reversed text).
TextEditingController tec = TextEditingController(text: myText);
return TextField(
controller: tec,
onChanged: (value) {
myText = tec.text;
},
);

How to reset error message in TextFormField?

I know this is a simple question but i did not find how to do this. How can i reset error message in TextFormField when the user input something in the field. Something like onchange listener. I notice onChanged is available in TextField but not TextFormField. How can i do this?
final _email = Container(
child: TextFormField(
decoration: InputDecoration(labelText: email),
keyboardType: TextInputType.emailAddress,
controller: emailController,
validator: _validateEmail,
onSaved: (input) => _stringEmail = input.toLowerCase().trim(),
),
);
UPDATE: I am using controller to add listener for user input. But i just want to reset the error message instead of validating the form again. Is this possible? How can i do this?
_resetEmailErrorMessage() {
print("Second text field: ${emailController.text}");
//replace clear to something that can reset the validation error message
emailController.clear();
}
#override
void initState() {
super.initState();
//start listening to changes
emailController.addListener(_resetEmailErrorMessage);
}
You can use auto-validation feature of Form
If you looking for this solution,
"As soon as you type it validates your input and show/hide error"
Flutter provides auto-validation feature, you just need to enable it at form level.
Default
_autoValidate = false;
Form
body: Form( key: _formKey,
autovalidate: _autoValidate,....)
Enable it when user presses submit once,
if (_formKey.currentState.validate()) {
// All Good
} else {
// start auto validate
setState(() {
_autoValidate = true;
});
}
Update :-
Now Form.autovalidate is deprecated. So Use
Form.autovalidateMode = AutovalidateMode.onUserInteraction
Form.autovalidate has been deprecated. Use Form.autovalidateMode = AutovalidateMode.onUserInteraction to have the errors automatically clear once a user begins to interact with the form field again without having to manage any state yourself.
The TextFormField also has the option of onChanged in that you can try to reset the form validation through the FormKey
final _email = Container(
child: TextFormField(
decoration: InputDecoration(labelText: email),
keyboardType: TextInputType.emailAddress,
controller: emailController,
validator: _validateEmail,
onChanged: (value){
formKey.currentState.reset();
},
onSaved: (input) => _stringEmail =
input.toLowerCase().trim(),
),
);
// Somewhere in `State` declaration
final formKey = GlobalKey<FormState>();
bool autovalidate = false;
#override
void build(BuildContext context) {
return Form(
key: formKey,
autovalidate: autovalidate,
child: Column(
children: [
TextFormField(
...
validator: (value) {
return (value == null || value.isEmpty) ? 'Cannot be empty' : null;
}
onChanged: (value) {
// Call this to refresh autovalidation result
setState(() {});
}
),
],
),
);
}
void validate() {
if(formKey.currentState.validate()) {
// data is valid!
return true;
} elsee {
// Activate autovalidation
autovalidate = true;
}
return false;
}
I would suggest to use the controller and listen to the User input . and whenever he types something you can validate() your form again
I don't see any answers to your question. I also ran into a situation like this I wanted the fields of the authentication form to reset their validation error once the user changes to Login or Signup. So I added this logic:
class _AuthFormState extends State<AuthForm> {
...
var _formFlipped = false; // THIS RIGHT HERE
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
...
}
#override
Widget build(BuildContext context) {
return
...
child: Form(
...
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (_formFlipped) return null; // THIS
if (value.isEmpty || !value.contains('#')) {
return 'Enter a valid email';
}
return null;
},
...
RaisedButton(
child: Text(_isLogin ? 'Login' : 'Signup'),
onPressed: () {
setState(() {
_formFlipped = false; // AND THIS
});
_trySubmit();
},
),
FlatButton(
child: Text(_isLogin
? 'Create new account'
: 'I already have an account'),
onPressed: () { // THIS TOO
setState(() {
_isLogin = !_isLogin;
_formFlipped = true;
_formKey.currentState.validate();
});
},
),
...
Before & After.
Edit:
So basically you just force pass null on all your field validators to clear the message. Alternatively as Yogesh pointed out you can achieve this by simply _formKey.currentState.reset();
But if you need more granular control, this method serves you good since you can set if (_formFlipped) return null; or similar condition to different fields according to your need.
This one worked form. You can try to validate form like this:
_formKey.currentState.validate()
You need to define validate field in your TextFormField.

Flutter: Adding a hyphen and brackets to a user's phone number entered in a textfield

I am trying to rearrange a user's entered phone number with brackets around the area code and a hyphen. For example, the user would enter 9991234567 and it would be rearranged to (999) 123-4567 inside the textfield.
I'm using a RegExp to separate the user's entry into the area code, and the 2 parts of the phone number. I am attempting to use a TextEditingController to edit the text field with brackets and a hyphen when the Save button is pressed but it does not seem to work.
_saveButtonPressed() async {
RegExp phone = RegExp(r'(\d{3})(\d{3})(\d{4})');
var matches = phone.allMatches(UserProfile.instance.phone);
var match = matches.elementAt(0);
setState(() {
phoneController.text = '(${match.group(1)}) ${match.group(2)}-${match.group(3)}';
});
}
This is the code for the phone number textfield.
_makeRowForAttribute(
imageAsset: "assets/images/phone.png",
title: "PHONE NUMBER",
keyboardType: TextInputType.phone,
placeholder: "6131110123",
charLimit: 10,
initialValue: UserProfile.instance.phone,
controller: phoneController,
onSave: (phone) {
UserProfile.instance.phone = phone.toString();
},
),
You can simply use flutter_masked_text package
It's just simple as following
import 'package:flutter_masked_text/flutter_masked_text.dart';
class MobileNumberTextField extends StatefulWidget {
createState() => MobileNumberTextFieldState();
}
class MobileNumberTextFieldState extends State<MobileNumberTextField> {
final controller =MaskedTextController(mask: "(000) 000-0000");
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
keyboardType: TextInputType.number,
autofocus: true,
);
}
}
Hope it will be helpful
I think this should do the trick.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FormattedPhoneNumber extends StatefulWidget {
#override
_FormattedPhoneNumberState createState() => _FormattedPhoneNumberState();
}
class _FormattedPhoneNumberState extends State<FormattedPhoneNumber> {
String text = "";
convert(TextEditingValue oldValue, TextEditingValue newValue) {
print("OldValue: ${oldValue.text}, NewValue: ${newValue.text}");
String newText = newValue.text;
if (newText.length == 10) {
// The below code gives a range error if not 10.
RegExp phone = RegExp(r'(\d{3})(\d{3})(\d{4})');
var matches = phone.allMatches(newValue.text);
var match = matches.elementAt(0);
newText = '(${match.group(1)}) ${match.group(2)}-${match.group(3)}';
}
// TODO limit text to the length of a formatted phone number?
setState(() {
text = newText;
});
return TextEditingValue(
text: newText,
selection: TextSelection(
baseOffset: newValue.text.length,
extentOffset: newValue.text.length));
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
inputFormatters: [
TextInputFormatter.withFunction(
(oldValue, newValue) => convert(oldValue, newValue)),
],
keyboardType: TextInputType.phone,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "input",
labelText: "Converts to phone number format"),
// Fixes a problem with text-caret only being at the start of the textfield.
controller: TextEditingController.fromValue(new TextEditingValue(
text: text,
selection: new TextSelection.collapsed(offset: text.length))),
),
),
],
);
}
}
Hope it helps :-)