form.dart 13 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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
import 'will_pop_scope.dart';
8

9
/// An optional container for grouping together multiple form field widgets
10
/// (e.g. [TextField] widgets).
11 12 13 14 15 16 17
///
/// 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].
18
///
Kate Lovett's avatar
Kate Lovett committed
19
/// {@tool snippet --template=stateful_widget_scaffold}
20 21 22 23 24
/// This example shows a [Form] with one [TextFormField] to enter an email
/// address and a [RaisedButton] to submit the form. A [GlobalKey] is used here
/// to identify the [Form] and validate input.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/form.png)
25 26 27 28 29 30 31 32 33 34 35 36
///
/// ```dart
/// final _formKey = GlobalKey<FormState>();
///
/// @override
/// Widget build(BuildContext context) {
///   return Form(
///     key: _formKey,
///     child: Column(
///       crossAxisAlignment: CrossAxisAlignment.start,
///       children: <Widget>[
///         TextFormField(
37 38 39
///           decoration: const InputDecoration(
///             hintText: 'Enter your email',
///           ),
40 41 42 43
///           validator: (value) {
///             if (value.isEmpty) {
///               return 'Please enter some text';
///             }
44
///             return null;
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
///           },
///         ),
///         Padding(
///           padding: const EdgeInsets.symmetric(vertical: 16.0),
///           child: RaisedButton(
///             onPressed: () {
///               // Validate will return true if the form is valid, or false if
///               // the form is invalid.
///               if (_formKey.currentState.validate()) {
///                 // Process data.
///               }
///             },
///             child: Text('Submit'),
///           ),
///         ),
///       ],
///     ),
///   );
/// }
/// ```
/// {@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].
72
class Form extends StatefulWidget {
73 74 75
  /// Creates a container for form fields.
  ///
  /// The [child] argument must not be null.
76
  const Form({
77
    Key key,
78
    @required this.child,
79
    this.autovalidate = false,
80
    this.onWillPop,
81
    this.onChanged,
82 83
  }) : assert(child != null),
       super(key: key);
84

Matt Perry's avatar
Matt Perry committed
85 86 87 88 89 90 91 92 93
  /// Returns the closest [FormState] which encloses the given context.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// FormState form = Form.of(context);
  /// form.save();
  /// ```
  static FormState of(BuildContext context) {
94
    final _FormScope scope = context.inheritFromWidgetOfExactType(_FormScope);
Matt Perry's avatar
Matt Perry committed
95 96
    return scope?._formState;
  }
97

98 99 100 101 102
  /// The widget below this widget in the tree.
  ///
  /// This is the root of the widget hierarchy that contains this form.
  ///
  /// {@macro flutter.widgets.child}
103 104
  final Widget child;

105 106 107 108 109
  /// If true, form fields will validate and update their error text
  /// immediately after every change. Otherwise, you must call
  /// [FormState.validate] to validate.
  final bool autovalidate;

110 111 112 113 114
  /// 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.
115 116 117 118 119
  ///
  /// See also:
  ///
  ///  * [WillPopScope], another widget that provides a way to intercept the
  ///    back button.
120
  final WillPopCallback onWillPop;
121

122 123 124 125 126 127
  /// Called when one of the form fields changes.
  ///
  /// In addition to this callback being invoked, all the form fields themselves
  /// will rebuild.
  final VoidCallback onChanged;

128
  @override
129
  FormState createState() => FormState();
130 131
}

132
/// State associated with a [Form] widget.
133
///
134
/// A [FormState] object can be used to [save], [reset], and [validate] every
135 136 137
/// [FormField] that is a descendant of the associated [Form].
///
/// Typically obtained via [Form.of].
Matt Perry's avatar
Matt Perry committed
138 139
class FormState extends State<Form> {
  int _generation = 0;
140
  final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};
141

142 143
  // 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
144
  void _fieldDidChange() {
145 146 147 148 149 150
    if (widget.onChanged != null)
      widget.onChanged();
    _forceRebuild();
  }

  void _forceRebuild() {
151
    setState(() {
Matt Perry's avatar
Matt Perry committed
152
      ++_generation;
153 154 155
    });
  }

Matt Perry's avatar
Matt Perry committed
156 157 158 159 160 161 162 163
  void _register(FormFieldState<dynamic> field) {
    _fields.add(field);
  }

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

164 165
  @override
  Widget build(BuildContext context) {
166
    if (widget.autovalidate)
167
      _validate();
168
    return WillPopScope(
169
      onWillPop: widget.onWillPop,
170
      child: _FormScope(
171 172
        formState: this,
        generation: _generation,
173
        child: widget.child,
174
      ),
175 176 177
    );
  }

178
  /// Saves every [FormField] that is a descendant of this [Form].
Matt Perry's avatar
Matt Perry committed
179 180 181 182
  void save() {
    for (FormFieldState<dynamic> field in _fields)
      field.save();
  }
183

184
  /// Resets every [FormField] that is a descendant of this [Form] back to its
185 186 187 188 189 190
  /// [FormField.initialState].
  ///
  /// The [Form.onChanged] callback will be called.
  ///
  /// If the form's [Form.autovalidate] property is true, the fields will all be
  /// revalidated after being reset.
Matt Perry's avatar
Matt Perry committed
191 192 193 194 195
  void reset() {
    for (FormFieldState<dynamic> field in _fields)
      field.reset();
    _fieldDidChange();
  }
196

197
  /// Validates every [FormField] that is a descendant of this [Form], and
198
  /// returns true if there are no errors.
199 200
  ///
  /// The form will rebuild to report the results.
201
  bool validate() {
202
    _forceRebuild();
203 204 205 206 207 208 209 210
    return _validate();
  }

  bool _validate() {
    bool hasError = false;
    for (FormFieldState<dynamic> field in _fields)
      hasError = !field.validate() || hasError;
    return !hasError;
Matt Perry's avatar
Matt Perry committed
211
  }
212 213
}

Matt Perry's avatar
Matt Perry committed
214
class _FormScope extends InheritedWidget {
215
  const _FormScope({
216 217
    Key key,
    Widget child,
Matt Perry's avatar
Matt Perry committed
218
    FormState formState,
219
    int generation,
220
  }) : _formState = formState,
221 222 223
       _generation = generation,
       super(key: key, child: child);

Matt Perry's avatar
Matt Perry committed
224
  final FormState _formState;
225 226 227 228 229

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

230
  /// The [Form] associated with this widget.
231
  Form get form => _formState.widget;
232

Matt Perry's avatar
Matt Perry committed
233 234 235 236 237
  @override
  bool updateShouldNotify(_FormScope old) => _generation != old._generation;
}

/// Signature for validating a form field.
238
///
239 240 241
/// Returns an error string to display if the input is invalid, or null
/// otherwise.
///
242
/// Used by [FormField.validator].
243
typedef FormFieldValidator<T> = String Function(T value);
Matt Perry's avatar
Matt Perry committed
244 245

/// Signature for being notified when a form field changes value.
246 247
///
/// Used by [FormField.onSaved].
248
typedef FormFieldSetter<T> = void Function(T newValue);
Matt Perry's avatar
Matt Perry committed
249 250

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

255 256 257 258
/// 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
259 260 261 262 263 264 265 266
///
/// 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.
///
267 268 269 270 271
/// 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.
///
272 273 274 275
/// 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
276
class FormField<T> extends StatefulWidget {
277 278 279
  /// Creates a single form field.
  ///
  /// The [builder] argument must not be null.
280
  const FormField({
Matt Perry's avatar
Matt Perry committed
281 282 283 284 285
    Key key,
    @required this.builder,
    this.onSaved,
    this.validator,
    this.initialValue,
286
    this.autovalidate = false,
287
    this.enabled = true,
288 289
  }) : assert(builder != null),
       super(key: key);
290

Matt Perry's avatar
Matt Perry committed
291
  /// An optional method to call with the final value when the form is saved via
292
  /// [FormState.save].
Matt Perry's avatar
Matt Perry committed
293 294 295 296
  final FormFieldSetter<T> onSaved;

  /// An optional method that validates an input. Returns an error string to
  /// display if the input is invalid, or null otherwise.
297 298 299 300
  ///
  /// The returned value is exposed by the [FormFieldState.errorText] property.
  /// The [TextFormField] uses this to override the [InputDecoration.errorText]
  /// value.
301 302 303 304 305 306 307
  ///
  /// 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
  /// height parent like [SizedBox], or set the [TextFormField.helperText]
  /// parameter to a space.
Matt Perry's avatar
Matt Perry committed
308 309 310 311 312 313 314 315 316
  final FormFieldValidator<T> validator;

  /// 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.
  final T initialValue;
317

318
  /// If true, this form field will validate and update its error text
319 320
  /// immediately after every change. Otherwise, you must call
  /// [FormFieldState.validate] to validate. If part of a [Form] that
321
  /// auto-validates, this value will be ignored.
322 323
  final bool autovalidate;

324 325 326 327 328 329 330
  /// Whether the form is able to receive user input.
  ///
  /// Defaults to true. If [autovalidate] is true, the field will be validated.
  /// Likewise, if this field is false, the widget will not be validated
  /// regardless of [autovalidate].
  final bool enabled;

331
  @override
332
  FormFieldState<T> createState() => FormFieldState<T>();
Matt Perry's avatar
Matt Perry committed
333 334
}

335 336
/// The current state of a [FormField]. Passed to the [FormFieldBuilder] method
/// for use in constructing the form field's widget.
Matt Perry's avatar
Matt Perry committed
337 338 339 340 341 342 343
class FormFieldState<T> extends State<FormField<T>> {
  T _value;
  String _errorText;

  /// The current value of the form field.
  T get value => _value;

344 345 346
  /// 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.
Matt Perry's avatar
Matt Perry committed
347 348 349 350 351 352 353
  String get errorText => _errorText;

  /// True if this field has any validation errors.
  bool get hasError => _errorText != null;

  /// Calls the [FormField]'s onSaved method with the current value.
  void save() {
354 355
    if (widget.onSaved != null)
      widget.onSaved(value);
Matt Perry's avatar
Matt Perry committed
356 357 358 359 360
  }

  /// Resets the field to its initial value.
  void reset() {
    setState(() {
361
      _value = widget.initialValue;
Matt Perry's avatar
Matt Perry committed
362 363 364 365
      _errorText = null;
    });
  }

366 367 368 369 370 371 372 373 374
  /// Calls [FormField.validator] to set the [errorText]. Returns true if there
  /// were no errors.
  bool validate() {
    setState(() {
      _validate();
    });
    return !hasError;
  }

375
  void _validate() {
376 377
    if (widget.validator != null)
      _errorText = widget.validator(_value);
378 379
  }

Matt Perry's avatar
Matt Perry committed
380
  /// Updates this field's state to the new value. Useful for responding to
381 382 383 384 385
  /// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
  ///
  /// Triggers the [Form.onChanged] callback and, if the [Form.autovalidate]
  /// field is set, revalidates all the fields of the form.
  void didChange(T value) {
Matt Perry's avatar
Matt Perry committed
386 387 388 389 390 391
    setState(() {
      _value = value;
    });
    Form.of(context)?._fieldDidChange();
  }

392 393 394 395 396
  /// Sets the value associated with this form field.
  ///
  /// This method should be only be called by subclasses that need to update
  /// the form field value due to state changes identified during the widget
  /// build phase, when calling `setState` is prohibited. In all other cases,
397
  /// the value should be set by a call to [didChange], which ensures that
398 399 400 401 402 403
  /// `setState` is called.
  @protected
  void setValue(T value) {
    _value = value;
  }

Matt Perry's avatar
Matt Perry committed
404 405 406
  @override
  void initState() {
    super.initState();
407
    _value = widget.initialValue;
Matt Perry's avatar
Matt Perry committed
408 409 410 411 412 413 414 415 416 417
  }

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

  @override
  Widget build(BuildContext context) {
418 419
    // Only autovalidate if the widget is also enabled
    if (widget.autovalidate && widget.enabled)
420
      _validate();
Matt Perry's avatar
Matt Perry committed
421
    Form.of(context)?._register(this);
422
    return widget.builder(this);
Matt Perry's avatar
Matt Perry committed
423
  }
424
}