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

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

import 'input_decorator.dart';
import 'text_field.dart';
10
import 'theme.dart';
11

12 13
export 'package:flutter/services.dart' show SmartQuotesType, SmartDashesType;

14 15
/// A [FormField] that contains a [TextField].
///
16
/// This is a convenience widget that wraps a [TextField] widget in a
17 18 19 20 21 22 23
/// [FormField].
///
/// 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.
///
24 25 26 27 28 29 30 31 32
/// When a [controller] is specified, its [TextEditingController.text]
/// defines the [initialValue]. If this [FormField] is part of a scrolling
/// container that lazily constructs its children, like a [ListView] or a
/// [CustomScrollView], then a [controller] should be specified.
/// The controller's lifetime should be managed by a stateful widget ancestor
/// of the scrolling container.
///
/// If a [controller] is not specified, [initialValue] can be used to give
/// the automatically generated controller an initial value.
33
///
34 35 36
/// Remember to [dispose] of the [TextEditingController] when it is no longer needed.
/// This will ensure we discard any resources used by the object.
///
37 38
/// For a documentation about the various parameters, see [TextField].
///
39
/// {@tool sample}
40 41 42
///
/// Creates a [TextFormField] with an [InputDecoration] and validator function.
///
43 44 45 46
/// ![If the user enters valid text, the TextField appears normally without any warnings to the user](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field.png)
///
/// ![If the user enters invalid text, the error message returned from the validator function is displayed in dark red underneath the input](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field_error.png)
///
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
/// ```dart
/// TextFormField(
///   decoration: const InputDecoration(
///     icon: Icon(Icons.person),
///     hintText: 'What do people call you?',
///     labelText: 'Name *',
///   ),
///   onSaved: (String value) {
///     // This optional block of code can be used to run
///     // code when the user saves the form.
///   },
///   validator: (String value) {
///     return value.contains('@') ? 'Do not use the @ char.' : null;
///   },
/// )
/// ```
63
/// {@end-tool}
64 65 66
///
/// See also:
///
67
///  * <https://material.io/design/components/text-fields.html>
68 69 70 71
///  * [TextField], which is the underlying text field without the [Form]
///    integration.
///  * [InputDecorator], which shows the labels and other visual elements that
///    surround the actual text editing widget.
72
///  * Learn how to use a [TextEditingController] in one of our [cookbook recipe]s.(https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller)
73
class TextFormField extends FormField<String> {
74 75
  /// Creates a [FormField] that contains a [TextField].
  ///
76 77 78
  /// When a [controller] is specified, [initialValue] must be null (the
  /// default). If [controller] is null, then a [TextEditingController]
  /// will be constructed automatically and its `text` will be initialized
79
  /// to [initialValue] or the empty string.
80
  ///
81 82
  /// For documentation about the various parameters, see the [TextField] class
  /// and [new TextField], the constructor.
83 84
  TextFormField({
    Key key,
85
    this.controller,
86
    String initialValue,
87
    FocusNode focusNode,
88
    InputDecoration decoration = const InputDecoration(),
89
    TextInputType keyboardType,
90
    TextCapitalization textCapitalization = TextCapitalization.none,
91
    TextInputAction textInputAction,
92
    TextStyle style,
93
    StrutStyle strutStyle,
94
    TextDirection textDirection,
95
    TextAlign textAlign = TextAlign.start,
96
    TextAlignVertical textAlignVertical,
97
    bool autofocus = false,
98
    bool readOnly = false,
99
    ToolbarOptions toolbarOptions,
100
    bool showCursor,
101 102
    bool obscureText = false,
    bool autocorrect = true,
103 104
    SmartDashesType smartDashesType,
    SmartQuotesType smartQuotesType,
105
    bool enableSuggestions = true,
106 107 108
    bool autovalidate = false,
    bool maxLengthEnforced = true,
    int maxLines = 1,
109 110
    int minLines,
    bool expands = false,
111
    int maxLength,
112
    ValueChanged<String> onChanged,
113
    GestureTapCallback onTap,
114
    VoidCallback onEditingComplete,
115
    ValueChanged<String> onFieldSubmitted,
116 117
    FormFieldSetter<String> onSaved,
    FormFieldValidator<String> validator,
118
    List<TextInputFormatter> inputFormatters,
119
    bool enabled = true,
120 121 122
    double cursorWidth = 2.0,
    Radius cursorRadius,
    Color cursorColor,
123
    Brightness keyboardAppearance,
124
    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
125
    bool enableInteractiveSelection = true,
126
    InputCounterWidgetBuilder buildCounter,
127
  }) : assert(initialValue == null || controller == null),
128
       assert(textAlign != null),
129
       assert(autofocus != null),
130
       assert(readOnly != null),
131
       assert(obscureText != null),
132
       assert(autocorrect != null),
133
       assert(enableSuggestions != null),
134
       assert(autovalidate != null),
135
       assert(maxLengthEnforced != null),
136
       assert(scrollPadding != null),
137
       assert(maxLines == null || maxLines > 0),
138 139 140 141 142 143 144 145 146 147
       assert(minLines == null || minLines > 0),
       assert(
         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
         'minLines can\'t be greater than maxLines',
       ),
       assert(expands != null),
       assert(
         !expands || (maxLines == null && minLines == null),
         'minLines and maxLines must be null when expands is true.',
       ),
148
       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
149
       assert(maxLength == null || maxLength > 0),
150
       assert(enableInteractiveSelection != null),
151
       super(
152
    key: key,
153
    initialValue: controller != null ? controller.text : (initialValue ?? ''),
154 155
    onSaved: onSaved,
    validator: validator,
156
    autovalidate: autovalidate,
157
    enabled: enabled,
158
    builder: (FormFieldState<String> field) {
159
      final _TextFormFieldState state = field as _TextFormFieldState;
160 161
      final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
        .applyDefaults(Theme.of(field.context).inputDecorationTheme);
162 163 164 165 166 167
      void onChangedHandler(String value) {
        if (onChanged != null) {
          onChanged(value);
        }
        field.didChange(value);
      }
168
      return TextField(
169
        controller: state._effectiveController,
170
        focusNode: focusNode,
171
        decoration: effectiveDecoration.copyWith(errorText: field.errorText),
172
        keyboardType: keyboardType,
173
        textInputAction: textInputAction,
174
        style: style,
175
        strutStyle: strutStyle,
176
        textAlign: textAlign,
177
        textAlignVertical: textAlignVertical,
178
        textDirection: textDirection,
179
        textCapitalization: textCapitalization,
180
        autofocus: autofocus,
181
        toolbarOptions: toolbarOptions,
182 183
        readOnly: readOnly,
        showCursor: showCursor,
184
        obscureText: obscureText,
185
        autocorrect: autocorrect,
186 187
        smartDashesType: smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
        smartQuotesType: smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
188
        enableSuggestions: enableSuggestions,
189
        maxLengthEnforced: maxLengthEnforced,
190
        maxLines: maxLines,
191 192
        minLines: minLines,
        expands: expands,
193
        maxLength: maxLength,
194
        onChanged: onChangedHandler,
195
        onTap: onTap,
196
        onEditingComplete: onEditingComplete,
197
        onSubmitted: onFieldSubmitted,
198
        inputFormatters: inputFormatters,
199
        enabled: enabled,
200 201 202
        cursorWidth: cursorWidth,
        cursorRadius: cursorRadius,
        cursorColor: cursorColor,
203
        scrollPadding: scrollPadding,
204
        keyboardAppearance: keyboardAppearance,
205
        enableInteractiveSelection: enableInteractiveSelection,
206
        buildCounter: buildCounter,
207 208 209
      );
    },
  );
210 211 212

  /// Controls the text being edited.
  ///
213 214
  /// If null, this widget will create its own [TextEditingController] and
  /// initialize its [TextEditingController.text] with [initialValue].
215 216 217
  final TextEditingController controller;

  @override
218
  _TextFormFieldState createState() => _TextFormFieldState();
219 220 221 222 223 224 225 226
}

class _TextFormFieldState extends FormFieldState<String> {
  TextEditingController _controller;

  TextEditingController get _effectiveController => widget.controller ?? _controller;

  @override
227
  TextFormField get widget => super.widget as TextFormField;
228 229 230 231 232

  @override
  void initState() {
    super.initState();
    if (widget.controller == null) {
233
      _controller = TextEditingController(text: widget.initialValue);
234 235 236 237 238 239 240 241 242 243 244 245 246
    } else {
      widget.controller.addListener(_handleControllerChanged);
    }
  }

  @override
  void didUpdateWidget(TextFormField oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.controller != oldWidget.controller) {
      oldWidget.controller?.removeListener(_handleControllerChanged);
      widget.controller?.addListener(_handleControllerChanged);

      if (oldWidget.controller != null && widget.controller == null)
247
        _controller = TextEditingController.fromValue(oldWidget.controller.value);
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
      if (widget.controller != null) {
        setValue(widget.controller.text);
        if (oldWidget.controller == null)
          _controller = null;
      }
    }
  }

  @override
  void dispose() {
    widget.controller?.removeListener(_handleControllerChanged);
    super.dispose();
  }

  @override
  void reset() {
    super.reset();
    setState(() {
      _effectiveController.text = widget.initialValue;
    });
  }

  void _handleControllerChanged() {
    // Suppress changes that originated from within this class.
    //
    // In the case where a controller has been passed in to this widget, we
    // register this change listener. In these cases, we'll also receive change
    // notifications for changes originating from within this class -- for
    // example, the reset() method. In such cases, the FormField value will
    // already have been set.
    if (_effectiveController.text != value)
279
      didChange(_effectiveController.text);
280
  }
281
}