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