Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/udos86/flutter-fast-forms

Flutter Fast Forms is a Dart package for building Flutter forms fast
https://github.com/udos86/flutter-fast-forms

dart flutter forms package

Last synced: 5 days ago
JSON representation

Flutter Fast Forms is a Dart package for building Flutter forms fast

Awesome Lists containing this project

README

        

# Flutter Fast Forms

[![CI](https://github.com/udos86/flutter-fast-forms/workflows/CI/badge.svg)](https://github.com/udos86/flutter-fast-forms/actions)
[![Pub Version](https://img.shields.io/pub/v/flutter_fast_forms)](https://pub.dev/packages/flutter_fast_forms)
[![codecov](https://codecov.io/gh/udos86/flutter-fast-forms/branch/master/graph/badge.svg)](https://codecov.io/gh/udos86/flutter-fast-forms)

Flutter Fast Forms is the only Dart package you need to build Flutter forms fast.

It adds these missing features to the Flutter SDK:

* `FastFormControl` convenience widgets that wrap [Material](https://flutter.dev/docs/development/ui/widgets/material#Input%20and%20selections) / [Cupertino](https://flutter.dev/docs/development/ui/widgets/cupertino) form controls in a `FormField` **according** to the already built-in [`TextFormField`](https://api.flutter.dev/flutter/material/TextFormField-class.html) / [`DropdownButtonFormField`](https://api.flutter.dev/flutter/material/DropdownButtonFormField-class.html)
* `FastForm` widget that wraps the built-in `Form` widget for providing the current form field values in `onChanged` callback
* `FastFormArray` widget that aggregates a flexible number of homogeneous controls in a single `FormField`
* `FastChipsInput` widget that converts text input into chips as defined by [Material Design](https://material.io/components/chips#input-chips)
* Conditional form fields
* `touched` [**validation state**](https://github.com/flutter/flutter/issues/18885)
* Common `FormFieldValidator` functions


## Table of Contents

- [Getting Started](#getting-started)
- [Widget Catalog](#widget-catalog)
- [Adaptive Form Fields](#adaptive-form-fields)
- [Conditional Form Fields](#conditional-form-fields)
- [Custom Form Fields](#custom-form-fields)


## Getting Started

**1.** Add a `FastForm` to your widget tree:
```dart
class MyFormPage extends StatelessWidget {
MyFormPage({Key? key, required this.title}) : super(key: key);

final formKey = GlobalKey();
final String title;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: SafeArea(
child: SingleChildScrollView(
child: FastForm(
formKey: formKey,
children: [],
),
),
),
);
}
}
```

**2.** Add `FastFormControl` children to the `FastForm`:
```dart
FastForm(
formKey: formKey,
children: [
const FastTextField(
name: 'field_destination',
labelText: 'Destination',
placeholder: 'Where are you going?',
),
FastDateRangePicker(
name: 'field_check_in_out',
labelText: 'Check-in - Check-out',
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
),
const FastCheckbox(
name: 'field_travel_purpose',
labelText: 'Travel purpose',
titleText: 'I am travelling for work',
),
],
),
```

**3.** Wrap the children in a `FastFormSection` for visual grouping and consistent padding:
```dart
FastForm(
formKey: formKey,
children: [
FastFormSection(
header: const Text('My Form'),
padding: EdgeInsets.all(16.0),
children: [
const FastTextField(
name: 'field_destination',
labelText: 'Destination',
placeholder: 'Where are you going?',
),
// ...
],
),
]
),
```

## Widget Catalog

| `FastFormControl` | field value type | wraps Material widget | wraps Cupertino widget
when `adaptive: true` |
|:--------------------------------:|:------------------:|:---------------------------------------:|:--------------------------------------------------------------------:|
| `FastAutocomplete` | `String` | `Autocomplete` | no |
| `FastCheckbox` | `bool` | `CheckboxListTile` | `CupertinoCheckbox` |
| `FastChoiceChips` | `Set` | `ChoiceChip` | no |
| `FastCalendar` | `DateTime` | `CalendarDatePicker` | no |
| `FastChipsInput` | `List` | `RawAutocomplete` + `InputChip` | no |
| `FastDatePicker` | `DateTime` | `showDatePicker` | `CupertinoDatePicker` |
| `FastDateRangePicker` | `DateTimeRange` | `showDateRangePicker` | no |
| `FastDropdown` | `T` | `DropdownButtonFormField` | no |
| `FastRadioGroup` | `T` | `RadioListTile` | no |
| `FastRangeSlider` | `RangeValues` | `RangeSlider` | no |
| `FastSegmentedButton` | `Set` | `SegmentedButton` | no |
| `FastSegmentedControl` | `T extends Object` | no | `CupertinoSlidingSegmentedControl` |
| `FastSlider` | `double` | `Slider.adaptive` | `CupertinoSlider` |
| `FastSwitch` | `bool` | `SwitchListTile` | `CupertinoSwitch` |
| `FastTextField` | `String` | `TextFormField` | `CupertinoTextFormFieldRow` |
| `FastTimePicker` | `TimeOfDay` | `showTimePicker` | no
use `FastDatePicker` with
`CupertinoDatePickerMode.time` |

## Adaptive Form Fields

While some form controls are unique to a certain platform, various others are present in multiple design languages.

By default, Flutter Fast Forms uses Material widgets on any platform.

This behavior is adjustable so that platform-specific Cupertino widgets are automatically rendered on iOS.

> [!TIP]
> The [widget catalog](#widget-catalog) tells you which `FastFormControl` is adaptive.


📓 **Example**: Always use Cupertino widgets on iOS in a `FastForm`.

```dart
FastForm(
formKey: formKey,
adaptive: true,
children: [
const FastSwitch(
name: 'switch',
titleText: 'Disable text field',
),
FastTextField(
name: 'text_field',
labelText: 'Just some sample text field',
),
]
),
```
> [!NOTE]
> * When `adaptive` is set to `true` any built-in `FormFieldBuilder` returns a corresponding Cupertino widget on iOS, if it exists.


📓 **Example**: Only use the Cupertino widget on iOS for a dedicated `FastSwitch`.

```dart
FastForm(
formKey: formKey,
children: [
const FastSwitch(
name: 'switch',
adaptive: true,
titleText: 'Disable text field',
),
]
),
```

## Conditional Form Fields

Not all controls in a form are autonomous and act independent of each other.

Occasionally, the state of a form field might be directly related to the state of some other form field as well.

Flutter Fast Forms allows you to define such conditions declaratively.


📓 **Example**: A `FastTextField` that is disabled when a `FastSwitch` is selected.

**1.** Add the `conditions` property to the conditional form field and assign an empty `Map`:

```dart
const FastSwitch(
name: 'switch',
titleText: 'Disable text field',
),
FastTextField(
name: 'text_field',
labelText: 'Just some sample text field',
conditions: {},
),
```

**2.** Choose a suitable `FastConditionHandler` as `Map` key and assign a `FastConditionList`:
```dart
const FastSwitch(
name: 'switch',
titleText: 'Disable text field when selected',
),
FastTextField(
name: 'text_field',
labelText: 'Just some sample text field',
conditions: {
FastCondition.disabled: FastConditionList([]),
},
)
```
> [!NOTE]
> A `FastConditionHandler` is a function that runs whenever a `FastConditionList` is checked and determines what happens when the condition is either met or not.

**3.** Add a `FastCondition` relating the field to another field:
```dart
const FastSwitch(
name: 'switch',
titleText: 'Disable text field when selected',
),
FastTextField(
name: 'text_field',
labelText: 'Just some sample text field',
conditions: {
FastCondition.disabled: FastConditionList([
FastCondition(
target: 'switch',
test: (value, field) => value is bool && value,
),
]),
},
),
```
> [!NOTE]
> `target` is the `name` of the `FastFormField` that the form field depends on.


📓 **Example**: A `FastTextField` that is enabled when a `FastSwitch` **or** a `FastCheckbox` is selected.

```dart
const FastCheckbox(
name: 'checkbox',
titleText: 'Enable text field when selected',
),
const FastSwitch(
name: 'switch',
titleText: 'Enable text field when selected',
),
FastTextField(
name: 'text_field',
enabled: false,
labelText: 'Just some sample text field',
conditions: {
FastCondition.enabled: FastConditionList([
FastCondition(
target: 'switch',
test: (value, field) => value is bool && value,
),
FastCondition(
target: 'checkbox',
test: (value, field) => value is bool && value,
),
]),
},
),
```


📓 **Example**: A `FastTextField` that is disabled when both a `FastSwitch` **and** a `FastCheckbox` are selected.

```dart
const FastCheckbox(
name: 'checkbox',
titleText: 'Disable text field when selected',
),
const FastSwitch(
name: 'switch',
titleText: 'Disable text field when selected',
),
FastTextField(
name: 'text_field',
labelText: 'Just some sample text field',
conditions: {
FastCondition.enabled: FastConditionList(
[
FastCondition(
target: 'switch',
test: (value, field) => value is bool && value,
),
FastCondition(
target: 'checkbox',
test: (value, field) => value is bool && value,
),
],
match: FastConditionMatch.every,
),
},
),
```
> [!NOTE]
> `match` specifies how all individual test results in the list are evaluated to determine whether the condition is met.

## Custom Form Fields

There are use cases where the widget catalog does not fully satisfy your individual requirements.

As a consequence you have to add non-standard controls to your form.

With Flutter Fast Forms you're free to wrap any custom widget into a form field.


📓 **Example**: A simple widget that provides a random integer whenever a button is pressed.

**1.** Create a stateful widget class extending `FastFormField` with a corresponding `FastFormFieldState`:
```dart
class MyCustomField extends FastFormField {
const MyCustomField({
super.builder = myCustomFormFieldBuilder,
super.key,
required super.name,
});

@override
MyCustomFieldState createState() => MyCustomFieldState();
}

class MyCustomFieldState extends FastFormFieldState {
@override
MyCustomField get widget => super.widget as MyCustomField;
}
```
> [!NOTE]
> * `builder` and `name` are required constructor parameters of `FastFormField`.
> * `builder` is a standard Flutter `FormFieldBuilder`.

**2.** Implement the `FormFieldBuilder` returning your custom widget:
```dart
Widget myCustomFormFieldBuilder(FormFieldState field) {
field as MyCustomFieldState;
final MyCustomFieldState(:decoration, :didChange, :value) = field;

return InputDecorator(
decoration: decoration,
child: Row(
children: [
ElevatedButton(
child: const Text('Create random number'),
onPressed: () => didChange(Random().nextInt(1 << 32)),
),
if (value is int)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(value.toString()),
)
],
),
);
}
```
> [!NOTE]
> * Casting `field` is mandatory to access `FastFormField` properties and functions.
> * Always call `field.didChange()` to update the value of the form field.

**3.** Add all super-initializer parameters that the form field should support:
```dart
class MyCustomField extends FastFormField {
const MyCustomField({
super.builder = myCustomFormFieldBuilder,
super.decoration,
super.enabled,
super.helperText,
super.initialValue,
super.key,
super.labelText,
required super.name,
super.onChanged,
super.onReset,
super.onSaved,
super.onTouched,
super.validator,
});

@override
MyCustomFieldState createState() => MyCustomFieldState();
}
```
> [!NOTE]
> Always make sure that you apply certain super-initializer parameters like `decoration` or `enabled` in your builder functions.
> Otherwise assigning those arguments when invoking the constructor won't have any effect.