I have a weird problem in flutter TextFormField. I implemented form validation in TextFormField. But onSaved() function doesn't get called after successful validation.
First I created basic Widget using TextFormField
--- In AppWidgets class ---
static Widget buildTextFormField(
String labelText,
String helperText,
IconData prefixIcon, {
Widget suffixIcon,
bool obscureText = false,
TextInputType keyboardType = TextInputType.text,
TextInputAction textInputAction = TextInputAction.none,
FocusNode focusNode,
ValueChanged<String> onFieldSubmitted,
TextEditingController controller,
FormFieldValidator<String> validator,
FormFieldSetter<String> onSaved,
bool isLightTheme = false,
}) {
return Theme(
data: isLightTheme
? AppThemesLight.textFormFieldThemeData
: AppThemesDark.textFormFieldThemeData,
child: TextFormField(
controller: controller,
validator: validator,
onSaved: onSaved,
keyboardType: keyboardType,
textInputAction: textInputAction,
focusNode: focusNode,
onFieldSubmitted: onFieldSubmitted,
obscureText: obscureText,
decoration: InputDecoration(
filled: true,
fillColor: isLightTheme
? AppColorsLight.textFieldFillColor
: AppColorsDark.textFieldFillColor,
labelText: labelText,
helperText: helperText,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(AppDimensions.textFieldBorderRadius),
),
),
prefixIcon: Icon(
prefixIcon,
color: isLightTheme
? AppColorsLight.primaryTextColor
: AppColorsDark.primaryTextColor,
),
suffixIcon: suffixIcon,
),
),
);
}
From that, created email text from field.
static Widget buildEmailTextFormField(LoginState loginState) {
return AppWidgets.buildTextFormField(
'Email address',
'Your email address',
Icons.email,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
focusNode: loginState.focusNodes[0],
onFieldSubmitted: (String value) {
print('submitted $value');
loginState.onFocusChanged(index: 0);
},
validator: (String email) {
print('validator $email');
return InputValidators.validateEmail(email);
},
onSaved: (String email) {
print('saved $email');
loginState.email = email;
},
);
}
Here is the email validator I used.
static String validateEmail(String email) {
Pattern pattern =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regex = new RegExp(pattern);
if (email.isEmpty)
return 'Email can\'t be empty';
else if (!regex.hasMatch(email))
return 'Enter valid email address';
else
return null;
}
I tested above code by putting print statement inside onSaved() function, but it's not printing after successful validation.
The onSaved() function won't be called automatically after successful validation. We have to call _formKey.currentState.save() manually to save our variables.
Form(
key: key,
child: TextFormField(
onSaved: (val) {
print('saved');
},
validator: (val) {
print('validating');
},
),
),
RaisedButton(
child: Text('Click me'),
onPressed: () {
if (key.currentState.validate()) {
key.currentState.save();
print('valid');
}
},
),
Did you call this method formKey.currentState.save() ?
in my case i forgot to call this after adding this it has worked.
Related
I'm following this https://medium.com/#c_innovative/simple-firebase-login-flow-in-flutter-6f44c2b5c58a tutorial and i am having some trouble with getting the login in to work. I have already connected my app to firebase and enabled email and password authentication on firebase console!
I have tried entering the key argument into the TextFormfield but so far im unable to do so.
class LoginPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _LoginPageState();
}
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
String _email;
String _password;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Login Page Flutter Firebase"),
),
body: Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Spacer(flex: 5),
Text(
"Login Information",
style: new TextStyle(fontSize: 30.0),
),
Spacer(
flex: 5,
),
TextFormField(
key: _formKey,
onSaved: (value) => _email = value,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: "Email Address"),
),
TextFormField(
onSaved: (value) => _password = value,
obscureText: true,
decoration: InputDecoration(labelText: "Password"),
),
Spacer(flex: 3),
RaisedButton(
child: Text("Login"),
onPressed: () async {
//Getting the error here
final form = _formKey.currentState;
form.save();
if (form.validate()) {
print('$_email $_password');
var result = await
Provider.of<AuthService(context).loginUser(email: _email, password:_password);
if (result == null) {
print("result is null");
}
}
}),
Spacer(
flex: 20,
)
],
),
));
}
}
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: NoSuchMethodError: The method 'save' was called on null.
Receiver: null
Tried calling: save()
this error message is what i get. somehow i understand that i have to initialise the receiver object(?) but i am confused on how to do so. Thanks for the help!
you put the _formKey on the TextFormField associated with the email field, it doesn't belong there
body: Container(
padding: EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
....
]
) // Form
) // Container
From the blog post..
Instead of using onSaved I'd just use TextEditingControllers on your fields and get the values of the text from those when you call your signIn function.
I just start to learn flutter, and I'm curious about how can i setState() dynamically in flutter
in React Native i usually used a function like this to setState dynamically:
class foo extends React.Component{
state={
username:null,
password:null
}
toggleSetState(whatState, value){
this.setState({ [whatState]: value })
}
render() {
return (
<View>
<TextInput
value={this.state.username}
onChangeText={(text)=>{toggleSetState(username, text)}
/>
<TextInput
value={this.state.password}
onChangeText={(text)=>{toggleSetState(password, text)}
/>
</View>
);
}
}
what is an equivalent of above code in Flutter?
I've tried this in Flutter but it seems doesn't work
class _LoginFormState extends State<LoginForm> {
String username, password;
void ToogleState(typedata, text){
setState(() {
typedata = text;
});
}
//Widget
TextField(
onChanged: (text){ToogleState(username, text); print(username);},
decoration: InputDecoration(
hintText: 'input username', labelText: 'Username'
),
),
}
after some research and trying, i can achieve what i want with the code below:
class _LoginFormState extends State<LoginForm> {
String username, password;
//create an object
var loginForm = {};
final myController = TextEditingController();
void ToogleState(typedata, text){
setState(() {
//i can assign any different variable with this code
loginForm[typedata] = text;
//output of LoginForm: {username: foo, password: bar}
});
}
//widget
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onEditingComplete: (){print(loginForm);},
onChanged: (text){ToogleState("username", text); print(loginForm['username']);},
decoration: InputDecoration(
hintText: 'input username', labelText: 'Username'
),
),
TextField(
onEditingComplete: (){print(loginForm);},
onChanged: (text){ToogleState("password", text); print(loginForm['password']);},
decoration: InputDecoration(
hintText: 'input password', labelText: 'Password'
),
),
],
),
);
}
You just need to make a variable to hold the value. I am confused why you are calling setState when you are not modifying ephemeral state
Here are some helpful docs
https://flutter.dev/docs/development/data-and-backend/state-mgmt/ephemeral-vs-app
class _LoginFormState extends State<LoginForm> {
String _username = "";
String __password = "";
void ToogleState( text){
setState(() {
_username = text;
});
}
//Widget
TextField(
onChanged: (text){ToogleState( text); print(username);},
decoration: InputDecoration(
hintText: 'input username', labelText: 'Username'
),
),
}
Below code is working properl. But I don't know how to set the default date value!
final dateFormat = DateFormat("dd-M-yyyy");
DateTimePickerFormField(
dateOnly: true,
format: dateFormat,
decoration: InputDecoration(labelText: 'Select Date'),
initialDate: DateTime.now(),
onSaved: (value){
_date = value;
},
),
I'm using datetime_picker_formfield: flutter library.
I'm trying to do that with initialDate: DateTime.now() initial date property but it was not displaying anything as an initial value.
Thanks in advance!
In Order to show the initial date Value you need to Use - initialValue:
initialDate: is for the Data Picker to show the Mentioned Date.
DateTimePickerFormField(
dateOnly: true,
format: dateFormat,
decoration: InputDecoration(labelText: 'Select Date'),
initialValue: DateTime.now(), //Add this in your Code.
// initialDate: DateTime(2017),
onSaved: (value) {
debugPrint(value.toString());
},
),
Updated Code with Validator:
var _myKey = GlobalKey<FormState>();
final dateFormat = DateFormat("dd-M-yyyy");
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Form(
key: _myKey,
child: Center(
child: DateTimePickerFormField(
dateOnly: true,
format: dateFormat,
validator: (val) {
if (val != null) {
return null;
} else {
return 'Date Field is Empty';
}
},
decoration: InputDecoration(labelText: 'Select Date'),
initialValue: DateTime.now(), //Add this in your Code.
// initialDate: DateTime(2017),
onSaved: (value) {
debugPrint(value.toString());
},
),
),
),
RaisedButton(
onPressed: () {
if (_myKey.currentState.validate()) {
_myKey.currentState.save();
} else {
}
},
child: Text('Submit'),
)
],
);
}
Change onSaved: to onChanged: and add setState
onChanged: (selectedDate) {
setState(() {
_properties.gameDateTime = selectedDate;
});
},
I am working on Flutter TextField widget. I want to show an error message below the TextField widget if the user does not fill that TextField. I only have to use TextField Widget not TextFormField in this case.
A Minimal Example of what you Want:
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',
errorText: _validate ? 'Value Can\'t Be Empty' : null,
),
),
RaisedButton(
onPressed: () {
setState(() {
_text.text.isEmpty ? _validate = true : _validate = false;
});
},
child: Text('Submit'),
textColor: Colors.white,
color: Colors.blueAccent,
)
],
),
),
);
}
}
Flutter handles error text itself, so we don't require to use variable _validate. It will check at runtime whether you satisfied the condition or not.
final confirmPassword = TextFormField(
controller: widget.confirmPasswordController,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock_open, color: Colors.grey),
hintText: 'Confirm Password',
errorText: validatePassword(widget.confirmPasswordController.text),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
),
);
String validatePassword(String value) {
if (!(value.length > 5) && value.isNotEmpty) {
return "Password should contain more than 5 characters";
}
return null;
}
Note: User must add at least one character to get this error message.
I would consider using a TextFormField with a validator.
Example:
class MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextFormField validator'),
),
body: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: 'Enter text',
),
textAlign: TextAlign.center,
validator: (text) {
if (text == null || text.isEmpty) {
return 'Text is empty';
}
return null;
},
),
RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
// TODO submit
}
},
child: Text('Submit'),
)
],
),
),
);
}
}
If you use TextFormField then you could easily implement 'Error
below your text fields'.
You can do this without using _validate or any other flags.
In this example, I have used validator method of TextFormField
widget. This makes the work a lot more easier and readable at the
same time.
I also used FormState to make the work more easier
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final _form = GlobalKey<FormState>(); //for storing form state.
//saving form after validation
void _saveForm() {
final isValid = _form.currentState.validate();
if (!isValid) {
return;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Form(
key: _form, //assigning key to form
child: ListView(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Full Name'),
validator: (text) {
if (!(text.length > 5) && text.isNotEmpty) {
return "Enter valid name of more then 5 characters!";
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: (text) {
if (!(text.contains('#')) && text.isNotEmpty) {
return "Enter a valid email address!";
}
return null;
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () => _saveForm(),
)
],
),
),
),
);
}
}
I hope this helps!
For TextFiled and TextFormFiled Validation you can use this Example I hope this will helpful for you people.
TextField(
enableInteractiveSelection: true,
autocorrect: false,
enableSuggestions: false,
toolbarOptions: ToolbarOptions(
copy: false,
paste: false,
cut: false,
selectAll: false,
),
controller: _currentPasswordController,
obscureText: passwordVisible,
decoration: InputDecoration(
errorText: Validators.password(
_currentPasswordController.text),
filled: true,
fillColor: Colors.white,
contentPadding:
const EdgeInsets.fromLTRB(20, 24, 12, 16),
border: const OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(8.0))),
// filled: true,
labelText: 'Password',
hintText: 'Enter your password',
suffixIcon: GestureDetector(
onTap: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
child: Container(
margin: const EdgeInsets.all(13),
child: Icon(
passwordVisible
? FontAwesomeIcons.eyeSlash
: Icons.remove_red_eye_sharp,
color: ColorUtils.primaryGrey,
size: 25)),
),
),
),
Validation Message Example Code
static password(String? txt) {
if (txt == null || txt.isEmpty) {
return "Invalid password!";
}
if (txt.length < 8) {
return "Password must has 8 characters";
}
if (!txt.contains(RegExp(r'[A-Z]'))) {
return "Password must has uppercase";
}
if (!txt.contains(RegExp(r'[0-9]'))) {
return "Password must has digits";
}
if (!txt.contains(RegExp(r'[a-z]'))) {
return "Password must has lowercase";
}
if (!txt.contains(RegExp(r'[#?!#$%^&*-]'))) {
return "Password must has special characters";
} else
return;
}
I have 2 form fields,I want to validate the second form field to match the password from the first one,I tried but no success..thanks for answering.
Updated : I already have submit button and its working,I want Validator in the second field to validate the first field text to match the second field.
new TextFormField(
controller: _registerPassController,
decoration: new InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) =>
value.isEmpty ? 'Password can\'t be empty' : null,
onSaved: (value) => _password = value,
),
],
),
new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextFormField(
controller: _registerPassController2,
decoration: new InputDecoration(labelText: 'Retype Password'),
obscureText: true,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
},),
I finally find the answer,its so simple actually.
new TextFormField(
controller: _registerPassController2,
decoration: new InputDecoration(labelText: 'Retype Password'),
obscureText: true,
validator: (value) {
if (value != _registerPassController.text) {
return 'Password is not matching';
}
},
),
Since you are using formfield it will be appropriate to use the key to access the value of other field. You can declare the global key like this
var passKey = GlobalKey<FormFieldState>();
Then pass it to the password field to retrieve its value during validation.
TextFormField(
key: passKey,
obscureText: true,
decoration: InputDecoration(
labelText: "Password"
),
validator: (password){
var result = password.length < 4 ? "Password should have at least 4 characters" : null;
return result;
},
);
Then you can use the password inside the confirmation validator like this
TextFormField(
obscureText: true,
decoration: InputDecoration(
labelText: "Confirm Password"
),
validator: (confirmation){
var password = passKey.currentState.value;
return equals(confirmation, password) ? null : "Confirm Password should match password";
},
);
The way I do this is to validate on a submit button and then show a message.
String _password;
Widget _passwordField() {
return new TextFormField(
autocorrect: false,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
validator: (value) =>
value.isEmpty ? "Password can't be empty" : null,
onSaved: (val) => _password = val;
}
String _passwordConfirm;
Widget _passwordConfirmField() {
return new TextFormField(
autocorrect: false,
obscureText: true,
decoration: InputDecoration(labelText: 'Retype Password'),
validator: (value) =>
value.isEmpty ? "Password can't be empty" : null,
onSaved: (val) => _passwordConfirm = val;
}
final formKey = new GlobalKey<FormState>();
Widget _buildForm() {
return new Form(
autovalidate: true,
key: formKey,
child: new Column(children: <Widget>[
_passwordField(),
_passwordConfirmField(),
new RaisedButton(
child: Text('Sign Up'),
onPressed: () => submit()
)
]
}
void submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save(); //this will cause the onSaved method to get called
//you will need to do some additional validation here like matching passwords
if(_password != _passwordConfirm) {
showDialog(....)
} else {
//complete
}
}
}
Note: This is a StatefulWidget
This is my method, big thanks to Suhair. It is based on Suhair's but his didn't work for me...
(First Password)
TextFormField(
key: passKey,
obscureText: true,
decoration: InputDecoration(labelText: "Password"),
validator: (password) {
var result = password.length < 4
? "Password should have at least 4 characters"
: null;
return result;
},
),
(Second)
TextFormField(
obscureText: true,
decoration: InputDecoration(labelText: "Confirm Password"),
validator: (confirmation) {
if (confirmation != passKey.currentState.value) {
return 'Passwords do not match';
} else {
return null;
}
},
),
and you also need to declare you variable
var passKey = GlobalKey<FormFieldState>();
To have the operation done in the TextField's validator, we can use the onChanged event to capture the value of the password field and use it later in the validator of the confirm password field.
String _password;
Widget _passwordField() {
return new TextFormField(
autocorrect: false,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
onChanged: (value) => _password = value,
validator: (value) =>
value.isEmpty ? "Password can't be empty" : null,
}
Widget _passwordConfirmField() {
return new TextFormField(
autocorrect: false,
obscureText: true,
decoration: InputDecoration(labelText: 'Retype Password'),
validator: (value) =>
value != _password ? "Passwords do not match!" : null,
}
final formKey = new GlobalKey<FormState>();
Widget _buildForm() {
return new Form(
autovalidate: true,
key: formKey,
child: new Column(children: <Widget>[
_passwordField(),
_passwordConfirmField(),
new RaisedButton(
child: Text('Sign Up'),
onPressed: () => submit()
)
]
}
void submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save(); //this will cause the onSaved method to get called
}
}
You can use a validator.
Define the validator:
class TwoFieldsMatchValidator extends TextFieldValidator {
String checkField;
TwoFieldsMatchValidator({required this.checkField, required String errorText}) : super(errorText);
#override
bool get ignoreEmptyValues => true;
#override
bool isValid(String? v) {
return checkField == v;
}
}
Use the validator:
final _controller = TextEditingController();
TextFormField(
...
validator: MultiValidator(
[
TwoFieldsMatchValidator(
checkField: _controller.text,
errorText: "<Error>",
),
],
),
),
Hope this helps anyone in future.