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
5import 'dart:async';
6
7import 'package:flutter/foundation.dart';
8import 'package:flutter/rendering.dart';
9
10import 'basic.dart';
11import 'framework.dart';
12import 'navigator.dart';
13import 'pop_scope.dart';
14import 'restoration.dart';
15import 'restoration_properties.dart';
16import 'routes.dart';
17import 'will_pop_scope.dart';
18
19// Duration for delay before announcement in IOS so that the announcement won't be interrupted.
20const Duration _kIOSAnnouncementDelayDuration = Duration(seconds: 1);
21
22// Examples can assume:
23// late BuildContext context;
24
25/// An optional container for grouping together multiple form field widgets
26/// (e.g. [TextField] widgets).
27///
28/// Each individual form field should be wrapped in a [FormField] widget, with
29/// the [Form] widget as a common ancestor of all of those. Call methods on
30/// [FormState] to save, reset, or validate each [FormField] that is a
31/// descendant of this [Form]. To obtain the [FormState], you may use [Form.of]
32/// with a context whose ancestor is the [Form], or pass a [GlobalKey] to the
33/// [Form] constructor and call [GlobalKey.currentState].
34///
35/// {@tool dartpad}
36/// This example shows a [Form] with one [TextFormField] to enter an email
37/// address and an [ElevatedButton] to submit the form. A [GlobalKey] is used here
38/// to identify the [Form] and validate input.
39///
40/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/form.png)
41///
42/// ** See code in examples/api/lib/widgets/form/form.0.dart **
43/// {@end-tool}
44///
45/// See also:
46///
47/// * [GlobalKey], a key that is unique across the entire app.
48/// * [FormField], a single form field widget that maintains the current state.
49/// * [TextFormField], a convenience widget that wraps a [TextField] widget in a [FormField].
50class Form extends StatefulWidget {
51 /// Creates a container for form fields.
52 const Form({
53 super.key,
54 required this.child,
55 this.canPop,
56 this.onPopInvoked,
57 @Deprecated(
58 'Use canPop and/or onPopInvoked instead. '
59 'This feature was deprecated after v3.12.0-1.0.pre.',
60 )
61 this.onWillPop,
62 this.onChanged,
63 AutovalidateMode? autovalidateMode,
64 }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled,
65 assert((onPopInvoked == null && canPop == null) || onWillPop == null, 'onWillPop is deprecated; use canPop and/or onPopInvoked.');
66
67 /// Returns the [FormState] of the closest [Form] widget which encloses the
68 /// given context, or null if none is found.
69 ///
70 /// Typical usage is as follows:
71 ///
72 /// ```dart
73 /// FormState? form = Form.maybeOf(context);
74 /// form?.save();
75 /// ```
76 ///
77 /// Calling this method will create a dependency on the closest [Form] in the
78 /// [context], if there is one.
79 ///
80 /// See also:
81 ///
82 /// * [Form.of], which is similar to this method, but asserts if no [Form]
83 /// ancestor is found.
84 static FormState? maybeOf(BuildContext context) {
85 final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
86 return scope?._formState;
87 }
88
89 /// Returns the [FormState] of the closest [Form] widget which encloses the
90 /// given context.
91 ///
92 /// Typical usage is as follows:
93 ///
94 /// ```dart
95 /// FormState form = Form.of(context);
96 /// form.save();
97 /// ```
98 ///
99 /// If no [Form] ancestor is found, this will assert in debug mode, and throw
100 /// an exception in release mode.
101 ///
102 /// Calling this method will create a dependency on the closest [Form] in the
103 /// [context].
104 ///
105 /// See also:
106 ///
107 /// * [Form.maybeOf], which is similar to this method, but returns null if no
108 /// [Form] ancestor is found.
109 static FormState of(BuildContext context) {
110 final FormState? formState = maybeOf(context);
111 assert(() {
112 if (formState == null) {
113 throw FlutterError(
114 'Form.of() was called with a context that does not contain a Form widget.\n'
115 'No Form widget ancestor could be found starting from the context that '
116 'was passed to Form.of(). This can happen because you are using a widget '
117 'that looks for a Form ancestor, but no such ancestor exists.\n'
118 'The context used was:\n'
119 ' $context',
120 );
121 }
122 return true;
123 }());
124 return formState!;
125 }
126
127 /// The widget below this widget in the tree.
128 ///
129 /// This is the root of the widget hierarchy that contains this form.
130 ///
131 /// {@macro flutter.widgets.ProxyWidget.child}
132 final Widget child;
133
134 /// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
135 /// that contains the form.
136 ///
137 /// If the callback returns a Future that resolves to false, the form's route
138 /// will not be popped.
139 ///
140 /// See also:
141 ///
142 /// * [WillPopScope], another widget that provides a way to intercept the
143 /// back button.
144 @Deprecated(
145 'Use canPop and/or onPopInvoked instead. '
146 'This feature was deprecated after v3.12.0-1.0.pre.',
147 )
148 final WillPopCallback? onWillPop;
149
150 /// {@macro flutter.widgets.PopScope.canPop}
151 ///
152 /// {@tool dartpad}
153 /// This sample demonstrates how to use this parameter to show a confirmation
154 /// dialog when a navigation pop would cause form data to be lost.
155 ///
156 /// ** See code in examples/api/lib/widgets/form/form.1.dart **
157 /// {@end-tool}
158 ///
159 /// See also:
160 ///
161 /// * [onPopInvoked], which also comes from [PopScope] and is often used in
162 /// conjunction with this parameter.
163 /// * [PopScope.canPop], which is what [Form] delegates to internally.
164 final bool? canPop;
165
166 /// {@macro flutter.widgets.navigator.onPopInvoked}
167 ///
168 /// {@tool dartpad}
169 /// This sample demonstrates how to use this parameter to show a confirmation
170 /// dialog when a navigation pop would cause form data to be lost.
171 ///
172 /// ** See code in examples/api/lib/widgets/form/form.1.dart **
173 /// {@end-tool}
174 ///
175 /// See also:
176 ///
177 /// * [canPop], which also comes from [PopScope] and is often used in
178 /// conjunction with this parameter.
179 /// * [PopScope.onPopInvoked], which is what [Form] delegates to internally.
180 final PopInvokedCallback? onPopInvoked;
181
182 /// Called when one of the form fields changes.
183 ///
184 /// In addition to this callback being invoked, all the form fields themselves
185 /// will rebuild.
186 final VoidCallback? onChanged;
187
188 /// Used to enable/disable form fields auto validation and update their error
189 /// text.
190 ///
191 /// {@macro flutter.widgets.FormField.autovalidateMode}
192 final AutovalidateMode autovalidateMode;
193
194 @override
195 FormState createState() => FormState();
196}
197
198/// State associated with a [Form] widget.
199///
200/// A [FormState] object can be used to [save], [reset], and [validate] every
201/// [FormField] that is a descendant of the associated [Form].
202///
203/// Typically obtained via [Form.of].
204class FormState extends State<Form> {
205 int _generation = 0;
206 bool _hasInteractedByUser = false;
207 final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};
208
209 // Called when a form field has changed. This will cause all form fields
210 // to rebuild, useful if form fields have interdependencies.
211 void _fieldDidChange() {
212 widget.onChanged?.call();
213
214 _hasInteractedByUser = _fields
215 .any((FormFieldState<dynamic> field) => field._hasInteractedByUser.value);
216 _forceRebuild();
217 }
218
219 void _forceRebuild() {
220 setState(() {
221 ++_generation;
222 });
223 }
224
225 void _register(FormFieldState<dynamic> field) {
226 _fields.add(field);
227 }
228
229 void _unregister(FormFieldState<dynamic> field) {
230 _fields.remove(field);
231 }
232
233 @override
234 Widget build(BuildContext context) {
235 switch (widget.autovalidateMode) {
236 case AutovalidateMode.always:
237 _validate();
238 case AutovalidateMode.onUserInteraction:
239 if (_hasInteractedByUser) {
240 _validate();
241 }
242 case AutovalidateMode.disabled:
243 break;
244 }
245
246 if (widget.canPop != null || widget.onPopInvoked != null) {
247 return PopScope(
248 canPop: widget.canPop ?? true,
249 onPopInvoked: widget.onPopInvoked,
250 child: _FormScope(
251 formState: this,
252 generation: _generation,
253 child: widget.child,
254 ),
255 );
256 }
257
258 return WillPopScope(
259 onWillPop: widget.onWillPop,
260 child: _FormScope(
261 formState: this,
262 generation: _generation,
263 child: widget.child,
264 ),
265 );
266 }
267
268 /// Saves every [FormField] that is a descendant of this [Form].
269 void save() {
270 for (final FormFieldState<dynamic> field in _fields) {
271 field.save();
272 }
273 }
274
275 /// Resets every [FormField] that is a descendant of this [Form] back to its
276 /// [FormField.initialValue].
277 ///
278 /// The [Form.onChanged] callback will be called.
279 ///
280 /// If the form's [Form.autovalidateMode] property is [AutovalidateMode.always],
281 /// the fields will all be revalidated after being reset.
282 void reset() {
283 for (final FormFieldState<dynamic> field in _fields) {
284 field.reset();
285 }
286 _hasInteractedByUser = false;
287 _fieldDidChange();
288 }
289
290 /// Validates every [FormField] that is a descendant of this [Form], and
291 /// returns true if there are no errors.
292 ///
293 /// The form will rebuild to report the results.
294 bool validate() {
295 _hasInteractedByUser = true;
296 _forceRebuild();
297 return _validate();
298 }
299
300 bool _validate() {
301 bool hasError = false;
302 String errorMessage = '';
303 for (final FormFieldState<dynamic> field in _fields) {
304 hasError = !field.validate() || hasError;
305 errorMessage += field.errorText ?? '';
306 }
307
308 if (errorMessage.isNotEmpty) {
309 final TextDirection directionality = Directionality.of(context);
310 if (defaultTargetPlatform == TargetPlatform.iOS) {
311 unawaited(Future<void>(() async {
312 await Future<void>.delayed(_kIOSAnnouncementDelayDuration);
313 SemanticsService.announce(errorMessage, directionality, assertiveness: Assertiveness.assertive);
314 }));
315 } else {
316 SemanticsService.announce(errorMessage, directionality, assertiveness: Assertiveness.assertive);
317 }
318 }
319 return !hasError;
320 }
321}
322
323class _FormScope extends InheritedWidget {
324 const _FormScope({
325 required super.child,
326 required FormState formState,
327 required int generation,
328 }) : _formState = formState,
329 _generation = generation;
330
331 final FormState _formState;
332
333 /// Incremented every time a form field has changed. This lets us know when
334 /// to rebuild the form.
335 final int _generation;
336
337 /// The [Form] associated with this widget.
338 Form get form => _formState.widget;
339
340 @override
341 bool updateShouldNotify(_FormScope old) => _generation != old._generation;
342}
343
344/// Signature for validating a form field.
345///
346/// Returns an error string to display if the input is invalid, or null
347/// otherwise.
348///
349/// Used by [FormField.validator].
350typedef FormFieldValidator<T> = String? Function(T? value);
351
352/// Signature for being notified when a form field changes value.
353///
354/// Used by [FormField.onSaved].
355typedef FormFieldSetter<T> = void Function(T? newValue);
356
357/// Signature for building the widget representing the form field.
358///
359/// Used by [FormField.builder].
360typedef FormFieldBuilder<T> = Widget Function(FormFieldState<T> field);
361
362/// A single form field.
363///
364/// This widget maintains the current state of the form field, so that updates
365/// and validation errors are visually reflected in the UI.
366///
367/// When used inside a [Form], you can use methods on [FormState] to query or
368/// manipulate the form data as a whole. For example, calling [FormState.save]
369/// will invoke each [FormField]'s [onSaved] callback in turn.
370///
371/// Use a [GlobalKey] with [FormField] if you want to retrieve its current
372/// state, for example if you want one form field to depend on another.
373///
374/// A [Form] ancestor is not required. The [Form] allows one to
375/// save, reset, or validate multiple fields at once. To use without a [Form],
376/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
377/// save or reset the form field.
378///
379/// See also:
380///
381/// * [Form], which is the widget that aggregates the form fields.
382/// * [TextField], which is a commonly used form field for entering text.
383class FormField<T> extends StatefulWidget {
384 /// Creates a single form field.
385 const FormField({
386 super.key,
387 required this.builder,
388 this.onSaved,
389 this.validator,
390 this.initialValue,
391 this.enabled = true,
392 AutovalidateMode? autovalidateMode,
393 this.restorationId,
394 }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
395
396 /// An optional method to call with the final value when the form is saved via
397 /// [FormState.save].
398 final FormFieldSetter<T>? onSaved;
399
400 /// An optional method that validates an input. Returns an error string to
401 /// display if the input is invalid, or null otherwise.
402 ///
403 /// The returned value is exposed by the [FormFieldState.errorText] property.
404 /// The [TextFormField] uses this to override the [InputDecoration.errorText]
405 /// value.
406 ///
407 /// Alternating between error and normal state can cause the height of the
408 /// [TextFormField] to change if no other subtext decoration is set on the
409 /// field. To create a field whose height is fixed regardless of whether or
410 /// not an error is displayed, either wrap the [TextFormField] in a fixed
411 /// height parent like [SizedBox], or set the [InputDecoration.helperText]
412 /// parameter to a space.
413 final FormFieldValidator<T>? validator;
414
415 /// Function that returns the widget representing this form field. It is
416 /// passed the form field state as input, containing the current value and
417 /// validation state of this field.
418 final FormFieldBuilder<T> builder;
419
420 /// An optional value to initialize the form field to, or null otherwise.
421 final T? initialValue;
422
423 /// Whether the form is able to receive user input.
424 ///
425 /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled],
426 /// the field will be auto validated. Likewise, if this field is false, the widget
427 /// will not be validated regardless of [autovalidateMode].
428 final bool enabled;
429
430 /// Used to enable/disable this form field auto validation and update its
431 /// error text.
432 ///
433 /// {@template flutter.widgets.FormField.autovalidateMode}
434 /// If [AutovalidateMode.onUserInteraction], this FormField will only
435 /// auto-validate after its content changes. If [AutovalidateMode.always], it
436 /// will auto-validate even without user interaction. If
437 /// [AutovalidateMode.disabled], auto-validation will be disabled.
438 ///
439 /// Defaults to [AutovalidateMode.disabled].
440 /// {@endtemplate}
441 final AutovalidateMode autovalidateMode;
442
443 /// Restoration ID to save and restore the state of the form field.
444 ///
445 /// Setting the restoration ID to a non-null value results in whether or not
446 /// the form field validation persists.
447 ///
448 /// The state of this widget is persisted in a [RestorationBucket] claimed
449 /// from the surrounding [RestorationScope] using the provided restoration ID.
450 ///
451 /// See also:
452 ///
453 /// * [RestorationManager], which explains how state restoration works in
454 /// Flutter.
455 final String? restorationId;
456
457 @override
458 FormFieldState<T> createState() => FormFieldState<T>();
459}
460
461/// The current state of a [FormField]. Passed to the [FormFieldBuilder] method
462/// for use in constructing the form field's widget.
463class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
464 late T? _value = widget.initialValue;
465 final RestorableStringN _errorText = RestorableStringN(null);
466 final RestorableBool _hasInteractedByUser = RestorableBool(false);
467
468 /// The current value of the form field.
469 T? get value => _value;
470
471 /// The current validation error returned by the [FormField.validator]
472 /// callback, or null if no errors have been triggered. This only updates when
473 /// [validate] is called.
474 String? get errorText => _errorText.value;
475
476 /// True if this field has any validation errors.
477 bool get hasError => _errorText.value != null;
478
479 /// Returns true if the user has modified the value of this field.
480 ///
481 /// This only updates to true once [didChange] has been called and resets to
482 /// false when [reset] is called.
483 bool get hasInteractedByUser => _hasInteractedByUser.value;
484
485 /// True if the current value is valid.
486 ///
487 /// This will not set [errorText] or [hasError] and it will not update
488 /// error display.
489 ///
490 /// See also:
491 ///
492 /// * [validate], which may update [errorText] and [hasError].
493 bool get isValid => widget.validator?.call(_value) == null;
494
495 /// Calls the [FormField]'s onSaved method with the current value.
496 void save() {
497 widget.onSaved?.call(value);
498 }
499
500 /// Resets the field to its initial value.
501 void reset() {
502 setState(() {
503 _value = widget.initialValue;
504 _hasInteractedByUser.value = false;
505 _errorText.value = null;
506 });
507 Form.maybeOf(context)?._fieldDidChange();
508 }
509
510 /// Calls [FormField.validator] to set the [errorText]. Returns true if there
511 /// were no errors.
512 ///
513 /// See also:
514 ///
515 /// * [isValid], which passively gets the validity without setting
516 /// [errorText] or [hasError].
517 bool validate() {
518 setState(() {
519 _validate();
520 });
521 return !hasError;
522 }
523
524 void _validate() {
525 if (widget.validator != null) {
526 _errorText.value = widget.validator!(_value);
527 } else {
528 _errorText.value = null;
529 }
530 }
531
532 /// Updates this field's state to the new value. Useful for responding to
533 /// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
534 ///
535 /// Triggers the [Form.onChanged] callback and, if [Form.autovalidateMode] is
536 /// [AutovalidateMode.always] or [AutovalidateMode.onUserInteraction],
537 /// revalidates all the fields of the form.
538 void didChange(T? value) {
539 setState(() {
540 _value = value;
541 _hasInteractedByUser.value = true;
542 });
543 Form.maybeOf(context)?._fieldDidChange();
544 }
545
546 /// Sets the value associated with this form field.
547 ///
548 /// This method should only be called by subclasses that need to update
549 /// the form field value due to state changes identified during the widget
550 /// build phase, when calling `setState` is prohibited. In all other cases,
551 /// the value should be set by a call to [didChange], which ensures that
552 /// `setState` is called.
553 @protected
554 // ignore: use_setters_to_change_properties, (API predates enforcing the lint)
555 void setValue(T? value) {
556 _value = value;
557 }
558
559 @override
560 String? get restorationId => widget.restorationId;
561
562 @override
563 void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
564 registerForRestoration(_errorText, 'error_text');
565 registerForRestoration(_hasInteractedByUser, 'has_interacted_by_user');
566 }
567
568 @override
569 void deactivate() {
570 Form.maybeOf(context)?._unregister(this);
571 super.deactivate();
572 }
573
574 @override
575 void dispose() {
576 _errorText.dispose();
577 _hasInteractedByUser.dispose();
578 super.dispose();
579 }
580
581 @override
582 Widget build(BuildContext context) {
583 if (widget.enabled) {
584 switch (widget.autovalidateMode) {
585 case AutovalidateMode.always:
586 _validate();
587 case AutovalidateMode.onUserInteraction:
588 if (_hasInteractedByUser.value) {
589 _validate();
590 }
591 case AutovalidateMode.disabled:
592 break;
593 }
594 }
595 Form.maybeOf(context)?._register(this);
596 return widget.builder(this);
597 }
598}
599
600/// Used to configure the auto validation of [FormField] and [Form] widgets.
601enum AutovalidateMode {
602 /// No auto validation will occur.
603 disabled,
604
605 /// Used to auto-validate [Form] and [FormField] even without user interaction.
606 always,
607
608 /// Used to auto-validate [Form] and [FormField] only after each user
609 /// interaction.
610 onUserInteraction,
611}
612