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'; |
7 | library; |
8 | |
9 | import 'dart:async'; |
10 | |
11 | import 'package:flutter/foundation.dart'; |
12 | import 'package:flutter/rendering.dart'; |
13 | |
14 | import 'basic.dart'; |
15 | import 'binding.dart'; |
16 | import 'focus_manager.dart'; |
17 | import 'focus_scope.dart'; |
18 | import 'framework.dart'; |
19 | import 'navigator.dart'; |
20 | import 'pop_scope.dart'; |
21 | import 'restoration.dart'; |
22 | import 'restoration_properties.dart'; |
23 | import 'routes.dart'; |
24 | import 'will_pop_scope.dart'; |
25 | |
26 | // Duration for delay before announcement in IOS so that the announcement won't be interrupted. |
27 | const 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 | ///  |
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]. |
57 | class 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]. |
238 | class 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 | |
398 | class _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]. |
422 | typedef 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]. |
430 | typedef 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]. |
435 | typedef FormFieldSetter<T> = void Function(T? newValue); |
436 | |
437 | /// Signature for building the widget representing the form field. |
438 | /// |
439 | /// Used by [FormField.builder]. |
440 | typedef 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. |
463 | class 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. |
581 | class 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. |
801 | enum 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 |
Definitions
- _kIOSAnnouncementDelayDuration
- Form
- Form
- maybeOf
- of
- _callPopInvoked
- createState
- FormState
- _fieldDidChange
- _forceRebuild
- _register
- _unregister
- build
- save
- reset
- validate
- validateGranularly
- _validate
- _FormScope
- _FormScope
- form
- updateShouldNotify
- FormField
- FormField
- createState
- FormFieldState
- value
- errorText
- hasError
- hasInteractedByUser
- isValid
- save
- reset
- validate
- _validate
- didChange
- setValue
- restorationId
- restoreState
- deactivate
- initState
- didUpdateWidget
- didChangeDependencies
- dispose
- build
Learn more about Flutter for embedded and desktop on industrialflutter.com