// Copyright 2014 The Flutter 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 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'basic.dart'; import 'framework.dart'; import 'navigator.dart'; import 'pop_scope.dart'; import 'restoration.dart'; import 'restoration_properties.dart'; import 'routes.dart'; import 'will_pop_scope.dart'; // Duration for delay before announcement in IOS so that the announcement won't be interrupted. const Duration _kIOSAnnouncementDelayDuration = Duration(seconds: 1); // Examples can assume: // late BuildContext context; /// An optional container for grouping together multiple form field widgets /// (e.g. [TextField] widgets). /// /// 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]. /// /// {@tool dartpad} /// This example shows a [Form] with one [TextFormField] to enter an email /// address and an [ElevatedButton] to submit the form. A [GlobalKey] is used here /// to identify the [Form] and validate input. /// ///  /// /// ** See code in examples/api/lib/widgets/form/form.0.dart ** /// {@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]. class Form extends StatefulWidget { /// Creates a container for form fields. const Form({ super.key, required this.child, this.canPop, this.onPopInvoked, @Deprecated( 'Use canPop and/or onPopInvoked instead. ' 'This feature was deprecated after v3.12.0-1.0.pre.', ) this.onWillPop, this.onChanged, AutovalidateMode? autovalidateMode, }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled, assert((onPopInvoked == null && canPop == null) || onWillPop == null, 'onWillPop is deprecated; use canPop and/or onPopInvoked.'); /// Returns the [FormState] of the closest [Form] widget which encloses the /// given context, or null if none is found. /// /// Typical usage is as follows: /// /// ```dart /// FormState? form = Form.maybeOf(context); /// form?.save(); /// ``` /// /// Calling this method will create a dependency on the closest [Form] in the /// [context], if there is one. /// /// See also: /// /// * [Form.of], which is similar to this method, but asserts if no [Form] /// ancestor is found. static FormState? maybeOf(BuildContext context) { final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>(); return scope?._formState; } /// Returns the [FormState] of the closest [Form] widget which encloses the /// given context. /// /// Typical usage is as follows: /// /// ```dart /// FormState form = Form.of(context); /// form.save(); /// ``` /// /// If no [Form] ancestor is found, this will assert in debug mode, and throw /// an exception in release mode. /// /// Calling this method will create a dependency on the closest [Form] in the /// [context]. /// /// See also: /// /// * [Form.maybeOf], which is similar to this method, but returns null if no /// [Form] ancestor is found. static FormState of(BuildContext context) { final FormState? formState = maybeOf(context); assert(() { if (formState == null) { throw FlutterError( 'Form.of() was called with a context that does not contain a Form widget.\n' 'No Form widget ancestor could be found starting from the context that ' 'was passed to Form.of(). This can happen because you are using a widget ' 'that looks for a Form ancestor, but no such ancestor exists.\n' 'The context used was:\n' ' $context', ); } return true; }()); return formState!; } /// The widget below this widget in the tree. /// /// This is the root of the widget hierarchy that contains this form. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; /// 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. /// /// See also: /// /// * [WillPopScope], another widget that provides a way to intercept the /// back button. @Deprecated( 'Use canPop and/or onPopInvoked instead. ' 'This feature was deprecated after v3.12.0-1.0.pre.', ) final WillPopCallback? onWillPop; /// {@macro flutter.widgets.PopScope.canPop} /// /// {@tool dartpad} /// This sample demonstrates how to use this parameter to show a confirmation /// dialog when a navigation pop would cause form data to be lost. /// /// ** See code in examples/api/lib/widgets/form/form.1.dart ** /// {@end-tool} /// /// See also: /// /// * [onPopInvoked], which also comes from [PopScope] and is often used in /// conjunction with this parameter. /// * [PopScope.canPop], which is what [Form] delegates to internally. final bool? canPop; /// {@macro flutter.widgets.navigator.onPopInvoked} /// /// {@tool dartpad} /// This sample demonstrates how to use this parameter to show a confirmation /// dialog when a navigation pop would cause form data to be lost. /// /// ** See code in examples/api/lib/widgets/form/form.1.dart ** /// {@end-tool} /// /// See also: /// /// * [canPop], which also comes from [PopScope] and is often used in /// conjunction with this parameter. /// * [PopScope.onPopInvoked], which is what [Form] delegates to internally. final PopInvokedCallback? onPopInvoked; /// 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; /// Used to enable/disable form fields auto validation and update their error /// text. /// /// {@macro flutter.widgets.FormField.autovalidateMode} final AutovalidateMode autovalidateMode; @override FormState createState() => FormState(); } /// State associated with a [Form] widget. /// /// A [FormState] object can be used to [save], [reset], and [validate] every /// [FormField] that is a descendant of the associated [Form]. /// /// Typically obtained via [Form.of]. class FormState extends State