Related
In my flutter app I've created a dropdown. initially there is only one dropdown but the user can add more. the items of these dropdowns are the same. but the selected results has to be different. but my code is returning the last selected value. so how can I add it on list?
Model
class DropModel {
String? selected;
DropModel({this.selected});
setData(list) {
for (int i = 0; i < list.length; i++) {
selected = list;
}
}
}
initialize
List<String>? selCat;
DropModel selDrop = DropModel();
dropdown widget
DropdownButtonFormField2 _generatedDropDown(
List<String> category, String? selected, int index) {
final group = _GroupControllers();
return DropdownButtonFormField2(
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
),
),
isExpanded: true,
hint: const Text(
'Select Category',
style: TextStyle(fontSize: 14),
),
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.black45,
),
iconSize: 30,
buttonHeight: 60,
buttonPadding: const EdgeInsets.only(left: 20, right: 10),
items: category
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
))
.toList(),
validator: (value) {
if (value == null) {
return 'Please select Catagory.';
}
},
onChanged: (value) {
//Do something when changing the item if you want.
setState(() {
// selDrop.setData(value.toString());
selected = value.toString();
selDrop.selected = selected;
selCat!.insert(index, selected.toString());
});
},
onSaved: (value) {
//selDrop.setData(value.toString());
selected = value.toString();
selDrop.selected = selected;
selCat!.insert(index, selected.toString());
},
);
}
result looped
print(".................${selDrop.selected}"); // returns the last added/ selected value
print(".................${selCat}"); //returns null
the traditional List returns null and the model returns the last added/selected value. how can i add multiple dropdown selected values to a List? each has different values. in a textfield I could create dynamic TextEditingController but how can I manage dropdowns
I need some help regarding flutter textformfield This is my code for the textfield controller. The problem is when I type new word,the cursor position is moved automatically from right to left (reset)(before first letter inside box). How I can make the cursor work as usual at the end of current text. I have read few solutions from stack overflow but it still not working. Please help me. Thanks.
class BillingWidget extends StatelessWidget {
final int pageIndex;
final Function validateController;
final formKey = new GlobalKey<FormState>();
BillingWidget(this.billingDetails,this.pageIndex,this.validateController);
final BillingDetails billingDetails;
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
onChanged: () {
if (formKey.currentState.validate()) {
validateController(pageIndex,false);
formKey.currentState.save();
final val = TextSelection.collapsed(offset: _textTEC.text.length);
_textTEC.selection = val;
}
else {
//prevent procced to next page if validation is not successful
validateController(pageIndex,true);
}
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 20,bottom: 0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"Maklumat Pembekal",
textAlign: TextAlign.left,
style: TextStyle(
decoration:TextDecoration.underline,
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.grey.shade700,
),
),
),
),
TextFormField(
controller: billingDetails.companyNameTxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Nama Syarikat"),
validator: (String value) {
return value.isEmpty ? 'Nama Syarikat Diperlukan' : null;
},
onSaved: (String value) {
billingDetails.companyName = value;
billingDetails.companyNameTxtCtrl.text = billingDetails.companyName;
},
),
TextFormField(
controller: billingDetails.addressLine1TxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Alamat Baris 1"),
validator: (String value) {
return value.isEmpty ? 'Alamat Baris tidak boleh kosong.' : null;
},
onSaved: (String value) {
billingDetails.addressLine1 = value;
billingDetails.addressLine1TxtCtrl.text = billingDetails.addressLine1;
},
),
TextFormField(
controller: billingDetails.addressLine2TxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Alamat Baris 2"),
onSaved: (String value) {
billingDetails.addressLine2 = value;
billingDetails.addressLine2TxtCtrl.text = billingDetails.addressLine2;
},
),
TextFormField(
controller: billingDetails.addressLine3TxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Alamat Baris 3"),
onSaved: (String value) {
billingDetails.addressLine3 = value;
billingDetails.addressLine3TxtCtrl.text = billingDetails.addressLine3;
},
),
],
),
);
}
yourController.text = yourString;
yourController.selection = TextSelection.fromPosition(TextPosition(offset: yourController.text.length));
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*************************************/
}
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.
Sorry for the large amount of code! I just started with flutter and am very new to programming as a whole. I am trying to make a functioning submittable form and followed a tutorial to do so, but I keep getting this error when I try to load the form page:
'package:flutter/src/widgets/text.dart': Failed assertion: line 241 pos 10: 'data != null'
I have attached the code, but if this is the wrong bit of code for the error let me know and I can attach the other lib files. When it works, I want this to be submittable form to a URL I have and JSON encoded.
I greatly appreciate any help!
I have tried removing all validation, and I have tried looking through the "null(s)", but am unsure which one one is throwing the error.
class MyFormPage extends StatefulWidget {
MyFormPage({Key key, this.title}) : super(key: key);
final String title;
#override
_FormPage createState() => new _FormPage();
}
class _FormPage extends State<MyFormPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new
GlobalKey<ScaffoldState>();
Contact newContact = new Contact();
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
List<String> _information = <String>[
'',
'male',
'female',
];
String _info = '';
final TextEditingController _controller = new TextEditingController();
Future _chooseDate(BuildContext context, String initialDateString) async {
var now = new DateTime.now();
var initialDate = convertToDate(initialDateString) ?? now;
initialDate = (initialDate.year >= 1900 && initialDate.isBefore(now)
? initialDate
: now);
var result = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: new DateTime(1900),
lastDate: new DateTime.now());
if (result == null) return;
setState(() {
_controller.text = new DateFormat.yMd().format(result);
});
}
DateTime convertToDate(String input) {
try {
var d = new DateFormat.yMd().parseStrict(input);
return d;
} catch (e) {
return null;
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text(widget.title),
),
body: new SafeArea(
top: false,
bottom: false,
child: new Form(
key: _formKey,
autovalidate: true,
child: new ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: <Widget>[
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your first name',
labelText: 'First Name',
),
inputFormatters: [new LengthLimitingTextInputFormatter(15)],
validator: (val) =>
val.isEmpty ? 'First name is required' : null,
onSaved: (val) => newContact.firstName = val,
),
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your last name',
labelText: 'Last Name',
),
inputFormatters: [new LengthLimitingTextInputFormatter(15)],
validator: (val) =>
val.isEmpty ? 'Last name is required' : null,
onSaved: (val) => newContact.lastName = val,
),
new Row(children: <Widget>[
new Expanded(
child: new TextFormField(
decoration: new InputDecoration(
icon: const Icon(Icons.calendar_today),
hintText: 'Enter your date of birth',
labelText: 'D.O.B.',
),
controller: _controller,
keyboardType: TextInputType.datetime,
onSaved: (val) => newContact.dob = convertToDate(val),
)),
new IconButton(
icon: new Icon(Icons.more_horiz),
tooltip: 'Choose date',
onPressed: (() {
_chooseDate(context, _controller.text);
}),
)
]),
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.phone),
hintText: 'Enter a phone number',
labelText: 'Phone',
),
keyboardType: TextInputType.phone,
inputFormatters: [
new WhitelistingTextInputFormatter(
new RegExp(r'^[()\d -]{1,15}$')),
],
validator: (value) => isValidPhoneNumber(value)
? null
: 'Phone number must be entered as (###)###-####',
onSaved: (val) => newContact.phone = val,
),
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.email),
hintText: 'Enter a email address',
labelText: 'Email',
),
keyboardType: TextInputType.emailAddress,
validator: (value) => isValidEmail(value)
? null
: 'Please enter a valid email address',
onSaved: (val) => newContact.email = val,
),
new FormField(
builder: (FormFieldState<String> state) {
return InputDecorator(
decoration: InputDecoration(
icon: const Icon(Icons.group),
labelText: 'Gender',
errorText: state.hasError ? state.errorText : null,
),
isEmpty: _info == '',
child: new DropdownButtonHideUnderline(
child: new DropdownButton<String>(
value: _info,
isDense: true,
onChanged: (String newValue) {
setState(() {
newContact.gender = newValue;
_info = newValue;
state.didChange(newValue);
});
},
items: _information.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
),
),
);
},
validator: (val) {
return val != '' ? null : 'Please select a gender';
},
),
new Container(
padding: const EdgeInsets.only(left: 40.0, top: 20.0),
child: new RaisedButton(
child: const Text('Submit'),
onPressed: _submitForm,
)),
],
))),
);
}
bool isValidPhoneNumber(String input) {
final RegExp regex = new RegExp(r'^\(\d\d\d\)\d\d\d\-\d\d\d\d$');
return regex.hasMatch(input);
}
bool isValidEmail(String input) {
final RegExp regex = new RegExp(
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$");
return regex.hasMatch(input);
}
bool isValidDob(String dob) {
if (dob.isEmpty) return true;
var d = convertToDate(dob);
return d != null && d.isBefore(new DateTime.now());
}
void showMessage(String message, [MaterialColor color = Colors.red]) {
_scaffoldKey.currentState.showSnackBar(
new SnackBar(backgroundColor: color, content: new Text(message)));
}
void _submitForm() {
final FormState form = _formKey.currentState;
if (!form.validate()) {
showMessage('Form is not valid! Please review and correct.');
} else {
form.save(); //This invokes each onSaved event
print('Form save called, newContact is now up to date...');
print('First Name: ${newContact.firstName}');
print('Last Name: ${newContact.lastName}');
print('Dob: ${newContact.dob}');
print('Phone: ${newContact.phone}');
print('Email: ${newContact.email}');
print('Gender: ${newContact.gender}');
print('========================================');
print('Submitting to back end...');
var contactService = new ContactService();
contactService.createContact(newContact).then((value) => showMessage(
'New contact created for ${value.firstName}!', Colors.blue));
}
}
}
So, when I click the button to navigate to my form page I get the red screen showing the error code I have mentioned above. If it were to work correctly, a sign-up page should appear.
Your title maybe null, which when it goes to the Text widget would cause this error. You can add a default title as follows:
MyFormPage({Key key, this.title = ''}) : super(key: key);