1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/material.dart';
6/// @docImport 'package:flutter/services.dart';
7library;
8
9import 'dart:async';
10
11import 'package:flutter/foundation.dart';
12import 'package:flutter/rendering.dart';
13
14import 'basic.dart';
15import 'binding.dart';
16import 'focus_manager.dart';
17import 'focus_scope.dart';
18import 'framework.dart';
19import 'navigator.dart';
20import 'pop_scope.dart';
21import 'restoration.dart';
22import 'restoration_properties.dart';
23import 'routes.dart';
24import 'will_pop_scope.dart';
25
26// Duration for delay before announcement in IOS so that the announcement won't be interrupted.
27const Duration _kIOSAnnouncementDelayDuration = Duration(seconds: 1);
28
29// Examples can assume:
30// late BuildContext context;
31
32/// An optional container for grouping together multiple form field widgets
33/// (e.g. [TextField] widgets).
34///
35/// Each individual form field should be wrapped in a [FormField] widget, with
36/// the [Form] widget as a common ancestor of all of those. Call methods on
37/// [FormState] to save, reset, or validate each [FormField] that is a
38/// descendant of this [Form]. To obtain the [FormState], you may use [Form.of]
39/// with a context whose ancestor is the [Form], or pass a [GlobalKey] to the
40/// [Form] constructor and call [GlobalKey.currentState].
41///
42/// {@tool dartpad}
43/// This example shows a [Form] with one [TextFormField] to enter an email
44/// address and an [ElevatedButton] to submit the form. A [GlobalKey] is used here
45/// to identify the [Form] and validate input.
46///
47/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/form.png)
48///
49/// ** See code in examples/api/lib/widgets/form/form.0.dart **
50/// {@end-tool}
51///
52/// See also:
53///
54/// * [GlobalKey], a key that is unique across the entire app.
55/// * [FormField], a single form field widget that maintains the current state.
56/// * [TextFormField], a convenience widget that wraps a [TextField] widget in a [FormField].
57class Form extends StatefulWidget {
58 /// Creates a container for form fields.
59 const Form({
60 super.key,
61 required this.child,
62 this.canPop,
63 @Deprecated(
64 'Use onPopInvokedWithResult instead. '
65 'This feature was deprecated after v3.22.0-12.0.pre.',
66 )
67 this.onPopInvoked,
68 this.onPopInvokedWithResult,
69 @Deprecated(
70 'Use canPop and/or onPopInvokedWithResult instead. '
71 'This feature was deprecated after v3.12.0-1.0.pre.',
72 )
73 this.onWillPop,
74 this.onChanged,
75 AutovalidateMode? autovalidateMode,
76 }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled,
77 assert(
78 onPopInvokedWithResult == null || onPopInvoked == null,
79 'onPopInvoked is deprecated; use onPopInvokedWithResult',
80 ),
81 assert(
82 ((onPopInvokedWithResult ?? onPopInvoked ?? canPop) == null) || onWillPop == null,
83 'onWillPop is deprecated; use canPop and/or onPopInvokedWithResult.',
84 );
85
86 /// Returns the [FormState] of the closest [Form] widget which encloses the
87 /// given context, or null if none is found.
88 ///
89 /// Typical usage is as follows:
90 ///
91 /// ```dart
92 /// FormState? form = Form.maybeOf(context);
93 /// form?.save();
94 /// ```
95 ///
96 /// Calling this method will create a dependency on the closest [Form] in the
97 /// [context], if there is one.
98 ///
99 /// See also:
100 ///
101 /// * [Form.of], which is similar to this method, but asserts if no [Form]
102 /// ancestor is found.
103 static FormState? maybeOf(BuildContext context) {
104 final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
105 return scope?._formState;
106 }
107
108 /// Returns the [FormState] of the closest [Form] widget which encloses the
109 /// given context.
110 ///
111 /// Typical usage is as follows:
112 ///
113 /// ```dart
114 /// FormState form = Form.of(context);
115 /// form.save();
116 /// ```
117 ///
118 /// If no [Form] ancestor is found, this will assert in debug mode, and throw
119 /// an exception in release mode.
120 ///
121 /// Calling this method will create a dependency on the closest [Form] in the
122 /// [context].
123 ///
124 /// See also:
125 ///
126 /// * [Form.maybeOf], which is similar to this method, but returns null if no
127 /// [Form] ancestor is found.
128 static FormState of(BuildContext context) {
129 final FormState? formState = maybeOf(context);
130 assert(() {
131 if (formState == null) {
132 throw FlutterError(
133 'Form.of() was called with a context that does not contain a Form widget.\n'
134 'No Form widget ancestor could be found starting from the context that '
135 'was passed to Form.of(). This can happen because you are using a widget '
136 'that looks for a Form ancestor, but no such ancestor exists.\n'
137 'The context used was:\n'
138 ' $context',
139 );
140 }
141 return true;
142 }());
143 return formState!;
144 }
145
146 /// The widget below this widget in the tree.
147 ///
148 /// This is the root of the widget hierarchy that contains this form.
149 ///
150 /// {@macro flutter.widgets.ProxyWidget.child}
151 final Widget child;
152
153 /// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
154 /// that contains the form.
155 ///
156 /// If the callback returns a Future that resolves to false, the form's route
157 /// will not be popped.
158 ///
159 /// See also:
160 ///
161 /// * [WillPopScope], another widget that provides a way to intercept the
162 /// back button.
163 @Deprecated(
164 'Use canPop and/or onPopInvokedWithResult instead. '
165 'This feature was deprecated after v3.12.0-1.0.pre.',
166 )
167 final WillPopCallback? onWillPop;
168
169 /// {@macro flutter.widgets.PopScope.canPop}
170 ///
171 /// {@tool dartpad}
172 /// This sample demonstrates how to use this parameter to show a confirmation
173 /// dialog when a navigation pop would cause form data to be lost.
174 ///
175 /// ** See code in examples/api/lib/widgets/form/form.1.dart **
176 /// {@end-tool}
177 ///
178 /// See also:
179 ///
180 /// * [onPopInvokedWithResult], which also comes from [PopScope] and is often used in
181 /// conjunction with this parameter.
182 /// * [PopScope.canPop], which is what [Form] delegates to internally.
183 final bool? canPop;
184
185 /// {@macro flutter.widgets.navigator.onPopInvokedWithResult}
186 @Deprecated(
187 'Use onPopInvokedWithResult instead. '
188 'This feature was deprecated after v3.22.0-12.0.pre.',
189 )
190 final PopInvokedCallback? onPopInvoked;
191
192 /// {@macro flutter.widgets.navigator.onPopInvokedWithResult}
193 ///
194 /// {@tool dartpad}
195 /// This sample demonstrates how to use this parameter to show a confirmation
196 /// dialog when a navigation pop would cause form data to be lost.
197 ///
198 /// ** See code in examples/api/lib/widgets/form/form.1.dart **
199 /// {@end-tool}
200 ///
201 /// See also:
202 ///
203 /// * [canPop], which also comes from [PopScope] and is often used in
204 /// conjunction with this parameter.
205 /// * [PopScope.onPopInvokedWithResult], which is what [Form] delegates to internally.
206 final PopInvokedWithResultCallback<Object?>? onPopInvokedWithResult;
207
208 /// Called when one of the form fields changes.
209 ///
210 /// In addition to this callback being invoked, all the form fields themselves
211 /// will rebuild.
212 final VoidCallback? onChanged;
213
214 /// Used to enable/disable form fields auto validation and update their error
215 /// text.
216 ///
217 /// {@macro flutter.widgets.FormField.autovalidateMode}
218 final AutovalidateMode autovalidateMode;
219
220 void _callPopInvoked(bool didPop, Object? result) {
221 if (onPopInvokedWithResult != null) {
222 onPopInvokedWithResult!(didPop, result);
223 return;
224 }
225 onPopInvoked?.call(didPop);
226 }
227
228 @override
229 FormState createState() => FormState();
230}
231
232/// State associated with a [Form] widget.
233///
234/// A [FormState] object can be used to [save], [reset], and [validate] every
235/// [FormField] that is a descendant of the associated [Form].
236///
237/// Typically obtained via [Form.of].
238class FormState extends State<Form> {
239 int _generation = 0;
240 bool _hasInteractedByUser = false;
241 final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};
242
243 // Called when a form field has changed. This will cause all form fields
244 // to rebuild, useful if form fields have interdependencies.
245 void _fieldDidChange() {
246 widget.onChanged?.call();
247
248 _hasInteractedByUser = _fields.any(
249 (FormFieldState<dynamic> field) => field._hasInteractedByUser.value,
250 );
251 _forceRebuild();
252 }
253
254 void _forceRebuild() {
255 setState(() {
256 ++_generation;
257 });
258 }
259
260 void _register(FormFieldState<dynamic> field) {
261 _fields.add(field);
262 }
263
264 void _unregister(FormFieldState<dynamic> field) {
265 _fields.remove(field);
266 }
267
268 @protected
269 @override
270 Widget build(BuildContext context) {
271 switch (widget.autovalidateMode) {
272 case AutovalidateMode.always:
273 _validate();
274 case AutovalidateMode.onUserInteraction:
275 if (_hasInteractedByUser) {
276 _validate();
277 }
278 case AutovalidateMode.onUnfocus:
279 case AutovalidateMode.disabled:
280 break;
281 }
282
283 if (widget.canPop != null || (widget.onPopInvokedWithResult ?? widget.onPopInvoked) != null) {
284 return PopScope<Object?>(
285 canPop: widget.canPop ?? true,
286 onPopInvokedWithResult: widget._callPopInvoked,
287 child: _FormScope(formState: this, generation: _generation, child: widget.child),
288 );
289 }
290
291 return WillPopScope(
292 onWillPop: widget.onWillPop,
293 child: _FormScope(formState: this, generation: _generation, child: widget.child),
294 );
295 }
296
297 /// Saves every [FormField] that is a descendant of this [Form].
298 void save() {
299 for (final FormFieldState<dynamic> field in _fields) {
300 field.save();
301 }
302 }
303
304 /// Resets every [FormField] that is a descendant of this [Form] back to its
305 /// [FormField.initialValue].
306 ///
307 /// The [Form.onChanged] callback will be called.
308 ///
309 /// If the form's [Form.autovalidateMode] property is [AutovalidateMode.always],
310 /// the fields will all be revalidated after being reset.
311 void reset() {
312 for (final FormFieldState<dynamic> field in _fields) {
313 field.reset();
314 }
315 _hasInteractedByUser = false;
316 _fieldDidChange();
317 }
318
319 /// Validates every [FormField] that is a descendant of this [Form], and
320 /// returns true if there are no errors.
321 ///
322 /// The form will rebuild to report the results.
323 ///
324 /// See also:
325 /// * [validateGranularly], which also validates descendant [FormField]s,
326 /// but instead returns a [Set] of fields with errors.
327 bool validate() {
328 _hasInteractedByUser = true;
329 _forceRebuild();
330 return _validate();
331 }
332
333 /// Validates every [FormField] that is a descendant of this [Form], and
334 /// returns a [Set] of [FormFieldState] of the invalid field(s) only, if any.
335 ///
336 /// This method can be useful to highlight field(s) with errors.
337 ///
338 /// The form will rebuild to report the results.
339 ///
340 /// See also:
341 /// * [validate], which also validates descendant [FormField]s,
342 /// and return true if there are no errors.
343 Set<FormFieldState<Object?>> validateGranularly() {
344 final Set<FormFieldState<Object?>> invalidFields = <FormFieldState<Object?>>{};
345 _hasInteractedByUser = true;
346 _forceRebuild();
347 _validate(invalidFields);
348 return invalidFields;
349 }
350
351 bool _validate([Set<FormFieldState<Object?>>? invalidFields]) {
352 bool hasError = false;
353 String errorMessage = '';
354 final bool validateOnFocusChange = widget.autovalidateMode == AutovalidateMode.onUnfocus;
355
356 for (final FormFieldState<dynamic> field in _fields) {
357 final bool hasFocus = field._focusNode.hasFocus;
358
359 if (!validateOnFocusChange || !hasFocus || (validateOnFocusChange && hasFocus)) {
360 final bool isFieldValid = field.validate();
361 hasError |= !isFieldValid;
362 // Ensure that only the first error message gets announced to the user.
363 if (errorMessage.isEmpty) {
364 errorMessage = field.errorText ?? '';
365 }
366 if (invalidFields != null && !isFieldValid) {
367 invalidFields.add(field);
368 }
369 }
370 }
371
372 if (errorMessage.isNotEmpty) {
373 final TextDirection directionality = Directionality.of(context);
374 if (defaultTargetPlatform == TargetPlatform.iOS) {
375 unawaited(
376 Future<void>(() async {
377 await Future<void>.delayed(_kIOSAnnouncementDelayDuration);
378 SemanticsService.announce(
379 errorMessage,
380 directionality,
381 assertiveness: Assertiveness.assertive,
382 );
383 }),
384 );
385 } else {
386 SemanticsService.announce(
387 errorMessage,
388 directionality,
389 assertiveness: Assertiveness.assertive,
390 );
391 }
392 }
393
394 return !hasError;
395 }
396}
397
398class _FormScope extends InheritedWidget {
399 const _FormScope({required super.child, required FormState formState, required int generation})
400 : _formState = formState,
401 _generation = generation;
402
403 final FormState _formState;
404
405 /// Incremented every time a form field has changed. This lets us know when
406 /// to rebuild the form.
407 final int _generation;
408
409 /// The [Form] associated with this widget.
410 Form get form => _formState.widget;
411
412 @override
413 bool updateShouldNotify(_FormScope old) => _generation != old._generation;
414}
415
416/// Signature for validating a form field.
417///
418/// Returns an error string to display if the input is invalid, or null
419/// otherwise.
420///
421/// Used by [FormField.validator].
422typedef FormFieldValidator<T> = String? Function(T? value);
423
424/// Signature for a callback that builds an error widget.
425///
426/// See also:
427///
428/// * [FormField.errorBuilder], which is of this type, and passes the result error
429/// given by [TextFormField.validator].
430typedef FormFieldErrorBuilder = Widget Function(BuildContext context, String errorText);
431
432/// Signature for being notified when a form field changes value.
433///
434/// Used by [FormField.onSaved].
435typedef FormFieldSetter<T> = void Function(T? newValue);
436
437/// Signature for building the widget representing the form field.
438///
439/// Used by [FormField.builder].
440typedef FormFieldBuilder<T> = Widget Function(FormFieldState<T> field);
441
442/// A single form field.
443///
444/// This widget maintains the current state of the form field, so that updates
445/// and validation errors are visually reflected in the UI.
446///
447/// When used inside a [Form], you can use methods on [FormState] to query or
448/// manipulate the form data as a whole. For example, calling [FormState.save]
449/// will invoke each [FormField]'s [onSaved] callback in turn.
450///
451/// Use a [GlobalKey] with [FormField] if you want to retrieve its current
452/// state, for example if you want one form field to depend on another.
453///
454/// A [Form] ancestor is not required. The [Form] allows one to
455/// save, reset, or validate multiple fields at once. To use without a [Form],
456/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
457/// save or reset the form field.
458///
459/// See also:
460///
461/// * [Form], which is the widget that aggregates the form fields.
462/// * [TextField], which is a commonly used form field for entering text.
463class FormField<T> extends StatefulWidget {
464 /// Creates a single form field.
465 const FormField({
466 super.key,
467 required this.builder,
468 this.onSaved,
469 this.forceErrorText,
470 this.validator,
471 this.errorBuilder,
472 this.initialValue,
473 this.enabled = true,
474 AutovalidateMode? autovalidateMode,
475 this.restorationId,
476 }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
477
478 /// Function that returns the widget representing this form field.
479 ///
480 /// It is passed the form field state as input, containing the current value
481 /// and validation state of this field.
482 final FormFieldBuilder<T> builder;
483
484 /// An optional method to call with the final value when the form is saved via
485 /// [FormState.save].
486 final FormFieldSetter<T>? onSaved;
487
488 /// An optional property that forces the [FormFieldState] into an error state
489 /// by directly setting the [FormFieldState.errorText] property without
490 /// running the validator function.
491 ///
492 /// When the [forceErrorText] property is provided, the [FormFieldState.errorText]
493 /// will be set to the provided value, causing the form field to be considered
494 /// invalid and to display the error message specified.
495 ///
496 /// When [validator] is provided, [forceErrorText] will override any error that it
497 /// returns. [validator] will not be called unless [forceErrorText] is null.
498 ///
499 /// See also:
500 ///
501 /// * [InputDecoration.errorText], which is used to display error messages in the text
502 /// field's decoration without effecting the field's state. When [forceErrorText] is
503 /// not null, it will override [InputDecoration.errorText] value.
504 final String? forceErrorText;
505
506 /// An optional method that validates an input. Returns an error string to
507 /// display if the input is invalid, or null otherwise.
508 ///
509 /// The returned value is exposed by the [FormFieldState.errorText] property.
510 /// The [TextFormField] uses this to override the [InputDecoration.errorText]
511 /// value.
512 ///
513 /// Alternating between error and normal state can cause the height of the
514 /// [TextFormField] to change if no other subtext decoration is set on the
515 /// field. To create a field whose height is fixed regardless of whether or
516 /// not an error is displayed, either wrap the [TextFormField] in a fixed
517 /// height parent like [SizedBox], or set the [InputDecoration.helperText]
518 /// parameter to a space.
519 final FormFieldValidator<T>? validator;
520
521 /// Function that returns the widget representing the error to display.
522 ///
523 /// It is passed the form field validator error string as input.
524 /// The resulting widget is passed to [InputDecoration.error].
525 ///
526 /// If null, the validator error string is passed to
527 /// [InputDecoration.errorText].
528 final FormFieldErrorBuilder? errorBuilder;
529
530 /// An optional value to initialize the form field to, or null otherwise.
531 ///
532 /// This is called `value` in the [DropdownButtonFormField] constructor to be
533 /// consistent with [DropdownButton].
534 ///
535 /// The `initialValue` affects the form field's state in two cases:
536 /// 1. When the form field is first built, `initialValue` determines the field's initial state.
537 /// 2. When [FormFieldState.reset] is called (either directly or by calling
538 /// [FormFieldState.reset]), the form field is reset to this `initialValue`.
539 final T? initialValue;
540
541 /// Whether the form is able to receive user input.
542 ///
543 /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled],
544 /// the field will be auto validated. Likewise, if this field is false, the widget
545 /// will not be validated regardless of [autovalidateMode].
546 final bool enabled;
547
548 /// Used to enable/disable this form field auto validation and update its
549 /// error text.
550 ///
551 /// {@template flutter.widgets.FormField.autovalidateMode}
552 /// If [AutovalidateMode.onUserInteraction], this FormField will only
553 /// auto-validate after its content changes. If [AutovalidateMode.always], it
554 /// will auto-validate even without user interaction. If
555 /// [AutovalidateMode.disabled], auto-validation will be disabled.
556 ///
557 /// Defaults to [AutovalidateMode.disabled].
558 /// {@endtemplate}
559 final AutovalidateMode autovalidateMode;
560
561 /// Restoration ID to save and restore the state of the form field.
562 ///
563 /// Setting the restoration ID to a non-null value results in whether or not
564 /// the form field validation persists.
565 ///
566 /// The state of this widget is persisted in a [RestorationBucket] claimed
567 /// from the surrounding [RestorationScope] using the provided restoration ID.
568 ///
569 /// See also:
570 ///
571 /// * [RestorationManager], which explains how state restoration works in
572 /// Flutter.
573 final String? restorationId;
574
575 @override
576 FormFieldState<T> createState() => FormFieldState<T>();
577}
578
579/// The current state of a [FormField]. Passed to the [FormFieldBuilder] method
580/// for use in constructing the form field's widget.
581class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
582 late T? _value = widget.initialValue;
583 // Marking it as late, so it can be registered
584 // with the value provided by [forceErrorText].
585 late final RestorableStringN _errorText;
586 final RestorableBool _hasInteractedByUser = RestorableBool(false);
587 final FocusNode _focusNode = FocusNode();
588
589 /// The current value of the form field.
590 T? get value => _value;
591
592 /// The current validation error returned by the [FormField.validator]
593 /// callback, or the manually provided error message using the
594 /// [FormField.forceErrorText] property.
595 ///
596 /// This property is automatically updated when [validate] is called and the
597 /// [FormField.validator] callback is invoked, or If [FormField.forceErrorText] is set
598 /// directly to a non-null value.
599 String? get errorText => _errorText.value;
600
601 /// True if this field has any validation errors.
602 bool get hasError => _errorText.value != null;
603
604 /// Returns true if the user has modified the value of this field.
605 ///
606 /// This only updates to true once [didChange] has been called and resets to
607 /// false when [reset] is called.
608 bool get hasInteractedByUser => _hasInteractedByUser.value;
609
610 /// True if the current value is valid.
611 ///
612 /// This will not set [errorText] or [hasError] and it will not update
613 /// error display.
614 ///
615 /// See also:
616 ///
617 /// * [validate], which may update [errorText] and [hasError].
618 ///
619 /// * [FormField.forceErrorText], which also may update [errorText] and [hasError].
620 bool get isValid => widget.forceErrorText == null && widget.validator?.call(_value) == null;
621
622 /// Calls the [FormField]'s onSaved method with the current value.
623 void save() {
624 widget.onSaved?.call(value);
625 }
626
627 /// Resets the field to its initial value.
628 void reset() {
629 setState(() {
630 _value = widget.initialValue;
631 _hasInteractedByUser.value = false;
632 _errorText.value = null;
633 });
634 Form.maybeOf(context)?._fieldDidChange();
635 }
636
637 /// Calls [FormField.validator] to set the [errorText] only if [FormField.forceErrorText] is null.
638 /// When [FormField.forceErrorText] is not null, [FormField.validator] will not be called.
639 ///
640 /// Returns true if there were no errors.
641 /// See also:
642 ///
643 /// * [isValid], which passively gets the validity without setting
644 /// [errorText] or [hasError].
645 bool validate() {
646 setState(() {
647 _validate();
648 });
649 return !hasError;
650 }
651
652 void _validate() {
653 if (widget.forceErrorText != null) {
654 _errorText.value = widget.forceErrorText;
655 // Skip validating if error is forced.
656 return;
657 }
658 if (widget.validator != null) {
659 _errorText.value = widget.validator!(_value);
660 } else {
661 _errorText.value = null;
662 }
663 }
664
665 /// Updates this field's state to the new value. Useful for responding to
666 /// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
667 ///
668 /// Triggers the [Form.onChanged] callback and, if [Form.autovalidateMode] is
669 /// [AutovalidateMode.always] or [AutovalidateMode.onUserInteraction],
670 /// revalidates all the fields of the form.
671 void didChange(T? value) {
672 setState(() {
673 _value = value;
674 _hasInteractedByUser.value = true;
675 });
676 Form.maybeOf(context)?._fieldDidChange();
677 }
678
679 /// Sets the value associated with this form field.
680 ///
681 /// This method should only be called by subclasses that need to update
682 /// the form field value due to state changes identified during the widget
683 /// build phase, when calling `setState` is prohibited. In all other cases,
684 /// the value should be set by a call to [didChange], which ensures that
685 /// `setState` is called.
686 @protected
687 // ignore: use_setters_to_change_properties, (API predates enforcing the lint)
688 void setValue(T? value) {
689 _value = value;
690 }
691
692 @override
693 String? get restorationId => widget.restorationId;
694
695 @protected
696 @override
697 void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
698 registerForRestoration(_errorText, 'error_text');
699 registerForRestoration(_hasInteractedByUser, 'has_interacted_by_user');
700 }
701
702 @protected
703 @override
704 void deactivate() {
705 Form.maybeOf(context)?._unregister(this);
706 super.deactivate();
707 }
708
709 @protected
710 @override
711 void initState() {
712 super.initState();
713 _errorText = RestorableStringN(widget.forceErrorText);
714 }
715
716 @protected
717 @override
718 void didUpdateWidget(FormField<T> oldWidget) {
719 super.didUpdateWidget(oldWidget);
720 if (widget.forceErrorText != oldWidget.forceErrorText) {
721 _errorText.value = widget.forceErrorText;
722 }
723 }
724
725 @protected
726 @override
727 void didChangeDependencies() {
728 super.didChangeDependencies();
729 switch (Form.maybeOf(context)?.widget.autovalidateMode) {
730 case AutovalidateMode.always:
731 WidgetsBinding.instance.addPostFrameCallback((_) {
732 // If the form is already validated, don't validate again.
733 if (widget.enabled && !hasError && !isValid) {
734 validate();
735 }
736 });
737 case AutovalidateMode.onUnfocus:
738 case AutovalidateMode.onUserInteraction:
739 case AutovalidateMode.disabled:
740 case null:
741 break;
742 }
743 }
744
745 @override
746 void dispose() {
747 _errorText.dispose();
748 _focusNode.dispose();
749 _hasInteractedByUser.dispose();
750 super.dispose();
751 }
752
753 @protected
754 @override
755 Widget build(BuildContext context) {
756 if (widget.enabled) {
757 switch (widget.autovalidateMode) {
758 case AutovalidateMode.always:
759 _validate();
760 case AutovalidateMode.onUserInteraction:
761 if (_hasInteractedByUser.value) {
762 _validate();
763 }
764 case AutovalidateMode.onUnfocus:
765 case AutovalidateMode.disabled:
766 break;
767 }
768 }
769
770 Form.maybeOf(context)?._register(this);
771
772 final Widget child = Semantics(
773 validationResult:
774 hasError ? SemanticsValidationResult.invalid : SemanticsValidationResult.valid,
775 child: widget.builder(this),
776 );
777
778 if (Form.maybeOf(context)?.widget.autovalidateMode == AutovalidateMode.onUnfocus &&
779 widget.autovalidateMode != AutovalidateMode.always ||
780 widget.autovalidateMode == AutovalidateMode.onUnfocus) {
781 return Focus(
782 canRequestFocus: false,
783 skipTraversal: true,
784 onFocusChange: (bool value) {
785 if (!value) {
786 setState(() {
787 _validate();
788 });
789 }
790 },
791 focusNode: _focusNode,
792 child: child,
793 );
794 }
795
796 return child;
797 }
798}
799
800/// Used to configure the auto validation of [FormField] and [Form] widgets.
801enum AutovalidateMode {
802 /// No auto validation will occur.
803 disabled,
804
805 /// Used to auto-validate [Form] and [FormField] even without user interaction.
806 always,
807
808 /// Used to auto-validate [Form] and [FormField] only after each user
809 /// interaction.
810 onUserInteraction,
811
812 /// Used to auto-validate [Form] and [FormField] only after the field has
813 /// lost focus.
814 ///
815 /// In order to validate all fields of a [Form] after the first time the user interacts
816 /// with one, use [always] instead.
817 onUnfocus,
818}
819

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com