Display Error Text under Dropdown Button Input Box - flutter

I am new to Flutter and I am creating a form with TextFormfield and DropdownButtonFormField. I am trying to display the error message of the DropdownButtonFormField(Site Crop) to below the text box like how the site name field display its error message.
Should I continue using DropdownButtonFormField or is there a better way to do this?
FormSample
code for dropdown
child: DropdownButtonHideUnderline(
child: DropdownButtonFormField(
decoration: InputDecoration(
errorBorder: InputBorder.none,
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
),
hint: Text(
'Choose Country',
style: TextStyle(
fontSize: 16,
color: NewSite.black.withOpacity(0.6),
),
),
validator: (value) => value == null
? 'Please fill in' : null,
value: _selectedLocation,
onChanged: (newValue) {
setState(() {
_selectedLocation = newValue;
});
},
items: _locations.map((location) {
return DropdownMenuItem(
child: new Text(location),
value: location,
);
}).toList(),
icon: Icon(Icons.arrow_drop_down, color: ColorTheme.main,),
style: TextStyle(color: NewSite.black),
isExpanded: true,
elevation: 26,
dropdownColor: NewSite.white,
),
),

Try Form Widget,
Wrap this your filed inside Form,
like this
import 'package:flutter/material.dart';
class DemoDropDownCreate extends StatefulWidget {
#override
_DemoDropDownCreate createState() =>
_DemoDropDownCreate();
}
class _DemoDropDownCreate
extends State<DemoDropDownCreate> {
final _formKey = GlobalKey<FormState>();
bool _autovalidate = false;
String selectedCountry;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
autovalidate: _autovalidate,
child:
Column(
children: <Widget>[
DropdownButtonFormField<String>(
value: selectedCountry,
hint: Text(
'Select Country',
),
onChanged: (salutation) =>
setState(() => selectedCountry = salutation),
validator: (value) => value.isEmpty ? 'field required' : null,
items:
['India', 'Japan'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
FlatButton(
child: Text('PROCEED'),
color: Colors.green,
onPressed: () {
if (_formKey.currentState.validate()) {
//form is valid, proceed further
_formKey.currentState.save();//save once fields are valid, onSaved method invoked for every form fields
print("success");
} else {
setState(() {
_autovalidate = true; //enable realtime validation
});
print("Please select all field");
}
},
)
],
),
),
);
}
}

You can indeed use validators for DropdownButtonFormField
DropdownButtonFormField(
validator: (dynamic value) => value.isEmpty ? "An error happened or please pick something" : null,
decoration: InputDecoration(....
you can change value.isEmpty to any condition you want, same as the "An error happened" message.

Related

TextFormField input text overflow with DropdownButtonFormField, Flutter

I have layout issues because the text from the dropdown menu that you choose can't fit inside the TextFormField and I can't seem to fix this issue. I have tried adding the overflow: TextOverflow.ellipsis property but that only changes for the dropdown labels, still when I choose one, they can't fit inside the TextFormField.
When you press the dropdown button, it shows like this:
and with the TextOverflow.elipsis property:
which is fine, but I still have the layout issue because of the chosen text that is now displayed inside the textformfield:
How can I add the same property to the TextFormField, or any sort of solution to this issue?
The code:
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
decoration:
kTextFieldDecoration.copyWith(
hintText: 'Legal status',
labelText: 'Legal status',
),
value: _legalStatus,
items: [
'Sole proprietorship',
'Partnerships',
'Limited Liability Company (LLC)',
'Corporation',
'Small Business Corporation (S-Corporation)'
]
.map((label) => DropdownMenuItem(
child: Text(
label.toString(),
),
value: label,
))
.toList(),
onChanged: (value) {
setState(() {
_legalStatus = value;
print(value);
});
},
validator: (thisValue) {
if (thisValue == null) {
return 'Please choose your legal status';
}
return null;
},
),
),
SizedBox(
width: 16.0,
),
Container(
width: 120.0,
child: DropdownButtonFormField<String>(
decoration:
kTextFieldDecoration.copyWith(
hintText: 'Year established',
labelText: 'Year established',
),
value: _yearEstablished,
items: items // a list of numbers that are Strings
.map((label) => DropdownMenuItem(
child:
Text(label.toString()),
value: label,
))
.toList(),
onChanged: (value) {
setState(() {
_yearEstablished = value;
print(value);
});
},
validator: (thisValue) {
if (thisValue == null) {
return 'Please choose your company year of establishment';
}
return null;
},
),
),
],
),
Thanks in advance for your help!
You need to use isExpanded property of DropDownFormField to solve this error.
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
decoration:
kTextFieldDecoration.copyWith(
hintText: 'Legal status',
labelText: 'Legal status',
),
value: _legalStatus,
items: [
'Sole proprietorship',
'Partnerships',
'Limited Liability Company (LLC)',
'Corporation',
'Small Business Corporation (S-Corporation)'
]
.map((label) => DropdownMenuItem(
child: Text(
label.toString(),
),
value: label,
))
.toList(),
onChanged: (value) {
setState(() {
_legalStatus = value;
print(value);
});
},
validator: (thisValue) {
if (thisValue == null) {
return 'Please choose your legal status';
}
return null;
},
),
),
SizedBox(
width: 16.0,
),
Container(
width: 120.0,
child: DropdownButtonFormField<String>(
decoration:
kTextFieldDecoration.copyWith(
hintText: 'Year established',
labelText: 'Year established',
),
value: _yearEstablished,
items: items // a list of numbers that are Strings
.map((label) => DropdownMenuItem(
child:
Text(label.toString()),
value: label,
))
.toList(),
onChanged: (value) {
setState(() {
_yearEstablished = value;
print(value);
});
},
validator: (thisValue) {
if (thisValue == null) {
return 'Please choose your company year of establishment';
}
return null;
},
),
),
],
),
You need to use selectedItemBuilder parameter which will control how the selected item will be displayed on the button. Then, TextOverflow.ellipsis will work with you as expected. Here's how to use it:
selectedItemBuilder: (BuildContext context) {
return items.map<Widget>((String item) {
return Text(item, overflow: TextOverflow.ellipsis,);
}).toList();
},

How set form validation in widget flutter

I'm working on flutter project .I have a revision form validator that is not working as expected. When I leave the TextFormField empty the validator doesn't show me anything. I want to stay on the revision form until I enter the values.
thanks in advance
my code :
class Revision extends StatefulWidget {
}
class _RevisionState extends State<Revision> with TickerProviderStateMixin {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
RevisionApi revisionApi = RevisionApi();
TextEditingController _Kilometrage_revisionController =
TextEditingController();
_showAddDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
title: Text("Ajouter un évènement"),
content: StatefulBuilder(builder: (
BuildContext context,
StateSetter setState,
) {
return SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(
child: DropdownButtonFormField(
decoration: InputDecoration(
hoverColor: Colors.white,
//contentPadding: EdgeInsets.only(left: 10, right: 15, top: 15),
labelText: 'Type',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
),
dropdownColor: Colors.white,
value: status,
items: <DropdownMenuItem>[
DropdownMenuItem(
// value: 'videnge',
value: 0,
child: InkWell(
child: Text('videnge'),
hoverColor: Colors.indigo,
),
),
DropdownMenuItem(
// value: 'visite technique',
value: 1,
child: Text('visite technique'),
),
DropdownMenuItem(
// value: 'assurance véhicule',
value: 2,
child: Text('assurance véhicule'),
),
DropdownMenuItem(
// value: 'autre',
value: 3,
child: Text('autre'),
),
],
onChanged: (value) {
setState(() {
status = value;
});
},
)),
]),
if (status == 1) visiTechniqueDropdown(),
]),
));
}),
actions: <Widget>[
TextButton(
child: Text(
"Enregistrer",
style: TextStyle(
color: Colors.red, fontWeight: FontWeight.bold),
),
onPressed: () {
if (status == null) return;
setState(() {
if (_events[_controller.selectedDay] != null) {
_events[_controller.selectedDay].add(status);
} else {
_events[_controller.selectedDay] = [status];
}
prefs.setString(
"events", json.encode(encodeMap(_events)));
status;
setRevision();
_KilometrageController.clear();
_eventController.clear();
_EmplacementController.clear();
_DateController.clear();
_repeat_revisionController.clear();
_revision_agenceController.clear();
_Kilometrage_revisionController.clear();
Navigator.of(context).pop();
// Navigator.pop(context);
});
},
),
new TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Retour'),
),
],
));
}
void setRevision() async {
print("hello");
if (_formKey.currentState.validate()) {
String kilometrage_pour_vidange = _KilometrageController.text;
String revision_type = status.toString();
String revision_title = _eventController.text;
String revision_location = _EmplacementController.text;
String revision_date = _DateController.text;
String repeat_revision = _repeat_revisionController.text;
String revision_agence = _revision_agenceController.text;
String kilometrage_revision = _Kilometrage_revisionController.text;
revisionApi
.setRevision(
revision_type,
revision_title,
revision_date,
revision_location,
kilometrage_pour_vidange,
repeat_revision,
revision_agence,
kilometrage_revision,
)
.then((data) {
if (data != null) {
Navigator.pop(context);
Navigator.of(context).pop();
Navigator.push(
context, MaterialPageRoute(builder: (context) => Revision()));
}
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(data)));
}).catchError((error) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.toString())));
});
setState(() {});
}
}
Widget visiTechniqueDropdown() {
return Column(mainAxisSize: MainAxisSize.min, children: [
Row(
children: [
Flexible(
child: TextFormField(
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
validator: (value) {
if (value.isEmpty) {
return 'Password is required';
}
return null;
},
controller: _DateController,
cursorColor: kPrimaryColor,
decoration: InputDecoration(
labelText: 'Date',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
How i can set the validator correctly ?
This is for you. Thanks and enjoy
// Create a corresponding State class.
// This class holds data related to the form.
class MyFormState extends State<MyForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyFormState>.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
// sendData();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
}
},
child: Text('Submit'),
),
),
],
),
);
}
}

Is there any way I can reset the dynamic fields I added into a form to their previous state if user doesn't make any changes (Presses back)?

I'm trying to create a dynamic form so I used the idea of using a listview builder to create it. I was able to successfully create it but I faced that I cannot discard changes made to the form by popping it off after editing it. The two textFormField Job name and rate per hour were able to discard changes as they were using onsaved but on the checkbox I can't do that as it has onChanged which wraps setstate to change its state.
You can take a look at the video at this link to see how it functions as of now - https://vimeo.com/523847256
As you can see that it is retaining the data even after popping the page and coming back which I don't want it to. I'm looking for a way to prevent that and make the form the same as before if the user didn't press save.
I have tried to reassign the variables() in onpressed of back button but that didn't work. I also tried push replacement to the same page to reset it but that also didn't work. I think the cuprit here is the sublist and the initialValueTextFormField and initialValueCheckbox which are used declared under ListView.builder but I don't know how to fix that without affecting the dynamic list functionality.
class EditJobPage extends StatefulWidget {
const EditJobPage({Key key, this.job}) : super(key: key);
final Job job;
static Future<void> show(BuildContext context, {Job job}) async {
await Navigator.of(context, rootNavigator: true).pushNamed(
AppRoutes.editJobPage,
arguments: job,
);
}
#override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
List<dynamic> _subList = [];
Set newSet = Set('', false);
#override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job?.name;
_ratePerHour = widget.job?.ratePerHour;
_subList = widget.job?.subList;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(widget.job == null ? 'New Job' : 'Edit Job'),
leading: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
FlatButton(
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
),
onPressed: () => _submit(),
),
],
),
body: _buildContents(),
backgroundColor: Colors.grey[200],
);
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
print(_subList);
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
keyboardAppearance: Brightness.light,
initialValue: _name,
validator: (value) =>
(value ?? '').isNotEmpty ? null : 'Name can\'t be empty',
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate per hour'),
keyboardAppearance: Brightness.light,
initialValue: _ratePerHour != null ? '$_ratePerHour' : null,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
onChanged: (value) {
setState(() {
_ratePerHour = int.tryParse(value ?? '') ?? 0;
});
},
),
Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: _subList?.length ?? 0,
itemBuilder: (context, index) {
String initialValueTextFormField =
_subList[index].subListTitle.toString();
bool initialValueCheckbox = _subList[index].subListStatus;
return Row(
children: [
Checkbox(
value: initialValueCheckbox,
onChanged: (bool newValue) {
setState(
() {
initialValueCheckbox = newValue;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
},
);
},
),
Expanded(
child: TextFormField(
minLines: 1,
maxLines: 1,
initialValue: initialValueTextFormField,
autofocus: false,
textAlign: TextAlign.left,
onChanged: (title) {
setState(() {
initialValueTextFormField = title;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
});
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
filled: true,
hintText: 'Write sub List here',
),
),
),
],
);
},
),
TextButton(
onPressed: () {
setState(() {
_subList.add(newSet);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.add),
Text('Add Sub Lists'),
],
),
),
],
),
];
}
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
} else {
final database = context.read<FirestoreDatabase>(databaseProvider);
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name ?? '',
ratePerHour: _ratePerHour ?? 0,
subList: _subList);
database.setJob(job);
Navigator.of(context).pop();
}
}
}
And this is the link to the full repository of the whole flutter app in case you want to look at any other part:- https://github.com/brightseagit/dynamic_forms . Thank you.
Note - This is the edited code of this repo - https://github.com/bizz84/starter_architecture_flutter_firebase.
When assigning the list we need to use _subList = List.from(widget.job.subList) instead of _subList = widget.job.subList.
Otherwise, the changes made in _subList will also be made in job.subList .

Keep Getting this Error: 'package:flutter/src/widgets/text.dart': Failed assertion: line 241 pos 10: 'data != null'

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);

Checkbox form validation

How can I validate a checkbox in a Flutter Form? Every other validation works fine, but the checkbox doesn't show an Error.
Here is my code:
FormField(
validator: (value) {
if (value == false) {
return 'Required.';
}
},
builder: (FormFieldState<dynamic> field) {
return CheckboxListTile(
value: checkboxValue,
onChanged: (val) {
if (checkboxValue == false) {
setState(() {
checkboxValue = true;
});
} else if (checkboxValue == true) {
setState(() {
checkboxValue = false;
});
}
},
title: new Text(
'I agree.',
style: TextStyle(fontSize: 14.0),
),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.green,
);
},
),
A cleaner solution to this problem is to make a class that extends FormField<bool>
Here is how I accomplished this:
class CheckboxFormField extends FormField<bool> {
CheckboxFormField(
{Widget title,
FormFieldSetter<bool> onSaved,
FormFieldValidator<bool> validator,
bool initialValue = false,
bool autovalidate = false})
: super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<bool> state) {
return CheckboxListTile(
dense: state.hasError,
title: title,
value: state.value,
onChanged: state.didChange,
subtitle: state.hasError
? Builder(
builder: (BuildContext context) => Text(
state.errorText,
style: TextStyle(color: Theme.of(context).errorColor),
),
)
: null,
controlAffinity: ListTileControlAffinity.leading,
);
});
}
in case if you want to put your checkbox directly in your Form widget tree you can use solution provided below with FormField widget. Instead of using ListTile I used rows and columns as my form was requiring different layout.
FormField<bool>(
builder: (state) {
return Column(
children: <Widget>[
Row(
children: <Widget>[
Checkbox(
value: checkboxValue,
onChanged: (value) {
setState(() {
//save checkbox value to variable that store terms and notify form that state changed
checkboxValue = value;
state.didChange(value);
});
}),
Text('I accept terms'),
],
),
//display error in matching theme
Text(
state.errorText ?? '',
style: TextStyle(
color: Theme.of(context).errorColor,
),
)
],
);
},
//output from validation will be displayed in state.errorText (above)
validator: (value) {
if (!checkboxValue) {
return 'You need to accept terms';
} else {
return null;
}
},
),
You could try something like this :
CheckboxListTile(
value: checkboxValue,
onChanged: (val) {
setState(() => checkboxValue = val
},
subtitle: !checkboxValue
? Text(
'Required.',
style: TextStyle(color: Colors.red),
)
: null,
title: new Text(
'I agree.',
style: TextStyle(fontSize: 14.0),
),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.green,
);
The above answer is correct, however, if you want to display an error message that is more consistent with the default layout of a TextFormField widget error message, then wrap the Text widget in a Padding widget, and give it the hex colour #e53935.
Note: You may need to adjust the left padding to fit the CheckboxListTile widget is also wrapped in a Padding widget.
Check the code below:
bool _termsChecked = false;
CheckboxListTile(
activeColor: Theme.of(context).accentColor,
title: Text('I agree to'),
value: _termsChecked,
onChanged: (bool value) => setState(() => _termsChecked = value),
subtitle: !_termsChecked
? Padding(
padding: EdgeInsets.fromLTRB(12.0, 0, 0, 0),
child: Text('Required field', style: TextStyle(color: Color(0xFFe53935), fontSize: 12),),)
: null,
),