form.dart 17.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'framework.dart';
6
import 'navigator.dart';
7 8
import 'restoration.dart';
import 'restoration_properties.dart';
9
import 'will_pop_scope.dart';
10

11
/// An optional container for grouping together multiple form field widgets
12
/// (e.g. [TextField] widgets).
13 14 15 16 17 18 19
///
/// Each individual form field should be wrapped in a [FormField] widget, with
/// the [Form] widget as a common ancestor of all of those. Call methods on
/// [FormState] to save, reset, or validate each [FormField] that is a
/// descendant of this [Form]. To obtain the [FormState], you may use [Form.of]
/// with a context whose ancestor is the [Form], or pass a [GlobalKey] to the
/// [Form] constructor and call [GlobalKey.currentState].
20
///
21
/// {@tool dartpad --template=stateful_widget_scaffold}
22
/// This example shows a [Form] with one [TextFormField] to enter an email
23
/// address and an [ElevatedButton] to submit the form. A [GlobalKey] is used here
24 25 26
/// to identify the [Form] and validate input.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/form.png)
27 28
///
/// ```dart
29
/// final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
30 31 32 33 34 35 36 37 38
///
/// @override
/// Widget build(BuildContext context) {
///   return Form(
///     key: _formKey,
///     child: Column(
///       crossAxisAlignment: CrossAxisAlignment.start,
///       children: <Widget>[
///         TextFormField(
39 40 41
///           decoration: const InputDecoration(
///             hintText: 'Enter your email',
///           ),
42 43
///           validator: (String? value) {
///             if (value == null || value.isEmpty) {
44 45
///               return 'Please enter some text';
///             }
46
///             return null;
47 48 49 50
///           },
///         ),
///         Padding(
///           padding: const EdgeInsets.symmetric(vertical: 16.0),
51
///           child: ElevatedButton(
52 53 54
///             onPressed: () {
///               // Validate will return true if the form is valid, or false if
///               // the form is invalid.
55
///               if (_formKey.currentState!.validate()) {
56 57 58
///                 // Process data.
///               }
///             },
59
///             child: const Text('Submit'),
60 61 62 63 64 65 66 67 68 69 70 71 72 73
///           ),
///         ),
///       ],
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
///  * [GlobalKey], a key that is unique across the entire app.
///  * [FormField], a single form field widget that maintains the current state.
///  * [TextFormField], a convenience widget that wraps a [TextField] widget in a [FormField].
74
class Form extends StatefulWidget {
75 76 77
  /// Creates a container for form fields.
  ///
  /// The [child] argument must not be null.
78
  const Form({
79 80
    Key? key,
    required this.child,
81
    @Deprecated(
82
      'Use autovalidateMode parameter which provides more specific '
83
      'behavior related to auto validation. '
84
      'This feature was deprecated after v1.19.0.',
85
    )
86
    this.autovalidate = false,
87
    this.onWillPop,
88
    this.onChanged,
89
    AutovalidateMode? autovalidateMode,
90
  }) : assert(child != null),
91 92 93 94
       assert(autovalidate != null),
       assert(
         autovalidate == false ||
         autovalidate == true && autovalidateMode == null,
95
         'autovalidate and autovalidateMode should not be used together.',
96
       ),
97 98
       autovalidateMode = autovalidateMode ??
         (autovalidate ? AutovalidateMode.always : AutovalidateMode.disabled),
99
       super(key: key);
100

Matt Perry's avatar
Matt Perry committed
101 102 103 104 105 106 107 108
  /// Returns the closest [FormState] which encloses the given context.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// FormState form = Form.of(context);
  /// form.save();
  /// ```
109 110
  static FormState? of(BuildContext context) {
    final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
Matt Perry's avatar
Matt Perry committed
111 112
    return scope?._formState;
  }
113

114 115 116 117
  /// The widget below this widget in the tree.
  ///
  /// This is the root of the widget hierarchy that contains this form.
  ///
118
  /// {@macro flutter.widgets.ProxyWidget.child}
119 120
  final Widget child;

121 122 123 124 125
  /// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
  /// that contains the form.
  ///
  /// If the callback returns a Future that resolves to false, the form's route
  /// will not be popped.
126 127 128 129 130
  ///
  /// See also:
  ///
  ///  * [WillPopScope], another widget that provides a way to intercept the
  ///    back button.
131
  final WillPopCallback? onWillPop;
132

133 134 135 136
  /// Called when one of the form fields changes.
  ///
  /// In addition to this callback being invoked, all the form fields themselves
  /// will rebuild.
137
  final VoidCallback? onChanged;
138

139 140 141
  /// Used to enable/disable form fields auto validation and update their error
  /// text.
  ///
142
  /// {@macro flutter.widgets.FormField.autovalidateMode}
143 144
  final AutovalidateMode autovalidateMode;

145 146 147
  /// Used to enable/disable form fields auto validation and update their error
  /// text.
  @Deprecated(
148
    'Use autovalidateMode parameter which provides more specific '
149
    'behavior related to auto validation. '
150
    'This feature was deprecated after v1.19.0.',
151 152 153
  )
  final bool autovalidate;

154
  @override
155
  FormState createState() => FormState();
156 157
}

158
/// State associated with a [Form] widget.
159
///
160
/// A [FormState] object can be used to [save], [reset], and [validate] every
161 162 163
/// [FormField] that is a descendant of the associated [Form].
///
/// Typically obtained via [Form.of].
Matt Perry's avatar
Matt Perry committed
164 165
class FormState extends State<Form> {
  int _generation = 0;
166
  bool _hasInteractedByUser = false;
167
  final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};
168

169 170
  // Called when a form field has changed. This will cause all form fields
  // to rebuild, useful if form fields have interdependencies.
Matt Perry's avatar
Matt Perry committed
171
  void _fieldDidChange() {
172
    widget.onChanged?.call();
173 174

    _hasInteractedByUser = _fields
175
        .any((FormFieldState<dynamic> field) => field._hasInteractedByUser.value);
176 177 178 179
    _forceRebuild();
  }

  void _forceRebuild() {
180
    setState(() {
Matt Perry's avatar
Matt Perry committed
181
      ++_generation;
182 183 184
    });
  }

Matt Perry's avatar
Matt Perry committed
185 186 187 188 189 190 191 192
  void _register(FormFieldState<dynamic> field) {
    _fields.add(field);
  }

  void _unregister(FormFieldState<dynamic> field) {
    _fields.remove(field);
  }

193 194
  @override
  Widget build(BuildContext context) {
195 196 197 198 199 200 201 202 203 204 205 206 207
    switch (widget.autovalidateMode) {
      case AutovalidateMode.always:
        _validate();
        break;
      case AutovalidateMode.onUserInteraction:
        if (_hasInteractedByUser) {
          _validate();
        }
        break;
      case AutovalidateMode.disabled:
        break;
    }

208
    return WillPopScope(
209
      onWillPop: widget.onWillPop,
210
      child: _FormScope(
211 212
        formState: this,
        generation: _generation,
213
        child: widget.child,
214
      ),
215 216 217
    );
  }

218
  /// Saves every [FormField] that is a descendant of this [Form].
Matt Perry's avatar
Matt Perry committed
219
  void save() {
220
    for (final FormFieldState<dynamic> field in _fields)
Matt Perry's avatar
Matt Perry committed
221 222
      field.save();
  }
223

224
  /// Resets every [FormField] that is a descendant of this [Form] back to its
225
  /// [FormField.initialValue].
226 227 228
  ///
  /// The [Form.onChanged] callback will be called.
  ///
229 230
  /// If the form's [Form.autovalidateMode] property is [AutovalidateMode.always],
  /// the fields will all be revalidated after being reset.
Matt Perry's avatar
Matt Perry committed
231
  void reset() {
232
    for (final FormFieldState<dynamic> field in _fields)
Matt Perry's avatar
Matt Perry committed
233
      field.reset();
234
    _hasInteractedByUser = false;
Matt Perry's avatar
Matt Perry committed
235 236
    _fieldDidChange();
  }
237

238
  /// Validates every [FormField] that is a descendant of this [Form], and
239
  /// returns true if there are no errors.
240 241
  ///
  /// The form will rebuild to report the results.
242
  bool validate() {
243
    _hasInteractedByUser = true;
244
    _forceRebuild();
245 246 247 248 249
    return _validate();
  }

  bool _validate() {
    bool hasError = false;
250
    for (final FormFieldState<dynamic> field in _fields)
251 252
      hasError = !field.validate() || hasError;
    return !hasError;
Matt Perry's avatar
Matt Perry committed
253
  }
254 255
}

Matt Perry's avatar
Matt Perry committed
256
class _FormScope extends InheritedWidget {
257
  const _FormScope({
258 259 260 261
    Key? key,
    required Widget child,
    required FormState formState,
    required int generation,
262
  }) : _formState = formState,
263 264 265
       _generation = generation,
       super(key: key, child: child);

Matt Perry's avatar
Matt Perry committed
266
  final FormState _formState;
267 268 269 270 271

  /// Incremented every time a form field has changed. This lets us know when
  /// to rebuild the form.
  final int _generation;

272
  /// The [Form] associated with this widget.
273
  Form get form => _formState.widget;
274

Matt Perry's avatar
Matt Perry committed
275 276 277 278 279
  @override
  bool updateShouldNotify(_FormScope old) => _generation != old._generation;
}

/// Signature for validating a form field.
280
///
281 282 283
/// Returns an error string to display if the input is invalid, or null
/// otherwise.
///
284
/// Used by [FormField.validator].
285
typedef FormFieldValidator<T> = String? Function(T? value);
Matt Perry's avatar
Matt Perry committed
286 287

/// Signature for being notified when a form field changes value.
288 289
///
/// Used by [FormField.onSaved].
290
typedef FormFieldSetter<T> = void Function(T? newValue);
Matt Perry's avatar
Matt Perry committed
291 292

/// Signature for building the widget representing the form field.
293 294
///
/// Used by [FormField.builder].
295
typedef FormFieldBuilder<T> = Widget Function(FormFieldState<T> field);
Matt Perry's avatar
Matt Perry committed
296

297 298 299 300
/// A single form field.
///
/// This widget maintains the current state of the form field, so that updates
/// and validation errors are visually reflected in the UI.
Matt Perry's avatar
Matt Perry committed
301 302 303 304 305 306 307 308
///
/// When used inside a [Form], you can use methods on [FormState] to query or
/// manipulate the form data as a whole. For example, calling [FormState.save]
/// will invoke each [FormField]'s [onSaved] callback in turn.
///
/// Use a [GlobalKey] with [FormField] if you want to retrieve its current
/// state, for example if you want one form field to depend on another.
///
309 310 311 312 313
/// A [Form] ancestor is not required. The [Form] simply makes it easier to
/// save, reset, or validate multiple fields at once. To use without a [Form],
/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
/// save or reset the form field.
///
314 315 316 317
/// See also:
///
///  * [Form], which is the widget that aggregates the form fields.
///  * [TextField], which is a commonly used form field for entering text.
Matt Perry's avatar
Matt Perry committed
318
class FormField<T> extends StatefulWidget {
319 320 321
  /// Creates a single form field.
  ///
  /// The [builder] argument must not be null.
322
  const FormField({
323 324
    Key? key,
    required this.builder,
Matt Perry's avatar
Matt Perry committed
325 326 327
    this.onSaved,
    this.validator,
    this.initialValue,
328
    @Deprecated(
329
      'Use autovalidateMode parameter which provides more specific '
330
      'behavior related to auto validation. '
331
      'This feature was deprecated after v1.19.0.',
332
    )
333
    this.autovalidate = false,
334
    this.enabled = true,
335
    AutovalidateMode? autovalidateMode,
336
    this.restorationId,
337
  }) : assert(builder != null),
338 339 340
       assert(
         autovalidate == false ||
         autovalidate == true && autovalidateMode == null,
341
         'autovalidate and autovalidateMode should not be used together.',
342
       ),
343 344
       autovalidateMode = autovalidateMode ??
         (autovalidate ? AutovalidateMode.always : AutovalidateMode.disabled),
345
       super(key: key);
346

Matt Perry's avatar
Matt Perry committed
347
  /// An optional method to call with the final value when the form is saved via
348
  /// [FormState.save].
349
  final FormFieldSetter<T>? onSaved;
Matt Perry's avatar
Matt Perry committed
350 351 352

  /// An optional method that validates an input. Returns an error string to
  /// display if the input is invalid, or null otherwise.
353 354 355 356
  ///
  /// The returned value is exposed by the [FormFieldState.errorText] property.
  /// The [TextFormField] uses this to override the [InputDecoration.errorText]
  /// value.
357 358 359 360 361
  ///
  /// Alternating between error and normal state can cause the height of the
  /// [TextFormField] to change if no other subtext decoration is set on the
  /// field. To create a field whose height is fixed regardless of whether or
  /// not an error is displayed, either wrap the  [TextFormField] in a fixed
362
  /// height parent like [SizedBox], or set the [InputDecoration.helperText]
363
  /// parameter to a space.
364
  final FormFieldValidator<T>? validator;
Matt Perry's avatar
Matt Perry committed
365 366 367 368 369 370 371

  /// Function that returns the widget representing this form field. It is
  /// passed the form field state as input, containing the current value and
  /// validation state of this field.
  final FormFieldBuilder<T> builder;

  /// An optional value to initialize the form field to, or null otherwise.
372
  final T? initialValue;
373

374 375
  /// Whether the form is able to receive user input.
  ///
376 377 378
  /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled],
  /// the field will be auto validated. Likewise, if this field is false, the widget
  /// will not be validated regardless of [autovalidateMode].
379 380
  final bool enabled;

381 382 383
  /// Used to enable/disable this form field auto validation and update its
  /// error text.
  ///
384
  /// {@template flutter.widgets.FormField.autovalidateMode}
385 386 387 388 389
  /// If [AutovalidateMode.onUserInteraction] this form field will only
  /// auto-validate after its content changes, if [AutovalidateMode.always] it
  /// will auto validate even without user interaction and
  /// if [AutovalidateMode.disabled] the auto validation will be disabled.
  ///
390 391
  /// Defaults to [AutovalidateMode.disabled] if `autovalidate` is false which
  /// means no auto validation will occur. If `autovalidate` is true then this
392 393 394 395
  /// is set to [AutovalidateMode.always] for backward compatibility.
  /// {@endtemplate}
  final AutovalidateMode autovalidateMode;

396 397 398
  /// Used to enable/disable auto validation and update their error
  /// text.
  @Deprecated(
399
    'Use autovalidateMode parameter which provides more specific '
400
    'behavior related to auto validation. '
401
    'This feature was deprecated after v1.19.0.',
402 403 404
  )
  final bool autovalidate;

405 406 407 408 409 410 411 412 413 414 415 416 417 418
  /// Restoration ID to save and restore the state of the form field.
  ///
  /// Setting the restoration ID to a non-null value results in whether or not
  /// the form field validation persists.
  ///
  /// The state of this widget is persisted in a [RestorationBucket] claimed
  /// from the surrounding [RestorationScope] using the provided restoration ID.
  ///
  /// See also:
  ///
  ///  * [RestorationManager], which explains how state restoration works in
  ///    Flutter.
  final String? restorationId;

419
  @override
420
  FormFieldState<T> createState() => FormFieldState<T>();
Matt Perry's avatar
Matt Perry committed
421 422
}

423 424
/// The current state of a [FormField]. Passed to the [FormFieldBuilder] method
/// for use in constructing the form field's widget.
425 426 427 428
class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
  late T? _value = widget.initialValue;
  final RestorableStringN _errorText = RestorableStringN(null);
  final RestorableBool _hasInteractedByUser = RestorableBool(false);
Matt Perry's avatar
Matt Perry committed
429 430

  /// The current value of the form field.
431
  T? get value => _value;
Matt Perry's avatar
Matt Perry committed
432

433 434 435
  /// The current validation error returned by the [FormField.validator]
  /// callback, or null if no errors have been triggered. This only updates when
  /// [validate] is called.
436
  String? get errorText => _errorText.value;
Matt Perry's avatar
Matt Perry committed
437 438

  /// True if this field has any validation errors.
439
  bool get hasError => _errorText.value != null;
Matt Perry's avatar
Matt Perry committed
440

441 442 443 444 445 446 447 448 449 450
  /// True if the current value is valid.
  ///
  /// This will not set [errorText] or [hasError] and it will not update
  /// error display.
  ///
  /// See also:
  ///
  ///  * [validate], which may update [errorText] and [hasError].
  bool get isValid => widget.validator?.call(_value) == null;

Matt Perry's avatar
Matt Perry committed
451 452
  /// Calls the [FormField]'s onSaved method with the current value.
  void save() {
453
    widget.onSaved?.call(value);
Matt Perry's avatar
Matt Perry committed
454 455 456 457 458
  }

  /// Resets the field to its initial value.
  void reset() {
    setState(() {
459
      _value = widget.initialValue;
460 461
      _hasInteractedByUser.value = false;
      _errorText.value = null;
Matt Perry's avatar
Matt Perry committed
462
    });
463
    Form.of(context)?._fieldDidChange();
Matt Perry's avatar
Matt Perry committed
464 465
  }

466 467
  /// Calls [FormField.validator] to set the [errorText]. Returns true if there
  /// were no errors.
468 469 470 471 472
  ///
  /// See also:
  ///
  ///  * [isValid], which passively gets the validity without setting
  ///    [errorText] or [hasError].
473 474 475 476 477 478 479
  bool validate() {
    setState(() {
      _validate();
    });
    return !hasError;
  }

480
  void _validate() {
481
    if (widget.validator != null)
482
      _errorText.value = widget.validator!(_value);
483 484
  }

Matt Perry's avatar
Matt Perry committed
485
  /// Updates this field's state to the new value. Useful for responding to
486 487
  /// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
  ///
488 489 490
  /// Triggers the [Form.onChanged] callback and, if [Form.autovalidateMode] is
  /// [AutovalidateMode.always] or [AutovalidateMode.onUserInteraction],
  /// revalidates all the fields of the form.
491
  void didChange(T? value) {
Matt Perry's avatar
Matt Perry committed
492 493
    setState(() {
      _value = value;
494
      _hasInteractedByUser.value = true;
Matt Perry's avatar
Matt Perry committed
495 496 497 498
    });
    Form.of(context)?._fieldDidChange();
  }

499 500
  /// Sets the value associated with this form field.
  ///
501
  /// This method should only be called by subclasses that need to update
502 503
  /// the form field value due to state changes identified during the widget
  /// build phase, when calling `setState` is prohibited. In all other cases,
504
  /// the value should be set by a call to [didChange], which ensures that
505 506
  /// `setState` is called.
  @protected
507
  void setValue(T? value) {
508 509 510
    _value = value;
  }

Matt Perry's avatar
Matt Perry committed
511
  @override
512 513 514 515 516 517
  String? get restorationId => widget.restorationId;

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_errorText, 'error_text');
    registerForRestoration(_hasInteractedByUser, 'has_interacted_by_user');
Matt Perry's avatar
Matt Perry committed
518 519 520 521 522 523 524 525 526 527
  }

  @override
  void deactivate() {
    Form.of(context)?._unregister(this);
    super.deactivate();
  }

  @override
  Widget build(BuildContext context) {
528 529 530 531 532 533
    if (widget.enabled) {
      switch (widget.autovalidateMode) {
        case AutovalidateMode.always:
          _validate();
          break;
        case AutovalidateMode.onUserInteraction:
534
          if (_hasInteractedByUser.value) {
535 536 537 538 539 540 541
            _validate();
          }
          break;
        case AutovalidateMode.disabled:
          break;
      }
    }
Matt Perry's avatar
Matt Perry committed
542
    Form.of(context)?._register(this);
543
    return widget.builder(this);
Matt Perry's avatar
Matt Perry committed
544
  }
545
}
546 547 548 549 550 551 552 553 554 555 556 557 558

/// Used to configure the auto validation of [FormField] and [Form] widgets.
enum AutovalidateMode {
  /// No auto validation will occur.
  disabled,

  /// Used to auto-validate [Form] and [FormField] even without user interaction.
  always,

  /// Used to auto-validate [Form] and [FormField] only after each user
  /// interaction.
  onUserInteraction,
}