form.dart 9.47 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/foundation.dart';
6

7
import 'framework.dart';
8
import 'navigator.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
class Form extends StatefulWidget {
21 22 23
  /// Creates a container for form fields.
  ///
  /// The [child] argument must not be null.
24
  const Form({
25
    Key key,
26
    @required this.child,
27
    this.autovalidate: false,
28
    this.onWillPop,
29 30
  }) : assert(child != null),
       super(key: key);
31

Matt Perry's avatar
Matt Perry committed
32 33 34 35 36 37 38 39 40
  /// 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) {
41
    final _FormScope scope = context.inheritFromWidgetOfExactType(_FormScope);
Matt Perry's avatar
Matt Perry committed
42 43
    return scope?._formState;
  }
44 45 46 47

  /// Root of the widget hierarchy that contains this form.
  final Widget child;

48 49 50 51 52
  /// 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;

53 54 55 56 57
  /// 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.
58 59 60 61 62
  ///
  /// See also:
  ///
  ///  * [WillPopScope], another widget that provides a way to intercept the
  ///    back button.
63
  final WillPopCallback onWillPop;
64

65
  @override
Matt Perry's avatar
Matt Perry committed
66
  FormState createState() => new FormState();
67 68
}

69
/// State associated with a [Form] widget.
70
///
71
/// A [FormState] object can be used to [save], [reset], and [validate] every
72 73 74
/// [FormField] that is a descendant of the associated [Form].
///
/// Typically obtained via [Form.of].
Matt Perry's avatar
Matt Perry committed
75 76
class FormState extends State<Form> {
  int _generation = 0;
77
  final Set<FormFieldState<dynamic>> _fields = new Set<FormFieldState<dynamic>>();
78

79 80
  // 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
81
  void _fieldDidChange() {
82
    setState(() {
Matt Perry's avatar
Matt Perry committed
83
      ++_generation;
84 85 86
    });
  }

Matt Perry's avatar
Matt Perry committed
87 88 89 90 91 92 93 94
  void _register(FormFieldState<dynamic> field) {
    _fields.add(field);
  }

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

95 96
  @override
  Widget build(BuildContext context) {
97
    if (widget.autovalidate)
98
      _validate();
99
    return new WillPopScope(
100
      onWillPop: widget.onWillPop,
101 102 103
      child: new _FormScope(
        formState: this,
        generation: _generation,
104
        child: widget.child,
105
      ),
106 107 108
    );
  }

109
  /// Saves every [FormField] that is a descendant of this [Form].
Matt Perry's avatar
Matt Perry committed
110 111 112 113
  void save() {
    for (FormFieldState<dynamic> field in _fields)
      field.save();
  }
114

115
  /// Resets every [FormField] that is a descendant of this [Form] back to its
Matt Perry's avatar
Matt Perry committed
116 117 118 119 120 121
  /// initialState.
  void reset() {
    for (FormFieldState<dynamic> field in _fields)
      field.reset();
    _fieldDidChange();
  }
122

123
  /// Validates every [FormField] that is a descendant of this [Form], and
124
  /// returns true if there are no errors.
125 126 127 128 129 130 131 132 133 134
  bool validate() {
    _fieldDidChange();
    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
135
  }
136 137
}

Matt Perry's avatar
Matt Perry committed
138
class _FormScope extends InheritedWidget {
139
  const _FormScope({
140 141
    Key key,
    Widget child,
Matt Perry's avatar
Matt Perry committed
142
    FormState formState,
143
    int generation
144
  }) : _formState = formState,
145 146 147
       _generation = generation,
       super(key: key, child: child);

Matt Perry's avatar
Matt Perry committed
148
  final FormState _formState;
149 150 151 152 153

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

154
  /// The [Form] associated with this widget.
155
  Form get form => _formState.widget;
156

Matt Perry's avatar
Matt Perry committed
157 158 159 160 161
  @override
  bool updateShouldNotify(_FormScope old) => _generation != old._generation;
}

/// Signature for validating a form field.
162 163
///
/// Used by [FormField.validator].
Matt Perry's avatar
Matt Perry committed
164 165 166
typedef String FormFieldValidator<T>(T value);

/// Signature for being notified when a form field changes value.
167 168
///
/// Used by [FormField.onSaved].
Matt Perry's avatar
Matt Perry committed
169 170 171
typedef void FormFieldSetter<T>(T newValue);

/// Signature for building the widget representing the form field.
172 173
///
/// Used by [FormField.builder].
Matt Perry's avatar
Matt Perry committed
174 175
typedef Widget FormFieldBuilder<T>(FormFieldState<T> field);

176 177 178 179
/// 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
180 181 182 183 184 185 186 187
///
/// 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.
///
188 189 190 191 192
/// 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.
///
193 194 195 196
/// 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
197
class FormField<T> extends StatefulWidget {
198 199 200
  /// Creates a single form field.
  ///
  /// The [builder] argument must not be null.
201
  const FormField({
Matt Perry's avatar
Matt Perry committed
202 203 204 205 206
    Key key,
    @required this.builder,
    this.onSaved,
    this.validator,
    this.initialValue,
207
    this.autovalidate: false,
208 209
  }) : assert(builder != null),
       super(key: key);
210

Matt Perry's avatar
Matt Perry committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
  /// An optional method to call with the final value when the form is saved via
  /// Form.save().
  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.
  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;
226

227
  /// If true, this form field will validate and update its error text
228 229 230 231 232
  /// immediately after every change. Otherwise, you must call
  /// [FormFieldState.validate] to validate. If part of a [Form] that
  /// autovalidates, this value will be ignored.
  final bool autovalidate;

233
  @override
Matt Perry's avatar
Matt Perry committed
234 235 236
  FormFieldState<T> createState() => new FormFieldState<T>();
}

237 238
/// 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
239 240 241 242 243 244 245
class FormFieldState<T> extends State<FormField<T>> {
  T _value;
  String _errorText;

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

246 247 248
  /// 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
249 250 251 252 253 254 255
  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() {
256 257
    if (widget.onSaved != null)
      widget.onSaved(value);
Matt Perry's avatar
Matt Perry committed
258 259 260 261 262
  }

  /// Resets the field to its initial value.
  void reset() {
    setState(() {
263
      _value = widget.initialValue;
Matt Perry's avatar
Matt Perry committed
264 265 266 267
      _errorText = null;
    });
  }

268 269 270 271 272 273 274 275 276 277
  /// Calls [FormField.validator] to set the [errorText]. Returns true if there
  /// were no errors.
  bool validate() {
    setState(() {
      _validate();
    });
    return !hasError;
  }

  bool _validate() {
278 279
    if (widget.validator != null)
      _errorText = widget.validator(_value);
280 281 282
    return !hasError;
  }

Matt Perry's avatar
Matt Perry committed
283 284 285 286 287 288 289 290 291
  /// Updates this field's state to the new value. Useful for responding to
  /// child widget changes, e.g. [Slider]'s onChanged argument.
  void onChanged(T value) {
    setState(() {
      _value = value;
    });
    Form.of(context)?._fieldDidChange();
  }

292 293 294 295 296 297 298 299 300 301 302 303
  /// 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,
  /// the value should be set by a call to [onChanged], which ensures that
  /// `setState` is called.
  @protected
  void setValue(T value) {
    _value = value;
  }

Matt Perry's avatar
Matt Perry committed
304 305 306
  @override
  void initState() {
    super.initState();
307
    _value = widget.initialValue;
Matt Perry's avatar
Matt Perry committed
308 309 310 311 312 313 314 315 316 317
  }

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

  @override
  Widget build(BuildContext context) {
318
    if (widget.autovalidate)
319
      _validate();
Matt Perry's avatar
Matt Perry committed
320
    Form.of(context)?._register(this);
321
    return widget.builder(this);
Matt Perry's avatar
Matt Perry committed
322
  }
323
}