Last updated:
0 purchases
flutter form builder fixed
Please note #
This package has been fixed to allow use with Flutter Beta. It works for me but it may not work for you.
Original readme below
Flutter FormBuilder - flutter_form_builder #
This package helps in creation of data collection forms in Flutter by removing the boilerplate needed to build a form, validate fields, react to changes,
and collect final user input.
Simple Usage #
To use this plugin, add flutter_form_builder as a
dependency in your pubspec.yaml file.
Migrating from v3 to v4 #
Improvements:
Internationalized default error texts for inbuilt validators - Help wanted to do even more in translating to more languages.
Ability to programmatically induce an error to a field - could be especially useful for server-side validation.
New field types including: SearchableDropdown and FilePickerField
Better composition of validators.
Breaking changes:
Rename attribute option in all fields to name.
validators attribute has been renamed to validator which takes Flutter's
FormFieldValidator object. To compose multiple FormFieldValidators together, use
FormBuilderValidators.compose() which takes a list of FormFieldValidator objects.
FormBuilderValidators.requiredTrue functionality has been replaced with FormBuilderValidators.equal which can be used to check equality of any Object or value
Due to its limited use, FormBuilderCountryPicker was removed from the package. Its functionality could be achieved with use of FormBuilderSearchableDropdown which is more extensible.
FormBuilderCustomField functionality is now achieved using FormBuilderField class which is the base class from which all fields are built in v4. Follow these instructions to construct your own custom form field using FormBuilderField.
Example #
final _formKey = GlobalKey<FormBuilderState>();
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
FormBuilder(
key: _formKey,
autovalidate: true,
child: Column(
children: <Widget>[
FormBuilderFilterChip(
name: 'filter_chip',
decoration: InputDecoration(
labelText: 'Select many options',
),
options: [
FormBuilderFieldOption(
value: 'Test', child: Text('Test')),
FormBuilderFieldOption(
value: 'Test 1', child: Text('Test 1')),
FormBuilderFieldOption(
value: 'Test 2', child: Text('Test 2')),
FormBuilderFieldOption(
value: 'Test 3', child: Text('Test 3')),
FormBuilderFieldOption(
value: 'Test 4', child: Text('Test 4')),
],
),
FormBuilderChoiceChip(
name: 'choice_chip',
decoration: InputDecoration(
labelText: 'Select an option',
),
options: [
FormBuilderFieldOption(
value: 'Test', child: Text('Test')),
FormBuilderFieldOption(
value: 'Test 1', child: Text('Test 1')),
FormBuilderFieldOption(
value: 'Test 2', child: Text('Test 2')),
FormBuilderFieldOption(
value: 'Test 3', child: Text('Test 3')),
FormBuilderFieldOption(
value: 'Test 4', child: Text('Test 4')),
],
),
FormBuilderColorPickerField(
name: 'color_picker',
// initialValue: Colors.yellow,
colorPickerType: ColorPickerType.MaterialPicker,
decoration: InputDecoration(labelText: 'Pick Color'),
),
FormBuilderChipsInput(
decoration: InputDecoration(labelText: 'Chips'),
name: 'chips_test',
onChanged: _onChanged,
initialValue: [
Contact('Andrew', '[email protected]',
'https://d2gg9evh47fn9z.cloudfront.net/800px_COLOURBOX4057996.jpg'),
],
maxChips: 5,
findSuggestions: (String query) {
if (query.isNotEmpty) {
var lowercaseQuery = query.toLowerCase();
return contacts.where((profile) {
return profile.name
.toLowerCase()
.contains(query.toLowerCase()) ||
profile.email
.toLowerCase()
.contains(query.toLowerCase());
}).toList(growable: false)
..sort((a, b) => a.name
.toLowerCase()
.indexOf(lowercaseQuery)
.compareTo(b.name
.toLowerCase()
.indexOf(lowercaseQuery)));
} else {
return const <Contact>[];
}
},
chipBuilder: (context, state, profile) {
return InputChip(
key: ObjectKey(profile),
label: Text(profile.name),
avatar: CircleAvatar(
backgroundImage: NetworkImage(profile.imageUrl),
),
onDeleted: () => state.deleteChip(profile),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
},
suggestionBuilder: (context, state, profile) {
return ListTile(
key: ObjectKey(profile),
leading: CircleAvatar(
backgroundImage: NetworkImage(profile.imageUrl),
),
title: Text(profile.name),
subtitle: Text(profile.email),
onTap: () => state.selectSuggestion(profile),
);
},
),
FormBuilderDateTimePicker(
name: 'date',
// onChanged: _onChanged,
inputType: InputType.time,
decoration: InputDecoration(
labelText: 'Appointment Time',
),
initialTime: TimeOfDay(hour: 8, minute: 0),
// initialValue: DateTime.now(),
// enabled: true,
),
FormBuilderDateRangePicker(
name: 'date_range',
firstDate: DateTime(1970),
lastDate: DateTime(2030),
format: DateFormat('yyyy-MM-dd'),
onChanged: _onChanged,
decoration: InputDecoration(
labelText: 'Date Range',
helperText: 'Helper text',
hintText: 'Hint text',
),
),
FormBuilderSlider(
name: 'slider',
validator: FormBuilderValidators.compose([
FormBuilderValidators.min(context, 6),
]),
onChanged: _onChanged,
min: 0.0,
max: 10.0,
initialValue: 7.0,
divisions: 20,
activeColor: Colors.red,
inactiveColor: Colors.pink[100],
decoration: InputDecoration(
labelText: 'Number of things',
),
),
FormBuilderCheckbox(
name: 'accept_terms',
initialValue: false,
onChanged: _onChanged,
title: RichText(
text: TextSpan(
children: [
TextSpan(
text: 'I have read and agree to the ',
style: TextStyle(color: Colors.black),
),
TextSpan(
text: 'Terms and Conditions',
style: TextStyle(color: Colors.blue),
),
],
),
),
validator: FormBuilderValidators.equal(
context,
true,
errorText:
'You must accept terms and conditions to continue',
),
),
FormBuilderTextField(
name: 'age',
decoration: InputDecoration(
labelText:
'This value is passed along to the [Text.maxLines] attribute of the [Text] widget used to display the hint text.',
),
onChanged: _onChanged,
// valueTransformer: (text) => num.tryParse(text),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
FormBuilderValidators.numeric(context),
FormBuilderValidators.max(context, 70),
]),
keyboardType: TextInputType.number,
),
FormBuilderDropdown(
name: 'gender',
decoration: InputDecoration(
labelText: 'Gender',
),
// initialValue: 'Male',
allowClear: true,
hint: Text('Select Gender'),
validator: FormBuilderValidators.compose(
[FormBuilderValidators.required(context)]),
items: genderOptions
.map((gender) => DropdownMenuItem(
value: gender,
child: Text('$gender'),
))
.toList(),
),
FormBuilderTypeAhead(
decoration: InputDecoration(
labelText: 'Country',
),
name: 'country',
onChanged: _onChanged,
itemBuilder: (context, country) {
return ListTile(
title: Text(country),
);
},
controller: TextEditingController(text: ''),
initialValue: 'Uganda',
suggestionsCallback: (query) {
if (query.isNotEmpty) {
var lowercaseQuery = query.toLowerCase();
return allCountries.where((country) {
return country.toLowerCase().contains(lowercaseQuery);
}).toList(growable: false)
..sort((a, b) => a
.toLowerCase()
.indexOf(lowercaseQuery)
.compareTo(
b.toLowerCase().indexOf(lowercaseQuery)));
} else {
return allCountries;
}
},
),
FormBuilderRadioList(
decoration:
InputDecoration(labelText: 'My chosen language'),
name: 'best_language',
onChanged: _onChanged,
validator: FormBuilderValidators.compose(
[FormBuilderValidators.required(context)]),
options: ['Dart', 'Kotlin', 'Java', 'Swift', 'Objective-C']
.map((lang) => FormBuilderFieldOption(
value: lang,
child: Text('$lang'),
))
.toList(growable: false),
),
FormBuilderTouchSpin(
decoration: InputDecoration(labelText: 'Stepper'),
name: 'stepper',
initialValue: 10,
step: 1,
iconSize: 48.0,
addIcon: Icon(Icons.arrow_right),
subtractIcon: Icon(Icons.arrow_left),
),
FormBuilderRating(
decoration: InputDecoration(labelText: 'Rate this form'),
name: 'rate',
iconSize: 32.0,
initialValue: 1.0,
max: 5.0,
onChanged: _onChanged,
),
],
),
),
Row(
children: <Widget>[
Expanded(
child: MaterialButton(
color: Theme.of(context).accentColor,
child: Text(
"Submit",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_formKey.currentState.save();
if (_formKey.currentState.validate()) {
print(_formKey.currentState.value);
} else {
print("validation failed");
}
},
),
),
SizedBox(width: 20),
Expanded(
child: MaterialButton(
color: Theme.of(context).accentColor,
child: Text(
"Reset",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_formKey.currentState.reset();
},
),
),
],
)
],
);
}
copied to clipboard
l10n #
Just add the FormBuilderLocalizations.delegate in the list of your app's localizationsDelegates
return MaterialApp(
supportedLocales: [
Locale('en'),
Locale('it'),
Locale('fr'),
Locale('es'),
],
localizationsDelegates: [
FormBuilderLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
copied to clipboard
Input widgets #
The currently supported fields include:
FormBuilderCheckbox - Single Checkbox field
FormBuilderCheckboxList - List of Checkboxes for multiple selection
FormBuilderChipsInput - Takes a list of Chips as input and suggests more options on typing
FormBuilderChoiceChip - Creates a chip that acts like a radio button.
FormBuilderColorPicker - For Color input selection
FormBuilderDateRangePicker - For selection of a range of dates
FormBuilderDateTimePicker - For Date, Time and DateTime input
FormBuilderDropdown - Used to select one value from a list as a Dropdown
FormBuilderFilterChip - Creates a chip that acts like a checkbox.
FormBuilderRadioGroup - Used to select one value from a list of Radio Widgets
FormBuilderRangeSlider - Used to select a range from a range of values
FormBuilderRating - For selection of a numerical value as a rating
FormBuilderSearchableDropdown - Field for selecting value(s) from a searchable list
FormBuilderSegmentedControl - For selection of a value from the CupertinoSegmentedControl as an input
FormBuilderSignaturePad - Field with drawing pad on which user can doodle
FormBuilderSlider - For selection of a numerical value on a slider
FormBuilderSwitch - On/Off switch field
FormBuilderTextField - A Material Design text field input.
FormBuilderTouchSpin - Selection of a number by tapping on a plus or minus icon
FormBuilderTypeAhead - Auto-completes user input from a list of items
In order to create an input field in the form, along with the label, and any applicable validation, there are several attributes that are supported by all types of inputs namely:
Attribute
Type
Default
Required
Description
name
String
Yes
This will form the key in the form value Map
initialValue
T
null
No
The initial value of the input field
enabled
bool
true
No
Determines whether the field widget will accept user input.
decoration
InputDecoration
InputDecoration()
No
Defines the border, labels, icons, and styles used to decorate the field.
validator
FormFieldValidator<T>
null
No
A FormFieldValidator that will check the validity of value in the FormField
onChanged
ValueChanged<T>
null
No
This event function will fire immediately the the field value changes
valueTransformer
ValueTransformer<T>
null
No
Function that transforms field value before saving to form value. e.g. transform TextField value for numeric field from String to num
The rest of the attributes will be determined by the type of Widget being used.
Additional input fields #
To make this package compartible with as many platforms as Flutter supports, we separated some input fields into their own packages because they depend on platform-specific plugins. Here's are the links to some of the packages that could be used with FormBuilder
FormBuilderFilePicker - Picks image(s) from user device storage.
FormBuilderImagePicker - Picks image(s) from Gallery or Camera.
FormBuilderLocationField - Geographic location input.
FormBuilderPhoneField - International phone number input.
Building your own custom field #
To build your own field within a FormBuilder, we use FormBuilderField which will require that you define your own field.
var options = ["Option 1", "Option 2", "Option 3"];
copied to clipboard
FormBuilderField(
name: "name",
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
builder: (FormFieldState<dynamic> field) {
return InputDecorator(
decoration: InputDecoration(
labelText: "Select option",
contentPadding:
EdgeInsets.only(top: 10.0, bottom: 0.0),
border: InputBorder.none,
errorText: field.errorText,
),
child: Container(
height: 200,
child: CupertinoPicker(
itemExtent: 30,
children: options.map((c) => Text(c)).toList(),
onSelectedItemChanged: (index) {
field.didChange(options[index]);
},
),
),
);
},
),
copied to clipboard
Programmatically changing field value #
You can either change the value of one field at a time like so:
_formKey.currentState.fields['color_picker'].didChange(Colors.black);
copied to clipboard
Or multiple fields value like so:
_formKey.currentState.patchValue({
'age': '50',
'slider': 6.7,
'filter_chip': ['Test 1'],
'choice_chip': 'Test 2',
'rate': 4,
'chips_test': [
Contact('Andrew', '[email protected]', 'https://d2gg9evh47fn9z.cloudfront.net/800px_COLOURBOX4057996.jpg'),
],
});
copied to clipboard
Validation #
The validator attribute in fields take in a FormFieldValidator which checks the validity of the
field. A FormFieldValidator returns null if validation is successful and a String for the
errorText if validation fails.
Built-in Validators #
This package comes with several most common FormFieldValidators such as required, numeric, mail,
URL, min, max, minLength, maxLength, IP, credit card etc. with default errorText.
Available built-in validators include:
FormBuilderValidators.creditCard() - requires the field's value to be a valid credit card number.
FormBuilderValidators.date() - requires the field's value to be a valid date string.
FormBuilderValidators.email() - requires the field's value to be a valid email address.
FormBuilderValidators.equal() - requires the field's value be equal to provided object.
FormBuilderValidators.integer() - requires the field's value to be an integer.
FormBuilderValidators.ip() - requires the field's value to be a valid IP address.
FormBuilderValidators.match() - requires the field's value to match the provided regex pattern.
FormBuilderValidators.max() - requires the field's value to be less than or equal to the provided number.
FormBuilderValidators.maxLength() - requires the length of the field's value to be less than or equal to the provided maximum length.
FormBuilderValidators.min() - requires the field's value to be greater than or equal to the provided number.
FormBuilderValidators.minLength() - requires the length of the field's value to be greater than or equal to the provided minimum length.
FormBuilderValidators.numeric() - requires the field's value to be a valid number.
FormBuilderValidators.required() - requires the field have a non-empty value.
FormBuilderValidators.url() - requires the field's value to be a valid url.
Using multiple validators #
FormBuilderValidators class comes with a very useful static function named compose() which takes
any number of FormFieldValidator functions. On validation each validator is run and if any returns
a non-null value (i.e. a String), validation fails and the errorText for the field is set as the
returned string.
Validation example:
FormBuilderTextField(
name: 'age',
decoration: InputDecoration(labelText: 'Age'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.numeric(context, errorText: 'La edad debe ser numérica.'),
FormBuilderValidators.max(context, 70),
(val){
if(val < 0)
return 'We cannot have a negative age';
return null;
}
]),
),
copied to clipboard
Programmatically inducing an error #
Declare a variable to hold your error:
String _emailError;
copied to clipboard
Use the variable as the errorText within InputDecoration
FormBuilderTextField(
name: 'email',
decoration: InputDecoration(
labelText: 'Email',
errorText: _emailError,
),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
FormBuilderValidators.email(context),
]),
),
copied to clipboard
Set the error text
RaisedButton(
child: Text('Submit'),
onPressed: () async {
setState(() => _emailError = null);
if(checkIfEmailExists()){
setState(() => _emailError = 'Email already taken.');
}
},
),
copied to clipboard
Conditional validation #
You can also validate a field based on the value of another field
FormBuilderRadioGroup(
decoration: InputDecoration(labelText: 'My best language'),
name: 'my_language',
validator: FormBuilderValidators.required(context),
options: [
'Dart',
'Kotlin',
'Java',
'Swift',
'Objective-C',
'Other'
]
.map((lang) => FormBuilderFieldOption(value: lang))
.toList(growable: false),
),
FormBuilderTextField(
name: 'specify',
decoration:
InputDecoration(labelText: 'If Other, please specify'),
validator: (val) {
if (_formKey.currentState.fields['my_language']?.value ==
'Other' &&
(val == null || val.isEmpty)) {
return 'Kindly specify your language';
}
return null;
},
),
copied to clipboard
SUPPORT #
Issues and PRs #
Any kind of support in the form of reporting bugs, answering questions or PRs is always appreciated.
We especially welcome efforts to internationalize/localize the package by translating the default
validation errorText strings.
Localizing messages #
With the app’s root directory as the current directory, generate l10n/intl_messages.arb
from lib/localization/form_builder_localizations.dart:
flutter pub pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/localization/form_builder_localizations.dart
The intl_messages.arb file is a JSON format map with one entry for each Intl.message()
function defined in lib/localization/form_builder_localizations.dart. This file serves as a template for the different translations
(for example intl_en.arb and intl_es.arb are English and Spanish translations respectively). You are therefore you are required to copy the intl_messages.arb and put the content in a new file with the name of your locale with a name with format intl_<locale>.arb (e.g. intl_fr.arb for French Translations).
Translate the messages in the new file to the required language.
With the app’s root directory as the current directory, generate intl_messages_<locale>.dart for your intl_<locale>.arb file and update intl_messages_all.dart, which imports all of the messages files:
flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/localization/form_builder_localizations.dart lib/l10n/intl_<en>.arb lib/l10n/intl_messages.arb
e.g. To generate for French run: flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/localization/form_builder_localizations.dart lib/l10n/intl_fr.arb lib/l10n/intl_messages.arb
Alternatively you could run the following command to generate Dart translation files for all the intl_<locale>.arb files in the l10n/ directory:
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/localization/form_builder_localizations.dart lib/l10n/intl_*.arb
Include your new language to FormBuilderLocalization's supported languages. Go to lib/localization/form_builder_localizations.dart and include the language like so:
@override
bool isSupported(Locale locale) {
return ['en', 'es', 'fr'].contains(locale.languageCode);
}
copied to clipboard
Submit your PR and be of help to millions of people all over the world!
Coffee :-) #
If this package was helpful to you in delivering your project or you just wanna to support this
package, a cup of coffee would be highly appreciated ;-)
CREDITS #
Contributors #
Made with contributors-img.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.