I'm trying to make Sign Up pages so I used Page View like this
page view controller.nextPage(....,...)
page view controller.prev page(....,...)
Boom Value in the Text Form Field is gone.
And this thing happens on all pages when I move to a new page in the page view and return to the page. The information that the user filled in is gone from the fields and has become empty!
why?
Form(
key: EmailKeys.formKey,
child: Column(
children: [
const Text(
"Create an account, It's free",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(
height: 40,
),
TextFormField(
onFieldSubmitted: (val) {
EmailKeys.formKey.currentState!.setState(() {
email = val.trim();
});
EmailKeys.formKey.currentState!.save();
},
textInputAction: TextInputAction.done,
inputFormatters: [LengthLimitingTextInputFormatter(100)],
obscureText: false,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: "Email Address",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
labelStyle: TextStyle(color: Colors.grey[400]),
floatingLabelStyle:
const TextStyle(color: Colors.blueGrey, fontSize: 18),
),
validator: (value) {
if (value!.isEmpty) {
return 'Email address is required';
} else if (!value.contains("#")) {
return "Email address should contain ' # ' symbol";
}
},
onChanged: (value) {
email = value.trimLeft();
},
onSaved: (val) {
setState(() {
email = val!;
});
print(email);
},
controller: _emailCtrl,
),
],
),
),
Use TexEditingController in TextFormField to hold and retrieve data https://api.flutter.dev/flutter/widgets/TextEditingController-class.html
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.addListener(() {
final String text = _controller.text.toLowerCase();
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(6),
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(border: OutlineInputBorder()),
),
),
);
}
}
I am trying to build a Sign In page with Flutter. I have used a validator to validate user inputs. I am trying to remove the error message automatically when the user fixes his input. As an example, if the user enters his email as: name#server (missing .domain) and clicks continue, s/he will get an error telling the user this is not a valid email form. if the user adds the missing part, .c (or more characters) the error message should disappear without the need to click continue again. This should go for the password field too.
Here is my code:
class _SignFormState extends State<SignForm> {
bool _isHidden = true;
final _formKey = GlobalKey<FormState>();
void inContact(TapDownDetails details) {
setState(() {
_isHidden = false;
});
}
void outContact(TapUpDetails details) {
setState(() {
_isHidden = true;
});
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
buildEmailForm(),
SizedBox(height: getProportionateScreenHeight(20)),
buildPasswordForm(),
SizedBox(height: getProportionateScreenHeight(20)),
DefaultButton(
text: 'Continue',
press: () {
if (_formKey.currentState.validate()) {
return;
}
},
),
],
),
);
}
TextFormField buildPasswordForm() {
return TextFormField(
keyboardType: TextInputType.visiblePassword,
obscureText: _isHidden,
decoration: InputDecoration(
//labelText: 'Passowrd',
hintText: 'Password',
floatingLabelBehavior: FloatingLabelBehavior.never,
prefixIcon: Icon(
Icons.lock_sharp,
//color: kTextColor,
),
suffixIcon: Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(12),
),
child: GestureDetector(
onTapDown: inContact,
onTapUp: outContact,
child: Icon(
Icons.remove_red_eye,
size: 26,
//color: kTextColor,
),
),
),
),
);
}
TextFormField buildEmailForm() {
return TextFormField(
keyboardType: TextInputType.emailAddress,
autofocus: true,
decoration: InputDecoration(
//labelText: 'Email',
hintText: 'Enter your email',
floatingLabelBehavior: FloatingLabelBehavior.always,
prefixIcon: Icon(Icons.mail),
),
validator: (value) {
if (value.isEmpty) {
return kEmailNullError;
}
if (!emailValidatorRegExp.hasMatch(value)) {
return kInvalidEmailError;
}
return null;
},
onChanged: (value) {},
);
}
}
You can use autovalidateMode: AutovalidateMode.onUserInteraction to remove the error after enters/fixes data
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction, // <-- add this line
keyboardType: TextInputType.emailAddress,
autofocus: true, ...
TextField(
inputFormatters: [
new FilteringTextInputFormatter.allow(
RegExp('[0-9]')),
],
hintText: 'some text'
title: '',
editingController: controllers[31],
value: somenumber,
onChange: (value) {
if (num.parse(value) <= 3000 &&
num.parse(value) >= 30) {
// save some data
}else{
controllers[31].clear(),
},
),
so above is a textfield with onchange, right now the textfield does not clear if i put outside of the range, is it possible to clear the textfield based onchange?
Is the TextField() you are using part of a plugin? The properties look a bit odd compared with the latest stable release of Flutter.
Here's an example that clears the text:
TextEditingController textEditingController = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: TextField(
inputFormatters: [
new FilteringTextInputFormatter.allow(RegExp('[0-9]')),
],
decoration: InputDecoration(
hintText: 'some text',
),
controller: textEditingController,
onChanged: (value) {
if (value.length <= 10) {
// something
} else {
textEditingController.clear();
}
}
),
),
);
}
Create TextEditingController for your text field and then assign it to the controller property of TextField.
// create controller
TextEditingController _controller = new TextEditingController();
// assign it to TextField controller property
TextField(
controller : _controller
// your other properties
)
then to clear the text
// clear text
_controller.clear();
``
U can also do this :
TextEditingController textEditingController = new TextEditingController();
Function onchange;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: TextField(
inputFormatters: [
new FilteringTextInputFormatter.allow(RegExp('[0-9]')),
],
decoration: InputDecoration(
hintText: 'some text',
suffixIcon: IconButton(
onPressed: () {
widget.onchanged('');
controller.clear();
},
),
controller: textEditingController,
onChanged: (value) => onchange(value)
}
),
),
);
}
I have a screen that shows a textformfield, I want to retrieve the value that the user has inputted. But when I made a change the cursor didn't move, so it stays at position 0. How do I solve this problem?
class ScreenFormArtikel extends StatefulWidget {
final String mode;
ScreenFormArtikel(this.mode);
#override
_ScreenFormArtikelState createState() => _ScreenFormArtikelState();
}
class _ScreenFormArtikelState extends State<ScreenFormArtikel> {
var _key = GlobalKey<FormState>();
var _titleController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Article'),
),
body: Container(
child: Form(
key: _key,
child: Column(
children: [
Consumer<ProviderArtikel>(
builder: (context, artikel, child) {
return TextFormField(
decoration: InputDecoration(
labelText: 'Title',
floatingLabelBehavior: FloatingLabelBehavior.always),
controller: _titleController,
validator: (value) {
if (value.isEmpty) {
return "Please fill this field";
}
return null;
},
onChanged: (value) {
_titleController.text = value;
},
);
},
),
RaisedButton(onPressed: () {}),
],
),
),
),
);
}
}
I have attached the sample video below
click here
Commenting the onChanged will do the work.
TextFormField(
decoration: InputDecoration(
labelText: 'Title',
floatingLabelBehavior: FloatingLabelBehavior.always),
controller: _titleController,
validator: (value) {
if (value.isEmpty) {
return "Please fill this field";
}
return null;
},
// onChanged: (value) {
// _titleController.text = value;
// },
),
Actually the _titleController.text is setting the text onChange, that's why cursor is moving to the first place.
And you don't have to explicitly do the onTextChange thing.
I'm making a data collection app which has multiple TextFields, like more than 12. I'm using a Form key to validate all of them. I want values of all the text fields so I can save them to firestore. How do I do this? Here's my code:
import 'package:flutter/material.dart';
class MainForm extends StatefulWidget {
#override
_MainFormState createState() => _MainFormState();
}
class _MainFormState extends State<MainForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text('Enter information about PG Owner'),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
textCapitalization: TextCapitalization.words,
textAlignVertical: TextAlignVertical.center,
onTap: () {},
decoration: InputDecoration(
prefixIcon: Icon(Icons.face),
labelText: 'Enter Name of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.length < 15) {
return 'Address seems very short!';
}
return null;
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(Icons.room),
labelText: 'Enter full address of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
if (value.length < 9) {
return 'Phone number must be 9 digits or longer';
}
return null;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone),
labelText: 'Phone number of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Please enter a valid email address';
}
if (!value.contains('#')) {
return 'Email is invalid, must contain #';
}
if (!value.contains('.')) {
return 'Email is invalid, must contain .';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
),
),
)
],
),
),
),
);
}
}
Update: I know that proper way (I've read the docs) of getting values from a TextField is by creating a controller. But, In my case there are 14 TextFields which requires me to create 14 controllers. Is there a better way of doing this?
You can use something like this in the following code:
_formKey.currentState.save(); calls the onSaved() on each textFormField items, which assigns the value to all the fields and you can use them as required. Try using the _formKey.currentState.save(); just after _formKey.currentState.validate() is evaluated as true.
The form code looks like this:
String contactNumber;
String pin;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
onSaved: (String value){contactNumber=value;},
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
maxLength: 10,
decoration: InputDecoration(
labelText: "Enter Your Mobile Number",
hintText: "Number",
icon: Icon(Icons.phone_iphone)),
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please Enter 10 digit number';
}
return null;
},
),
TextFormField(
onSaved: (String value){pin=value;},
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
maxLength: 10,
decoration: InputDecoration(
labelText: "Enter Your PIN",
hintText: "Number",
icon: Icon(Icons.lock)),
validator: (value) {
if (value.isEmpty || value.length < 6) {
return 'Please Enter 6 digit PIN';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
color: Colors.black,
textColor: Colors.white,
onPressed: () {
if (_formKey.currentState.validate()) {
***_formKey.currentState.save();***
bloc.loginUser(contactNumber, pin);
}
},
child: Text('Login' /*style: TextStyle(fontSize: 30),*/)),
)
],
),
);
Using controller in TextFormField, you can get value of the TextFormField.
TextEditingController emailEditingController = TextEditingController();
TextFormField(
controller: emailEditingController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter a valid email address';
}
if (!value.contains('#')) {
return 'Email is invalid, must contain #';
}
if (!value.contains('.')) {
return 'Email is invalid, must contain .';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
);
Get Value like:
String email=emailEditingController.text;
Updated Answer
Get value by using onSubmitted
onSubmitted: (String value){email=value;},
I am not satisfied with how Flutter make you handle the form values yourself, you need to create a TextEditingController instance for each field, assign it to the controller and remember to dispose all of them manually. This leads to a lot of boilerplate code and makes it more error-prone:
final _formKey = GlobalKey<FormState>();
final controller1 = TextEditingController();
final controller2 = TextEditingController();
final controller3 = TextEditingController();
#override
void dispose() {
super.dispose();
controller1.dispose();
controller2.dispose();
controller3.dispose();
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: [
TextFormField(controller: controller1),
TextFormField(controller: controller2),
TextFormField(
controller: controller3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
final value1 = controller1.text;
final value2 = controller2.text;
final value3 = controller3.text;
// do something with the form data
}
},
child: const Text('Submit'),
),
]),
);
}
A much less cumbersome way is to use the flutter_form_builder package and replace TextFormField with the FormBuilderTextField widget which is a wrapper of the old plain TextField. You can see all of the supported input widgets here.
All you need to do now is to specify the name of each field in your form, and access it in _formKey.currentState?.value. See the example below:
final _formKey = GlobalKey<FormBuilderState>();
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _formKey,
child: Column(children: [
FormBuilderTextField(name: 'field1'),
FormBuilderTextField(name: 'field2'),
FormBuilderTextField(
name: 'field3',
validator: FormBuilderValidators.required(
context,
errorText: 'Please enter some text',
),
),
ElevatedButton(
onPressed: () {
_formKey.currentState.save();
if (_formKey.currentState!.validate()) {
final formData = _formKey.currentState?.value;
// formData = { 'field1': ..., 'field2': ..., 'field3': ... }
// do something with the form data
}
},
child: const Text('Submit'),
),
]),
);
}
You can use flutter_form_bloc, you don't need to create any TextEditingController and can separate the Business Logic from the User Interface, in addition to offering other advantages.
dependencies:
flutter_bloc: ^0.21.0
form_bloc: ^0.4.1
flutter_form_bloc: ^0.3.0
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:form_bloc/form_bloc.dart';
void main() => runApp(MaterialApp(home: MainForm()));
class MainFormBloc extends FormBloc<String, String> {
final nameField = TextFieldBloc();
final addressField = TextFieldBloc(validators: [
(value) => value.length < 15 ? 'Address seems very short!' : null,
]);
final phoneNumberField = TextFieldBloc(validators: [
(value) =>
value.length < 9 ? 'Phone number must be 9 digits or longer' : null,
]);
final emailField = TextFieldBloc(validators: [Validators.email]);
#override
List<FieldBloc> get fieldBlocs => [
nameField,
addressField,
phoneNumberField,
emailField,
];
#override
Stream<FormBlocState<String, String>> onSubmitting() async* {
// This method is called when you call [mainFormBloc.submit]
// and each field bloc have a valid value.
// And you can save them in firestore.
print(nameField.value);
print(addressField.value);
print(phoneNumberField.value);
print(emailField.value);
yield currentState.toSuccess('Data saved successfully.');
// yield `currentState.toLoaded()` because
// you can't submit if the state is `FormBlocSuccess`.
// In most cases you don't need to do this,
// because you only want to submit only once.
yield currentState.toLoaded();
}
}
class MainForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<MainFormBloc>(
builder: (context) => MainFormBloc(),
child: Builder(
builder: (context) {
final formBloc = BlocProvider.of<MainFormBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Main Form')),
body: FormBlocListener<MainFormBloc, String, String>(
onSuccess: (context, state) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.successResponse),
backgroundColor: Colors.green,
),
);
},
onSubmissionFailed: (context, state) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Some fields have invalid data.'),
backgroundColor: Colors.red,
),
);
},
child: ListView(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: formBloc.nameField,
padding: const EdgeInsets.all(8.0),
autofocus: true,
textCapitalization: TextCapitalization.words,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
prefixIcon: Icon(Icons.face),
labelText: 'Enter Name of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.addressField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(Icons.room),
labelText: 'Enter full address of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.phoneNumberField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone),
labelText: 'Phone number of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.emailField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: formBloc.submit,
child: Center(child: Text('SUBMIT')),
),
),
],
),
),
);
},
),
);
}
}
I came here from a similar search. All the answers found did not satisfy my need, hence I wrote a custom solution.
form key
final _signUpKey = GlobalKey<FormState>();
declare your TextEditingController
final Map<String, TextEditingController> sigUpController = {
'firstName': TextEditingController(),
'lastName': TextEditingController(),
'email': TextEditingController(),
'phone': TextEditingController(),
'password': TextEditingController(),
};
Pass controller to TextFormField like this
Form(
key: _signUpKey,
child: Column(
children: [
TextFormField(
controller: sigUpController['firstName'],
validator: validator,
autofocus: autofocus,
keyboardType: TextInputType.text,
style: const TextStyle(
fontSize: 14,
),
onTap: onTap,
onChanged: onChanged,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r"[a-zA-Z]+|\s"),
),
],
),
// define the other TextFormField here
TextButton(
onPressed: () {
if (!_signUpKey.currentState!.validate()) {
return;
}
// To get data I wrote an extension method bellow
final data = sigUpController.data();
print('data: $data'); // data: {firstName: John, lastName: Doe, email: example#email.com, phone: 0000000000, password: password}
},
child: const Text('submit'),
)
],
),
);
Extension method to get data from Map<String, TextEditingController>
extension Data on Map<String, TextEditingController> {
Map<String, dynamic> data() {
final res = <String, dynamic>{};
for (MapEntry e in entries) {
res.putIfAbsent(e.key, () => e.value?.text);
}
return res;
}
}
Try using this flutter package flutter_form_builder, it will help you from repeating yourself by creating multiple controllers for each form field. In addition to that, it will help you in validating the form, and updating the form with simplicity by using only a simple form key to control the entire form.