blob: 50c04d70ca5fda4ce688adb03bbd6a043f37f791 [file] [log] [blame]
// 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:
/// * <>
/// * [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.
Key key,
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,
Brightness keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
}) : 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),
key: key,
initialValue: controller != null ? controller.text : (initialValue ?? ''),
onSaved: onSaved,
validator: validator,
autovalidate: autovalidate,
builder: (FormFieldState<String> field) {
final _TextFormFieldState state = field;
final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
return new 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,
/// Controls the text being edited.
/// If null, this widget will create its own [TextEditingController] and
/// initialize its [TextEditingController.text] with [initialValue].
final TextEditingController controller;
_TextFormFieldState createState() => new _TextFormFieldState();
class _TextFormFieldState extends FormFieldState<String> {
TextEditingController _controller;
TextEditingController get _effectiveController => widget.controller ?? _controller;
TextFormField get widget => super.widget;
void initState() {
if (widget.controller == null) {
_controller = new TextEditingController(text: widget.initialValue);
} else {
void didUpdateWidget(TextFormField oldWidget) {
if (widget.controller != oldWidget.controller) {
if (oldWidget.controller != null && widget.controller == null)
_controller = new TextEditingController.fromValue(oldWidget.controller.value);
if (widget.controller != null) {
if (oldWidget.controller == null)
_controller = null;
void dispose() {
void 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)