diff --git a/.github/workflows/base.yaml b/.github/workflows/base.yaml index 4ea09a383..c8a2319f1 100644 --- a/.github/workflows/base.yaml +++ b/.github/workflows/base.yaml @@ -42,11 +42,13 @@ jobs: - name: Install dependencies run: flutter pub get - name: Format code - run: dart format --set-exit-if-changed . + run: dart format --set-exit-if-changed lib/ test/ example/ - name: Analyze static code run: flutter analyze - name: Run tests run: flutter test --coverage + - name: Run fixes tests + run: dart fix --compare-to-golden test_fixes/ - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b303465..85faa3cef 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,5 @@ include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - "test_fixes/**" diff --git a/example/lib/minimal_code_example.dart b/example/lib/minimal_code_example.dart index 4164b5b98..bbc60d825 100644 --- a/example/lib/minimal_code_example.dart +++ b/example/lib/minimal_code_example.dart @@ -44,7 +44,7 @@ class _ExamplePageState extends State<_ExamplePage> { key: _formKey, child: Column( children: [ - FormBuilderFilterChip( + FormBuilderFilterChips( decoration: const InputDecoration( labelText: 'The language of my people', enabled: false, diff --git a/example/lib/sources/complete_form.dart b/example/lib/sources/complete_form.dart index 1bf193237..844947af6 100644 --- a/example/lib/sources/complete_form.dart +++ b/example/lib/sources/complete_form.dart @@ -252,7 +252,7 @@ class _CompleteFormState extends State { FormBuilderValidators.maxLength(3), ]), ), - FormBuilderFilterChip( + FormBuilderFilterChips( autovalidateMode: AutovalidateMode.onUserInteraction, decoration: const InputDecoration( labelText: 'The language of my people'), @@ -286,7 +286,7 @@ class _CompleteFormState extends State { FormBuilderValidators.maxLength(3), ]), ), - FormBuilderChoiceChip( + FormBuilderChoiceChips( autovalidateMode: AutovalidateMode.onUserInteraction, decoration: const InputDecoration( labelText: diff --git a/lib/fix_data.yaml b/lib/fix_data.yaml new file mode 100644 index 000000000..27f55ffef --- /dev/null +++ b/lib/fix_data.yaml @@ -0,0 +1,45 @@ +version: 1 +transforms: + - title: 'Remove deprecated maxChips property on FormBuilderFilterChip' + date: 2025-01-15 + element: + uris: [ 'flutter_form_builder.dart' ] + constructor: '' + inClass: 'FormBuilderFilterChip' + changes: + - kind: 'removeParameter' + name: 'maxChips' + - title: 'Remove deprecated resetIcon property on FormBuilderDateTimePicker' + date: 2025-01-15 + element: + uris: [ 'flutter_form_builder.dart' ] + constructor: '' + inClass: 'FormBuilderDateTimePicker' + changes: + - kind: 'removeParameter' + name: 'resetIcon' + - title: 'Remove deprecated onPopInvoked property on FormBuilder' + date: 2025-01-15 + element: + uris: [ 'flutter_form_builder.dart' ] + constructor: '' + inClass: 'FormBuilder' + changes: + - kind: 'removeParameter' + name: 'onPopInvoked' + - title: 'Rename FormBuilderChoiceChip to be plural' + date: 2025-01-15 + element: + uris: [ 'flutter_form_builder.dart' ] + class: 'FormBuilderChoiceChip' + changes: + - kind: 'rename' + newName: 'FormBuilderChoiceChips' + - title: 'Rename FormBuilderFilterChip to be plural' + date: 2025-01-15 + element: + uris: [ 'flutter_form_builder.dart' ] + class: 'FormBuilderFilterChip' + changes: + - kind: 'rename' + newName: 'FormBuilderFilterChips' \ No newline at end of file diff --git a/lib/src/extensions/generic_validator.dart b/lib/src/extensions/generic_validator.dart index 8ad4b1706..885c4711b 100644 --- a/lib/src/extensions/generic_validator.dart +++ b/lib/src/extensions/generic_validator.dart @@ -1,4 +1,5 @@ extension GenericValidator on T? { + /// Check if the value is empty in a generic way bool emptyValidator() { if (this == null) return true; if (this is Iterable) return (this as Iterable).isEmpty; diff --git a/lib/src/fields/form_builder_choice_chips.dart b/lib/src/fields/form_builder_choice_chips.dart index d0c444a49..890de3880 100644 --- a/lib/src/fields/form_builder_choice_chips.dart +++ b/lib/src/fields/form_builder_choice_chips.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; /// A list of `Chip`s that acts like radio buttons -class FormBuilderChoiceChip extends FormBuilderFieldDecoration { +class FormBuilderChoiceChips extends FormBuilderFieldDecoration { /// The list of items the user can select. final List> options; @@ -344,7 +344,7 @@ class FormBuilderChoiceChip extends FormBuilderFieldDecoration { final String? tooltip; /// Creates a list of `Chip`s that acts like radio buttons - FormBuilderChoiceChip({ + FormBuilderChoiceChips({ super.autovalidateMode = AutovalidateMode.disabled, super.enabled, super.focusNode, @@ -457,12 +457,12 @@ class FormBuilderChoiceChip extends FormBuilderFieldDecoration { }); @override - FormBuilderFieldDecorationState, T> createState() => + FormBuilderFieldDecorationState, T> createState() => _FormBuilderChoiceChipState(); } class _FormBuilderChoiceChipState - extends FormBuilderFieldDecorationState, T> { + extends FormBuilderFieldDecorationState, T> { void handleFocusChange() { setState(() {}); } diff --git a/lib/src/fields/form_builder_date_time_picker.dart b/lib/src/fields/form_builder_date_time_picker.dart index 782002229..f1f2d3276 100644 --- a/lib/src/fields/form_builder_date_time_picker.dart +++ b/lib/src/fields/form_builder_date_time_picker.dart @@ -40,10 +40,6 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration { /// to noon. Explicitly set this to `null` to use the current time. final TimeOfDay initialTime; - @Deprecated( - 'This property is no used anymore. Please use decoration.suffixIcon to set your desired icon') - final Widget? resetIcon; - /// Called when an enclosing form is saved. The value passed will be `null` /// if [format] fails to parse the text. // final FormFieldSetter onSaved; @@ -146,7 +142,6 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration { this.scrollPadding = const EdgeInsets.all(20.0), this.cursorWidth = 2.0, this.enableInteractiveSelection = true, - this.resetIcon = const Icon(Icons.close), this.initialTime = const TimeOfDay(hour: 12, minute: 0), this.keyboardType, this.textAlign = TextAlign.start, diff --git a/lib/src/fields/form_builder_filter_chips.dart b/lib/src/fields/form_builder_filter_chips.dart index 058804d69..d8c4b7f52 100644 --- a/lib/src/fields/form_builder_filter_chips.dart +++ b/lib/src/fields/form_builder_filter_chips.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; /// Field with chips that acts like a list checkboxes. -class FormBuilderFilterChip extends FormBuilderFieldDecoration> { +class FormBuilderFilterChips extends FormBuilderFieldDecoration> { //TODO: Add documentation final Color? backgroundColor; final Color? disabledColor; @@ -35,7 +35,7 @@ class FormBuilderFilterChip extends FormBuilderFieldDecoration> { final ShapeBorder avatarBorder; /// Creates field with chips that acts like a list checkboxes. - FormBuilderFilterChip({ + FormBuilderFilterChips({ super.autovalidateMode = AutovalidateMode.disabled, super.enabled, super.focusNode, @@ -59,7 +59,7 @@ class FormBuilderFilterChip extends FormBuilderFieldDecoration> { this.labelPadding, this.labelStyle, this.materialTapTargetSize, - this.maxChips, + @Deprecated('Useless property. Please remove it.') this.maxChips, this.padding, this.pressElevation, this.runAlignment = WrapAlignment.start, @@ -143,9 +143,9 @@ class FormBuilderFilterChip extends FormBuilderFieldDecoration> { ); @override - FormBuilderFieldDecorationState, List> + FormBuilderFieldDecorationState, List> createState() => _FormBuilderFilterChipState(); } class _FormBuilderFilterChipState extends FormBuilderFieldDecorationState< - FormBuilderFilterChip, List> {} + FormBuilderFilterChips, List> {} diff --git a/lib/src/fields/form_builder_text_field.dart b/lib/src/fields/form_builder_text_field.dart index ea8b577d0..fd3ad8bb4 100644 --- a/lib/src/fields/form_builder_text_field.dart +++ b/lib/src/fields/form_builder_text_field.dart @@ -424,6 +424,11 @@ class FormBuilderTextField extends FormBuilderFieldDecoration { this.contentInsertionConfiguration, this.spellCheckConfiguration, this.clipBehavior = Clip.hardEdge, + @Deprecated( + 'This property will be removed in the next Flutter stable versions. ' + 'Use FocusNode.canRequestFocus instead. ' + 'Ref: https://docs.flutter.dev/release/breaking-changes/can-request-focus', + ) this.canRequestFocus = true, this.cursorErrorColor, this.cursorOpacityAnimates, diff --git a/lib/src/form_builder.dart b/lib/src/form_builder.dart index 5f6e296bd..6bfc961b0 100644 --- a/lib/src/form_builder.dart +++ b/lib/src/form_builder.dart @@ -10,9 +10,6 @@ class FormBuilder extends StatefulWidget { /// will rebuild. final VoidCallback? onChanged; - /// DEPRECATED: Use [onPopInvokedWithResult] instead. - final void Function(bool)? onPopInvoked; - /// {@macro flutter.widgets.navigator.onPopInvokedWithResult} /// /// {@tool dartpad} @@ -105,11 +102,6 @@ class FormBuilder extends StatefulWidget { required this.child, this.onChanged, this.autovalidateMode, - @Deprecated( - 'Use onPopInvokedWithResult instead. ' - 'This feature was deprecated after v3.22.0-12.0.pre.', - ) - this.onPopInvoked, this.onPopInvokedWithResult, this.initialValue = const {}, this.skipDisabled = false, @@ -143,6 +135,7 @@ class FormBuilderState extends State { /// Only used to internal logic bool get focusOnInvalid => _focusOnInvalid; + /// Get if form is enabled bool get enabled => widget.enabled; /// Verify if all fields on form are valid. @@ -171,6 +164,7 @@ class FormBuilderState extends State { /// Get all fields of form. FormBuilderFields get fields => _fields; + /// Get all fields values of form. Map get instantValue => Map.unmodifiable( _instantValue.map( (key, value) => MapEntry( @@ -204,15 +198,18 @@ class FormBuilderState extends State { initialValue[name]; } + /// Get a field value by name void setInternalFieldValue(String name, T? value) { _instantValue[name] = value; widget.onChanged?.call(); } + /// Remove a field value by name void removeInternalFieldValue(String name) { _instantValue.remove(name); } + /// Register a field on form void registerField(String name, FormBuilderFieldState field) { // Each field must have a unique name. Ideally we could simply: // assert(!_fields.containsKey(name)); @@ -242,6 +239,7 @@ class FormBuilderState extends State { ); } + /// Unregister a field on form void unregisterField(String name, FormBuilderFieldState field) { assert( _fields.containsKey(name), @@ -270,6 +268,7 @@ class FormBuilderState extends State { } } + /// Save form values void save() { _formKey.currentState!.save(); // Copy values from instant to saved @@ -277,16 +276,6 @@ class FormBuilderState extends State { _savedValue.addAll(_instantValue); } - @Deprecated( - 'Will be remove to avoid redundancy. Use fields[name]?.invalidate(errorText) instead') - void invalidateField({required String name, String? errorText}) => - fields[name]?.invalidate(errorText ?? ''); - - @Deprecated( - 'Will be remove to avoid redundancy. Use fields.first.invalidate(errorText) instead') - void invalidateFirstField({required String errorText}) => - fields.values.first.invalidate(errorText); - /// Validate all fields of form /// /// Focus to first invalid field when has field invalid, if [focusOnInvalid] is `true`. @@ -343,7 +332,7 @@ class FormBuilderState extends State { ); } - /// Reset form to `initialValue` + /// Reset form to `initialValue` set on FormBuilder or on each field. void reset() { _formKey.currentState?.reset(); } @@ -376,8 +365,6 @@ class FormBuilderState extends State { key: _formKey, autovalidateMode: widget.autovalidateMode, onPopInvokedWithResult: widget.onPopInvokedWithResult, - // ignore: deprecated_member_use - onPopInvoked: widget.onPopInvoked, canPop: widget.canPop, // `onChanged` is called during setInternalFieldValue else will be called early child: _FormBuilderScope( diff --git a/lib/src/form_builder_field.dart b/lib/src/form_builder_field.dart index c2f498b17..c90f373ce 100644 --- a/lib/src/form_builder_field.dart +++ b/lib/src/form_builder_field.dart @@ -71,12 +71,17 @@ class FormBuilderFieldState, T> FormBuilderState? _formBuilderState; bool _touched = false; bool _dirty = false; + + /// The focus node that is used to focus this field. late FocusNode effectiveFocusNode; + + /// The focus attachment for the [effectiveFocusNode]. FocusAttachment? focusAttachment; @override F get widget => super.widget as F; + /// Returns the parent [FormBuilderState] if it exists. FormBuilderState? get formState => _formBuilderState; /// Returns the initial value, which may be declared at the field, or by the @@ -91,18 +96,33 @@ class FormBuilderFieldState, T> widget.valueTransformer == null ? value : widget.valueTransformer!(value); @override + + /// Returns the current error text, + /// which may be a validation error or a custom error text. String? get errorText => super.errorText ?? _customErrorText; @override + + /// Returns `true` if the field has an error or has a custom error text. bool get hasError => super.hasError || errorText != null; @override + + /// Returns `true` if the field is valid and has no custom error text. bool get isValid => super.isValid && _customErrorText == null; + /// Returns `true` if the field is valid. bool get valueIsValid => super.isValid; + + /// Returns `true` if the field has an error. bool get valueHasError => super.hasError; + /// Returns `true` if the field is enabled and the parent FormBuilder is enabled. bool get enabled => widget.enabled && (_formBuilderState?.enabled ?? true); + + /// Returns `true` if the field is read only. + /// + /// See [FormBuilder.skipDisabled] for more information. bool get readOnly => !(_formBuilderState?.widget.skipDisabled ?? false); /// Will be true if the field is dirty @@ -199,6 +219,10 @@ class FormBuilderFieldState, T> } @override + + /// Reset field value to initial value + /// + /// Also reset custom error text if exists, and set [isDirty] to `false`. void reset() { super.reset(); didChange(initialValue); @@ -276,10 +300,12 @@ class FormBuilderFieldState, T> ); } + /// Focus field void focus() { FocusScope.of(context).requestFocus(effectiveFocusNode); } + /// Scroll to show field void ensureScrollableVisibility() { Scrollable.ensureVisible(context); } diff --git a/lib/src/form_builder_field_decoration.dart b/lib/src/form_builder_field_decoration.dart index 7fb636caa..4317867c9 100644 --- a/lib/src/form_builder_field_decoration.dart +++ b/lib/src/form_builder_field_decoration.dart @@ -35,6 +35,7 @@ class FormBuilderFieldDecorationState, @override F get widget => super.widget as F; + /// Get the decoration with the current state InputDecoration get decoration => widget.decoration.copyWith( // Read only allow show error to support property skipDisabled errorText: widget.enabled || readOnly diff --git a/lib/src/form_builder_field_option.dart b/lib/src/form_builder_field_option.dart index fe3e8ba0f..1a68f9e00 100644 --- a/lib/src/form_builder_field_option.dart +++ b/lib/src/form_builder_field_option.dart @@ -5,7 +5,10 @@ import 'package:flutter/widgets.dart'; /// The type `T` is the type of the value the entry represents. All the entries /// in a given menu must represent values with consistent types. class FormBuilderFieldOption extends StatelessWidget { + /// The widget to display in list of options. final Widget? child; + + /// The value of the option. final T value; /// Creates an option for fields with selection options diff --git a/lib/src/options/form_builder_chip_option.dart b/lib/src/options/form_builder_chip_option.dart index d8f390bd6..481e6f7f4 100644 --- a/lib/src/options/form_builder_chip_option.dart +++ b/lib/src/options/form_builder_chip_option.dart @@ -6,6 +6,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; /// The type `T` is the type of the value the entry represents. All the entries /// in a given menu must represent values with consistent types. class FormBuilderChipOption extends FormBuilderFieldOption { + /// The avatar to display in list of options. final Widget? avatar; /// Creates an option for fields with selection options diff --git a/lib/src/widgets/grouped_checkbox.dart b/lib/src/widgets/grouped_checkbox.dart index d8d2421e7..5c2f50d13 100644 --- a/lib/src/widgets/grouped_checkbox.dart +++ b/lib/src/widgets/grouped_checkbox.dart @@ -23,6 +23,9 @@ class GroupedCheckbox extends StatelessWidget { /// The color to use when this checkbox is checked. /// /// Defaults to [ColorScheme.secondary]. + /// + /// If [fillColor] returns a non-null color in the [WidgetState.selected] + /// state, it will be used instead of this color. final Color? activeColor; final VisualDensity? visualDensity; diff --git a/test/src/fields/form_builder_choice_chips_test.dart b/test/src/fields/form_builder_choice_chips_test.dart index 1cdef27e8..8bf4a68a3 100644 --- a/test/src/fields/form_builder_choice_chips_test.dart +++ b/test/src/fields/form_builder_choice_chips_test.dart @@ -11,7 +11,7 @@ void main() { testWidgets('basic', (WidgetTester tester) async { const widgetName = 'cc1'; - final testWidget = FormBuilderChoiceChip( + final testWidget = FormBuilderChoiceChips( name: widgetName, options: const [ FormBuilderChipOption(key: ValueKey('1'), value: 1), @@ -37,7 +37,7 @@ void main() { testWidgets('to FormBuilder', (WidgetTester tester) async { const widgetName = 'cc2'; - final testWidget = FormBuilderChoiceChip( + final testWidget = FormBuilderChoiceChips( name: widgetName, options: const [ FormBuilderChipOption(key: ValueKey('1'), value: 1), @@ -62,7 +62,7 @@ void main() { testWidgets('to Widget', (WidgetTester tester) async { const widgetName = 'cc3'; - final testWidget = FormBuilderChoiceChip( + final testWidget = FormBuilderChoiceChips( name: widgetName, initialValue: 1, options: const [ @@ -87,7 +87,7 @@ void main() { testWidgets('When press tab, field will be focused', (WidgetTester tester) async { const widgetName = 'cb1'; - final testWidget = FormBuilderChoiceChip( + final testWidget = FormBuilderChoiceChips( name: widgetName, options: const [ FormBuilderChipOption(key: ValueKey('1'), value: 1), diff --git a/test/src/fields/form_builder_filter_chips_test.dart b/test/src/fields/form_builder_filter_chips_test.dart index da207b821..0d5eff9cc 100644 --- a/test/src/fields/form_builder_filter_chips_test.dart +++ b/test/src/fields/form_builder_filter_chips_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('basic', (WidgetTester tester) async { const widgetName = 'formBuilderFilterChip'; - final testWidget = FormBuilderFilterChip( + final testWidget = FormBuilderFilterChips( name: widgetName, options: const [ FormBuilderChipOption(key: ValueKey('1'), value: 1), @@ -35,7 +35,7 @@ void main() { testWidgets('to FormBuilder', (WidgetTester tester) async { const widgetName = 'fc2'; - final testWidget = FormBuilderFilterChip( + final testWidget = FormBuilderFilterChips( name: widgetName, options: const [ FormBuilderChipOption(key: ValueKey('1'), value: 1), @@ -66,7 +66,7 @@ void main() { testWidgets('to Widget', (WidgetTester tester) async { const widgetName = 'fc3'; - final testWidget = FormBuilderFilterChip( + final testWidget = FormBuilderFilterChips( name: widgetName, initialValue: const [1], options: const [ @@ -95,7 +95,7 @@ void main() { testWidgets('When press tab, field will be focused', (WidgetTester tester) async { const widgetName = 'key'; - final testWidget = FormBuilderFilterChip( + final testWidget = FormBuilderFilterChips( name: widgetName, options: const [ FormBuilderChipOption(key: ValueKey('1'), value: 1), diff --git a/test_fixes/analysis_options.yaml b/test_fixes/analysis_options.yaml new file mode 100644 index 000000000..f9b303465 --- /dev/null +++ b/test_fixes/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/test_fixes/fixes_10.0.0.dart b/test_fixes/fixes_10.0.0.dart new file mode 100644 index 000000000..f23e6f09d --- /dev/null +++ b/test_fixes/fixes_10.0.0.dart @@ -0,0 +1,98 @@ +// test_fixes/fix_test.dart +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter FormBuilder Example', + debugShowCheckedModeBanner: false, + home: const _ExamplePage(), + ); + } +} + +class _ExamplePage extends StatefulWidget { + const _ExamplePage(); + + @override + State<_ExamplePage> createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State<_ExamplePage> { + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Minimal code example')), + body: Padding( + padding: const EdgeInsets.all(16), + child: FormBuilder( + key: _formKey, + onPopInvoked: (p0) {}, + child: Column( + children: [ + FormBuilderDateTimePicker( + name: 'date', + resetIcon: const Icon(Icons.clear), + ), + FormBuilderChoiceChip( + name: 'choice_chip', + options: const [ + 'Option 1', + 'Option 2', + 'Option 3', + ].map((e) => FormBuilderChipOption(value: e)).toList(), + ), + FormBuilderFilterChip( + maxChips: 2, + decoration: const InputDecoration( + labelText: 'The language of my people', + enabled: false, + ), + name: 'languages_filter', + selectedColor: Colors.red, + options: const [ + FormBuilderChipOption( + value: 'Dart', + avatar: CircleAvatar(child: Text('D')), + ), + FormBuilderChipOption( + value: 'Kotlin', + avatar: CircleAvatar(child: Text('K')), + ), + FormBuilderChipOption( + value: 'Java', + avatar: CircleAvatar(child: Text('J')), + ), + FormBuilderChipOption( + value: 'Swift', + avatar: CircleAvatar(child: Text('S')), + ), + FormBuilderChipOption( + value: 'Objective-C', + avatar: CircleAvatar(child: Text('O')), + ), + ], + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () { + _formKey.currentState?.saveAndValidate(); + debugPrint(_formKey.currentState?.value.toString()); + }, + child: const Text('Print'), + ) + ], + ), + ), + ), + ); + } +} diff --git a/test_fixes/fixes_10.0.0.dart.expect b/test_fixes/fixes_10.0.0.dart.expect new file mode 100644 index 000000000..5bc87a096 --- /dev/null +++ b/test_fixes/fixes_10.0.0.dart.expect @@ -0,0 +1,95 @@ +// test_fixes/fix_test.dart +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter FormBuilder Example', + debugShowCheckedModeBanner: false, + home: const _ExamplePage(), + ); + } +} + +class _ExamplePage extends StatefulWidget { + const _ExamplePage(); + + @override + State<_ExamplePage> createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State<_ExamplePage> { + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Minimal code example')), + body: Padding( + padding: const EdgeInsets.all(16), + child: FormBuilder( + key: _formKey, + child: Column( + children: [ + FormBuilderDateTimePicker( + name: 'date', + ), + FormBuilderChoiceChips( + name: 'choice_chip', + options: const [ + 'Option 1', + 'Option 2', + 'Option 3', + ].map((e) => FormBuilderChipOption(value: e)).toList(), + ), + FormBuilderFilterChips( + decoration: const InputDecoration( + labelText: 'The language of my people', + enabled: false, + ), + name: 'languages_filter', + selectedColor: Colors.red, + options: const [ + FormBuilderChipOption( + value: 'Dart', + avatar: CircleAvatar(child: Text('D')), + ), + FormBuilderChipOption( + value: 'Kotlin', + avatar: CircleAvatar(child: Text('K')), + ), + FormBuilderChipOption( + value: 'Java', + avatar: CircleAvatar(child: Text('J')), + ), + FormBuilderChipOption( + value: 'Swift', + avatar: CircleAvatar(child: Text('S')), + ), + FormBuilderChipOption( + value: 'Objective-C', + avatar: CircleAvatar(child: Text('O')), + ), + ], + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () { + _formKey.currentState?.saveAndValidate(); + debugPrint(_formKey.currentState?.value.toString()); + }, + child: const Text('Print'), + ) + ], + ), + ), + ), + ); + } +}