// Copyright 2015 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 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'input_decorator.dart'; import 'text_field.dart'; import 'theme.dart'; /// A [FormField] that contains a [TextField]. /// /// This is a convenience widget that wraps a [TextField] widget in a /// [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. /// /// 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. /// /// For a documentation about the various parameters, see [TextField]. /// /// See also: /// /// * <https://material.google.com/components/text-fields.html> /// * [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. class TextFormField extends FormField<String> { /// Creates a [FormField] that contains a [TextField]. /// /// 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 /// to [initalValue] or the empty string. /// /// For documentation about the various parameters, see the [TextField] class /// and [new TextField], the constructor. TextFormField({ Key key, this.controller, String initialValue, FocusNode focusNode, InputDecoration decoration = const InputDecoration(), TextInputType keyboardType, TextCapitalization textCapitalization = TextCapitalization.none, TextInputAction textInputAction, TextStyle style, TextAlign textAlign = TextAlign.start, bool autofocus = false, bool obscureText = false, bool autocorrect = true, bool autovalidate = false, bool maxLengthEnforced = true, int maxLines = 1, int maxLength, VoidCallback onEditingComplete, ValueChanged<String> onFieldSubmitted, FormFieldSetter<String> onSaved, FormFieldValidator<String> validator, List<TextInputFormatter> inputFormatters, bool enabled = true, Brightness keyboardAppearance, EdgeInsets scrollPadding = const EdgeInsets.all(20.0), bool enableInteractiveSelection = true, }) : assert(initialValue == null || controller == null), assert(textAlign != null), assert(autofocus != null), assert(obscureText != null), assert(autocorrect != null), assert(autovalidate != null), assert(maxLengthEnforced != null), assert(scrollPadding != null), assert(maxLines == null || maxLines > 0), assert(maxLength == null || maxLength > 0), assert(enableInteractiveSelection != null), super( key: key, initialValue: controller != null ? controller.text : (initialValue ?? ''), onSaved: onSaved, validator: validator, autovalidate: autovalidate, enabled: enabled, builder: (FormFieldState<String> field) { final _TextFormFieldState state = field; final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration()) .applyDefaults(Theme.of(field.context).inputDecorationTheme); return TextField( controller: state._effectiveController, focusNode: focusNode, decoration: effectiveDecoration.copyWith(errorText: field.errorText), keyboardType: keyboardType, textInputAction: textInputAction, style: style, textAlign: textAlign, textCapitalization: textCapitalization, autofocus: autofocus, obscureText: obscureText, autocorrect: autocorrect, maxLengthEnforced: maxLengthEnforced, maxLines: maxLines, maxLength: maxLength, onChanged: field.didChange, onEditingComplete: onEditingComplete, onSubmitted: onFieldSubmitted, inputFormatters: inputFormatters, enabled: enabled, scrollPadding: scrollPadding, keyboardAppearance: keyboardAppearance, enableInteractiveSelection: enableInteractiveSelection, ); }, ); /// Controls the text being edited. /// /// If null, this widget will create its own [TextEditingController] and /// initialize its [TextEditingController.text] with [initialValue]. final TextEditingController controller; @override _TextFormFieldState createState() => _TextFormFieldState(); } class _TextFormFieldState extends FormFieldState<String> { TextEditingController _controller; TextEditingController get _effectiveController => widget.controller ?? _controller; @override TextFormField get widget => super.widget; @override void initState() { super.initState(); if (widget.controller == null) { _controller = TextEditingController(text: widget.initialValue); } 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) _controller = TextEditingController.fromValue(oldWidget.controller.value); 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) didChange(_effectiveController.text); } }