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 | import 'dart:async'; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | import 'package:flutter/rendering.dart'; |
9 | |
10 | import 'basic.dart'; |
11 | import 'framework.dart'; |
12 | import 'navigator.dart'; |
13 | import 'pop_scope.dart'; |
14 | import 'restoration.dart'; |
15 | import 'restoration_properties.dart'; |
16 | import 'routes.dart'; |
17 | import 'will_pop_scope.dart'; |
18 | |
19 | // Duration for delay before announcement in IOS so that the announcement won't be interrupted. |
20 | const 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]. |
50 | class 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]. |
204 | class 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 | |
323 | class _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]. |
350 | typedef FormFieldValidator<T> = String? Function(T? value); |
351 | |
352 | /// Signature for being notified when a form field changes value. |
353 | /// |
354 | /// Used by [FormField.onSaved]. |
355 | typedef FormFieldSetter<T> = void Function(T? newValue); |
356 | |
357 | /// Signature for building the widget representing the form field. |
358 | /// |
359 | /// Used by [FormField.builder]. |
360 | typedef 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. |
383 | class 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. |
463 | class 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. |
601 | enum 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 | |