Flutter onChanged: not triggering method to read textfield content - flutter

I have a form where users capture information on multiple textfields. Within the Onchange:, I can see that there's activity every time the user types something on the textfield. However, when I call a method to read the textfield content, the method is not being fired. For example, I call the updateFirstName() method within the OnChange: within the nameController textfield. The method doesn't fire and the App fails when I press Save because the FirstName field is null. Any reason why the updateFirstName method on my code below is not being called? I'm new to Flutter so I might be missing something basic.
import 'dart:ffi';
import 'package:flutter/material.dart';
import '../widgets/main_drawer.dart';
import '../utils/database_helper.dart';
import '../models/customer.dart';
import 'package:intl/intl.dart';
class CustomerDetailsScreen extends StatefulWidget {
static const routeName = '/customer-details';
#override
_CustomerDetailsScreenState createState() => _CustomerDetailsScreenState();
}
class _CustomerDetailsScreenState extends State<CustomerDetailsScreen> {
//Define editing controllers for all the text fields
TextEditingController nameController = TextEditingController();
TextEditingController surnameController = TextEditingController();
TextEditingController cellphoneController = TextEditingController();
TextEditingController emailController = TextEditingController();
//Connecting to the database
DatabaseHelper helper = DatabaseHelper();
//Define some variables
String appBarTitle;
Customer customer; //This is the Customer Model
/*
String sFirstName;
String sSurname;
String sCellNumber;
String sEmailAddress;
String sCompanyName = '-';
*/
var _formKey = GlobalKey<FormState>();
//Method to validate e-mail address
bool validateEmail(String value) {
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);
return (!regex.hasMatch(value)) ? false : true;
}
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
//Populate the text fields
//nameController.text = customer.sFirstName;
//surnameController.text = customer.sSurname;
//cellphoneController.text = customer.sCellNumber;
//emailController.text = customer.sEmailAddress;
return Scaffold(
appBar: AppBar(
title: Text('Edit Customer'),
),
body: GestureDetector(
//Gesture detector wrapped the entire body so we can hide keyboard \
// when user clicks anywhere on the screen
behavior: HitTestBehavior.opaque,
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0, right: 10.0),
child: ListView(
children: <Widget>[
//First Element - Name
Padding(
padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
child: TextFormField(
controller: nameController,
style: textStyle,
textCapitalization: TextCapitalization.words,
validator: (String value) {
if (value.isEmpty) {
return 'Please enter your name';
}
return null;
},
onChanged: (value) {
debugPrint('Something changed on the Name Text Field');
updateFirstName();
},
decoration: InputDecoration(
labelText: 'Name',
labelStyle: textStyle,
errorStyle:
TextStyle(color: Colors.redAccent, fontSize: 15.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
//Second Element - Surname
Padding(
padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
child: TextFormField(
controller: surnameController,
style: textStyle,
textCapitalization: TextCapitalization.words,
validator: (String value) {
if (value.isEmpty) {
return 'Please enter your surname';
}
return null;
},
onChanged: (value) {
debugPrint('Something changed on the Surname Text Field');
updateSurname();
},
decoration: InputDecoration(
labelText: 'Surname',
labelStyle: textStyle,
errorStyle:
TextStyle(color: Colors.redAccent, fontSize: 15.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
//Third Element - Cellphone
Padding(
padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
child: TextFormField(
controller: cellphoneController,
style: textStyle,
keyboardType: TextInputType.number,
validator: (String value) {
if (value.isEmpty) {
return 'Please enter your cellphone number';
} else {
if (value.length < 10)
return 'Cell number must be at least 10 digits';
}
return null;
},
onChanged: (value) {
debugPrint(
'Something changed on the Cellphone Text Field');
updateCellNumber();
},
decoration: InputDecoration(
labelText: 'Cellphone',
labelStyle: textStyle,
errorStyle:
TextStyle(color: Colors.redAccent, fontSize: 15.0),
hintText: 'Enter Cell Number e.g. 0834567891',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
//Fourth Element - Email Address
Padding(
padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
child: TextFormField(
controller: emailController,
style: textStyle,
keyboardType: TextInputType.emailAddress,
validator: (String value) {
if (value.isEmpty) {
return 'Please enter your e-mail address';
} else {
//Check if email address is valid.
bool validmail = validateEmail(value);
if (!validmail) {
return 'Please enter a valid e-mail address';
}
}
return null;
},
onChanged: (value) {
debugPrint(
'Something changed on the Email Address Text Field');
updateEmailAddress();
},
decoration: InputDecoration(
labelText: 'E-mail',
labelStyle: textStyle,
errorStyle:
TextStyle(color: Colors.redAccent, fontSize: 15.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
//Fifth Element - Row for Save Button
Padding(
padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
color: Theme.of(context).primaryColorDark,
textColor: Theme.of(context).primaryColorLight,
child: Text(
'Save',
textScaleFactor: 1.5,
),
onPressed: () {
setState(() {
if (_formKey.currentState.validate()) {
debugPrint('Save button clicked');
//Call the Save method only if the validation is passed
_saveCustomerDetails();
}
});
}),
),
],
)),
],
),
),
),
),
);
}
//**********************Updating what is captured by the user on each text field******************/
//Update the sFirstName of the Customer model object
void updateFirstName() {
print('The updateFirstName was called');
customer.sFirstName = nameController.text;
}
//Update the sSurname of the Customer model object
void updateSurname() {
customer.sSurname = surnameController.text;
}
//Update the sCellNumber of the Customer model object
void updateCellNumber() {
customer.sCellNumber = cellphoneController.text;
}
//Update the sEmailAddress of the Customer model object
void updateEmailAddress() {
customer.sEmailAddress = emailController.text;
customer.sCompanyName = '-';
}
//**********************END - Updating what is captured by the user on each text field******************/
//**************************Saving to the Database*************************************/
void _saveCustomerDetails() async {
//moveToLastScreen();
//Update the dtUpdated of the Customer model with current time (Confirm that it is GMT)
print('Trying to save customer info was called');
customer.dtUpdated = DateFormat.yMMMd().format(DateTime.now());
print('Trying to save customer info was called - 2');
int result;
result = await helper.insertNewHumanCustomer(customer);
if (result != 0) {
//Saving was a Success
_showAlertDialog('Success', 'Customer details saved successfully');
print('The customer details were saved successfully');
} else {
//Saving was a Failure
print('FAILURE - The customer details failed to save');
_showAlertDialog('Failure', 'Oopsy.....something went wrong. Try again');
}
}
//*****Show Alert Popup message*****/
void _showAlertDialog(String title, String message) {
AlertDialog alertDialog = AlertDialog(
title: Text(title),
content: Text(message),
);
showDialog(context: context, builder: (_) => alertDialog);
}
//*****END - Show Alert Popup message*****/
//**************************Saving to the Database*************************************/
}

Related

i want to create a list of user profile in flutter?

I am new to the flutter and for practice purposes, I am creating a blood donation app, in my app, I have added a registration form for donor registration, now when a recipient comes for blood needs, they fill out the form for specific blood group accordingly recipient could see a list of donors with profile, name, city, and country. Now I need help in creating this page that shows all the registered donors on the list.
Firstly you need to make the data model where you can add specific data. Make form where user fills the data and afterwards put that data into model. Add those models into list and then map into listView.
Model:
class BloodDonor {
final String profile;
final String name;
final String city;
final String country;
BloodDonor({
required this.city,
required this.country,
required this.name,
required this.profile,
});
}
Screen:
class GetDonorDetailScreen extends StatefulWidget {
#override
State<GetDonorDetailScreen> createState() => _GetDonorDetailScreenState();
}
class _GetDonorDetailScreenState extends State<GetDonorDetailScreen> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
BloodDonor bloodDonor = BloodDonor();
List<BloodDonor> donors = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Form(
key: formKey,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
hintText: 'Enter profile',
),
validator: (valueEntered) {
return valueEntered!.isEmpty ? "Please fill here" : null;
},
onSaved: (value) {
bloodDonor.profile = value!;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
hintText: 'Enter Name',
),
validator: (valueEntered) {
return valueEntered!.isEmpty ? "Please fill here" : null;
},
onSaved: (value) {
bloodDonor.name = value!;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
hintText: 'Enter City',
),
validator: (valueEntered) {
return valueEntered!.isEmpty ? "Please fill here" : null;
},
onSaved: (value) {
bloodDonor.city = value!;
},
),
SizedBox(height: 10),
TextFormField(
decoration: InputDecoration(
hintText: 'Enter Country',
),
validator: (valueEntered) {
return valueEntered!.isEmpty ? "Please fill here" : null;
},
onSaved: (value) {
bloodDonor.country = value!;
},
),
SizedBox(height: 10),
Center(
child: ElevatedButton(
child: Text("Add"),
onPressed: () {
if (formKey.currentState!.validate()) {
formKey.currentState!.save();
setState(() {
donors.add(bloodDonor);
bloodDonor = BloodDonor();
formKey.currentState?.reset();
});
}
},
),
),
SizedBox(height: 10),
Expanded(
child: ListView(
children: [
for (final donor in donors)
ListTile(
title: Text(donor.name),
subtitle: Text(
"${donor.city}, ${donor.country} - ${donor.profile}"),
),
],
),
),
],
),
),
),
),
);
}
}

the values not remain in the Text Form Field when I click continue, then when I return to the page the value is empty Note that I used Page View

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()),
),
),
);
}
}

Remove Error Message From TextFeild While User Enters/Fixes Data in Flutter

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, ...

Flutter Textform Validation not showing up

I have watched a couple of tutorials on how to make validators work but none of them seemed to work. Can anyone help me with this? This is the code of a simple sign in page. My validators don't show up on screen if there's any sort of error it should be detecting. I've watched tutorials where it shows up in red but in my app, it doesn't show up at all.
class UserLogin extends StatefulWidget {
UserLogin({this.auth,this.onSignedIn});
final BaseAuth auth;
final VoidCallback onSignedIn;
#override
State<StatefulWidget> createState()=> _UserLoginState();
}
class _UserLoginState extends State<UserLogin> {
final formkey = GlobalKey<FormState>();
bool _validateAndSave()
{
final form = formkey.currentState;
if(form.validate())
{
form.save();
return true;
}
else
return false;
}
static final incorrect_icon = Icon(
Icons.error,
color: Colors.pink,
);
void _validateAndSubmit() async
{
if(_validateAndSave()) {
try {
String userId = await widget.auth.signIn(emailid, password);
print('Signed in! $userId');
//widget.onSignedIn();
Navigator.push(context, MaterialPageRoute(builder: (context)=>Feed()));
}
catch (e) {
print('Error: $e');
}
}
}
static final TextEditingController emailContr = new TextEditingController();
static final TextEditingController passwordContr = new TextEditingController();
static String get emailid => emailContr.text;
static String get password => passwordContr.text;
final _email = Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
controller: emailContr,
autofocus: false,
validator: (input) {
if(input.isEmpty)
{
return 'Email cannot be empty';
}
return null;
},
//onSaved: (input)=> emailid = input,
decoration: InputDecoration(
hintText: 'Enter Email Address',
suffixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)
),
),
),
);
final _pass = Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: TextFormField(
controller: passwordContr,
obscureText: true,
autofocus: false,
validator: (input) {
if(input.length <= 6)
{
return 'Password should be at least 6 characters';
}
return null;
},
decoration: InputDecoration(
hintText: 'Enter password',
suffixIcon: Icon(Icons.lock),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)
),
),
),
);
/*final login_button =
},
);
*/
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.yellow,
body: Container(
child: Form(
key: formkey,
child: Column(
children: <Widget>[
SizedBox(height: 200,),
Text('Vibing',
style:TextStyle(
fontWeight: FontWeight.bold,
fontSize: 64,
),
),
SizedBox(height: 100,),
_email,
SizedBox(height: 20,),
_pass,
SizedBox(height:30),
RaisedButton(
color: Colors.yellow,
elevation: 5,
child: Text('Login'),
onPressed: (){
_validateAndSubmit();
formkey.currentState.reset();
}
),
SizedBox(height:10),
FlatButton(
child: Text('Forgot password'),
onPressed: ()=> Navigator.push(context, MaterialPageRoute(builder:(context)=>ForgotPassword()),)
),
SizedBox(height:10),
FlatButton(
child: Text('New? Register here!'),
onPressed: ()=> Navigator.push(context, MaterialPageRoute(builder:(context)=>UserReg()),)
),
],
),
),
) ,
);
}
}
The problem is that you're resetting the form after validation so any error shown will reset. Just remove this line from your login button callback:
formkey.currentState.reset();
And voila:
reset():
Resets every [FormField] that is a descendant of this [Form] back to its
[FormField.initialValue].
In your case, the initialValue is empty string "" and that's why when you called reset() method of Form, it's setting an empty string, that will not show any error, as nothing is there.

Flutter: Best way to get all values in a form

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.