Skip to main content

flutterx_forms 2.0.0 copy "flutterx_forms: ^2.0.0" to clipboard
flutterx_forms: ^2.0.0 copied to clipboard

Declarative Flutter forms with typed fields, validators, number, date, file and list inputs, a form controller and drop support.

flutterx_forms #

Typed Flutter form fields with validators and one form controller: text, integer and double with arrows, checkbox and switch, dropdowns, date and time, file and files with drop on the web, lists of fields, tabs, chips and dynamic inner forms.

Installation #

flutter pub add flutterx_forms
import 'package:flutterx_forms/flutterx_forms.dart';

Setup #

Mix FormController into the State of the form. Every field has a handle, XFormField<T>, created with field() before the form is built: it gives the value, the focus node and the controller. The mixin builds the Form, disposes the handles with the state and calls onFormSubmit when the form is valid.

class _LoginState extends State<Login> with FormController<Login> {
  late final XFormField<String> _email = field();
  late final XFormField<String> _password = field();

  @override
  Widget build(BuildContext context) => buildForm(
    autovalidateMode: AutovalidateMode.onUserInteraction,
    children: [
      XTextFormField(
        field: _email,
        decoration: const InputDecoration(labelText: 'Email'),
        textInputAction: TextInputAction.next,
        validator: validateFirst([nonBlank(() => 'Required'), matchesEmail(() => 'Not an email')]),
      ),
      XTextFormField(field: _password, obscureText: true, validator: minLen(8, () => 'At least 8 characters')),
      FilledButton(onPressed: submitForm, child: const Text('Sign in')),
    ],
  );

  @override
  Future<void> onFormSubmit() => signIn(_email.value, _password.value);
}

submitForm() validates, unfocuses and runs onFormSubmit; validateForm(), resetForm() and saveForm() act on every field. formFields lists the attached handles; with autoscroll overridden to true a failed submit focuses the first invalid field and scrolls to it. A handle read before its field is built fails: use valueOrNull or attached.

controller() returns the text controller of the handle, created on the first call: TextEditingController for String, IntEditingController for int, DoubleEditingController for double (also nullable), TextListEditingController for List<String>. Any other T throws UnsupportedError: pass a controllerFactory to field().

Fields #

Every field takes its handle as field: and an optional initialValue, that applies only when the handle has no controller yet. Validators take the non-null value.

late final XFormField<int?> _age = field();
late final XFormField<bool> _terms = field();
late final XFormField<MaterialColor> _color = field();
late final XFormField<DateTime?> _birthday = field();
late final XFormField<TimeOfDay?> _alarm = field();
late final XFormField<XFile?> _photo = field();

XIntFormField(field: _age, min: 18, max: 120, arrows: const NumberFieldArrows(step: 5)),
XBoolFormField.switchbox(field: _terms, titleText: 'I accept', validator: (value) => value ? null : 'Required'),
XDropdownButtonFormField<MaterialColor>(field: _color, items: Colors.primaries),
XDateTimeFormField(field: _birthday, lastDate: DateTime.now()),
XTimeFormField(field: _alarm),
XFileFormField(field: _photo, mimeTypes: const {'image/*'}),
  • Text: XTextFormField (XFormField<String>).
  • Numbers: XIntFormField and XDoubleFormField (XFormField<int?>, XFormField<double?>) take min, max and arrows; NumberFieldArrows sets step, onlyArrows and roundToStep. IntField and DoubleField are the same inputs outside a form.
  • Bool: XBoolFormField.checkbox and .switchbox.
  • Dropdown: XDropdownButtonFormField and XDropdownMenuFormField (with menuEntryByLabel).
  • Date and time: XDateTimeFormField (firstDate, lastDate, includeTime, dateFormat) and XTimeFormField.
  • Files: XFileFormField (XFormField<XFile?>) and XFilesFormField (XFormField<List<XFile>>) take mimeTypes and onMimeTypeNotMatch, and accept files dropped on them on the web. XDropView is the standalone drop target.

Lists #

XListFormField<E> shows a row per item with add and remove buttons; the itemBuilder gets the handle of each item. XSetFormField<E> lets each row choose among items not chosen by the other rows. XListTabsFormField<E> puts each item in a tab. XTextListFormField takes words typed in one text field and shows them as chips (List<String>).

XListFormField<String>(
  field: _tags,                                    // XFormField<List<String>>
  decoration: const InputDecoration(labelText: 'Tags'),
  maxLength: 3,
  addButtonText: 'Add tag',
  itemInitialValue: (values, index) => '',
  validator: listUnique<String>(() => 'Duplicated tag'),
  itemBuilder: (state, field, values, index) => XTextFormField(field: field, initialValue: values[index]),
),

Dynamic inner forms #

ProxyFormField<T> computes its value from inner fields that live in an XFormFieldStore: create, remove, reorder and read them by id.

ProxyFormField<List<String>>(
  field: _phones,                                  // XFormField<List<String>>
  initialValue: const [],
  initializeFields: (value, fields) => fields.addFields<String>({for (var i = 0; i < value.length; i++) '$i'}),
  computeFieldData: (values) => values.values.cast<String>().toList(),
  builder: (state, fields) => Column(
    children: [
      for (final id in fields.fields.keys) XTextFormField(field: fields.field<String>(id)),
      TextButton(onPressed: fields.addUniqueField<String>, child: const Text('Add')),
    ],
  ),
),

The inner fields are validated, saved and reset with the outer form; computeErrorText builds the error from the invalid ones. validateUnique and validateUniqueBy reject a value repeated among the fields of a store.

Validators #

nonEmpty, nonBlank, nonNull, minLen, maxLen, matches, matchesEmail, contains, gt, gte, lt, lte, oneOf, notIn, fieldEquals, fieldNotEquals, listNonEmpty, listUnique, listUniqueBy. Each takes a message getter, so messages can be localized. Comparison and pattern validators accept a null value: pair them with nonNull.

validateFirst returns the first error of several validators; validateAll joins all errors (joiner prefixes each). fieldEquals(_password, () => 'Different') compares with another handle, for a confirmation field.

Decorations #

SingleSideInputBorder(side:, margin:) draws one line on a side of the field, as a border of the InputDecoration. InputDecoratorLoader shows a progress indicator in the decorator while something loads. CustomContentInputDecorator puts any widget inside a themed decorator, as the bool and the arrows do.

Behavior #

  • reset restores the value of the first construction, also after the form was rebuilt with another initialValue.
  • The handle disposes the focus node and the controller it created, also those of a controllerFactory; the controllers, scroll controllers and nodes you pass to a field stay yours to dispose.
  • Fields removed from an XFormFieldStore (also on reset and with the state) are disposed by the store.
  • Dates are formatted with the locale of the context: for a locale other than en_US call initializeDateFormatting first. The pickers start from clock.now(), which tests control with withClock.
  • XDropView, and so the drop on the file fields, receives files only on the web; elsewhere the dialog is the way in.
  • matches and contains clash with the matchers of flutter_test: in tests import it with hide contains, matches.

Testing #

Replace the file dialog with a FileSelectorPlatform fake (file_selector_platform_interface, plugin_platform_interface): set FileSelectorPlatform.instance to a class that returns the chosen file from openFile and openFiles. Fields are driven with tester.enterText, submitForm() and resetForm().

Claude Code plugin #

The repository ships a plugin with skills that teach Claude Code to integrate the library:

/plugin marketplace add https://gitlab.com/dade.sliep/flutterx_forms
/plugin install flutterx-forms@flutterx-forms
2
likes
150
points
343
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Declarative Flutter forms with typed fields, validators, number, date, file and list inputs, a form controller and drop support.

Homepage

License

BSD-3-Clause (license)

Dependencies

clock, collection, cross_file, file_selector, flutter, flutterx_utils, intl, mime, web

More

Packages that depend on flutterx_forms