diff --git a/examples/cookbook/forms/focus/lib/main.dart b/examples/cookbook/forms/focus/lib/main.dart index ebe75ccfc7f..d73d703c611 100644 --- a/examples/cookbook/forms/focus/lib/main.dart +++ b/examples/cookbook/forms/focus/lib/main.dart @@ -24,7 +24,7 @@ class MyCustomForm extends StatefulWidget { class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in // the initState method, and clean it up in the dispose method. - late FocusNode myFocusNode; + late final FocusNode myFocusNode; @override void initState() { @@ -63,7 +63,7 @@ class _MyCustomFormState extends State { onPressed: () => myFocusNode.requestFocus(), tooltip: 'Focus Second Text Field', child: const Icon(Icons.edit), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), ); } } diff --git a/examples/cookbook/forms/focus/lib/starter.dart b/examples/cookbook/forms/focus/lib/starter.dart index 6273b19ccff..f0db1744c8f 100644 --- a/examples/cookbook/forms/focus/lib/starter.dart +++ b/examples/cookbook/forms/focus/lib/starter.dart @@ -14,7 +14,7 @@ class MyCustomForm extends StatefulWidget { class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in // the initState method, and clean it up in the dispose method. - late FocusNode myFocusNode; + late final FocusNode myFocusNode; @override void initState() { diff --git a/examples/cookbook/forms/focus/lib/step2.dart b/examples/cookbook/forms/focus/lib/step2.dart index 2e32c1d5439..addcbf25689 100644 --- a/examples/cookbook/forms/focus/lib/step2.dart +++ b/examples/cookbook/forms/focus/lib/step2.dart @@ -13,7 +13,7 @@ class MyCustomForm extends StatefulWidget { class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in // the initState method, and clean it up in the dispose method. - late FocusNode myFocusNode; + late final FocusNode myFocusNode; @override void initState() { diff --git a/examples/cookbook/forms/focus/lib/step3.dart b/examples/cookbook/forms/focus/lib/step3.dart index 2bbcd7951f8..b095522716c 100644 --- a/examples/cookbook/forms/focus/lib/step3.dart +++ b/examples/cookbook/forms/focus/lib/step3.dart @@ -13,7 +13,7 @@ class MyCustomForm extends StatefulWidget { class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in // the initState method, and clean it up in the dispose method. - late FocusNode myFocusNode; + late final FocusNode myFocusNode; @override void initState() { @@ -51,6 +51,8 @@ class _MyCustomFormState extends State { // When the button is pressed, // give focus to the text field using myFocusNode. onPressed: () => myFocusNode.requestFocus(), + tooltip: 'Focus Second Text Field', + child: const Icon(Icons.edit), ), // #enddocregion FloatingActionButton ); diff --git a/examples/cookbook/forms/retrieve_input/lib/main.dart b/examples/cookbook/forms/retrieve_input/lib/main.dart index f93d877bc94..f5a6a3257e0 100644 --- a/examples/cookbook/forms/retrieve_input/lib/main.dart +++ b/examples/cookbook/forms/retrieve_input/lib/main.dart @@ -52,7 +52,7 @@ class _MyCustomFormState extends State { context: context, builder: (context) { return AlertDialog( - // Retrieve the text the that user has entered by using the + // Retrieve the text that the user has entered by using the // TextEditingController. content: Text(myController.text), ); diff --git a/examples/cookbook/forms/text_field_changes/lib/main.dart b/examples/cookbook/forms/text_field_changes/lib/main.dart index ddc4af99e5f..e1d2490c928 100644 --- a/examples/cookbook/forms/text_field_changes/lib/main.dart +++ b/examples/cookbook/forms/text_field_changes/lib/main.dart @@ -8,7 +8,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - title: 'Retrieve Text Input', + title: 'Handle Text Field Changes', home: MyCustomForm(), ); } @@ -59,7 +59,7 @@ class _MyCustomFormState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Retrieve Text Input')), + appBar: AppBar(title: const Text('Handle Text Field Changes')), body: Padding( padding: const EdgeInsets.all(16), child: Column( diff --git a/examples/cookbook/forms/text_input/lib/main.dart b/examples/cookbook/forms/text_input/lib/main.dart index 693e92092a4..8d9b23811b8 100644 --- a/examples/cookbook/forms/text_input/lib/main.dart +++ b/examples/cookbook/forms/text_input/lib/main.dart @@ -25,7 +25,7 @@ class MyCustomForm extends StatelessWidget { Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), // #docregion TextField diff --git a/examples/cookbook/forms/validation/lib/form.dart b/examples/cookbook/forms/validation/lib/form.dart index c87b9eaeba1..6f40f851ec5 100644 --- a/examples/cookbook/forms/validation/lib/form.dart +++ b/examples/cookbook/forms/validation/lib/form.dart @@ -5,19 +5,17 @@ class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override - MyCustomFormState createState() { - return MyCustomFormState(); - } + State createState() => _MyCustomFormState(); } // Define a corresponding State class. // This class holds data related to the form. -class MyCustomFormState extends State { +class _MyCustomFormState extends State { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a `GlobalKey`, - // not a GlobalKey. + // not a GlobalKey<_MyCustomFormState>. final _formKey = GlobalKey(); @override @@ -26,7 +24,7 @@ class MyCustomFormState extends State { return Form( key: _formKey, child: const Column( - children: [ + children: [ // Add TextFormFields and ElevatedButton here. ], ), diff --git a/examples/cookbook/forms/validation/lib/main.dart b/examples/cookbook/forms/validation/lib/main.dart index e955a821c26..b0777ae9ae5 100644 --- a/examples/cookbook/forms/validation/lib/main.dart +++ b/examples/cookbook/forms/validation/lib/main.dart @@ -24,19 +24,17 @@ class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override - MyCustomFormState createState() { - return MyCustomFormState(); - } + State createState() => _MyCustomFormState(); } // Create a corresponding State class. // This class holds data related to the form. -class MyCustomFormState extends State { +class _MyCustomFormState extends State { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a GlobalKey, - // not a GlobalKey. + // not a GlobalKey<_MyCustomFormState>. final _formKey = GlobalKey(); @override diff --git a/sites/docs/src/content/cookbook/forms/focus.md b/sites/docs/src/content/cookbook/forms/focus.md index 6ae62f4fb38..7d205e3297a 100644 --- a/sites/docs/src/content/cookbook/forms/focus.md +++ b/sites/docs/src/content/cookbook/forms/focus.md @@ -34,8 +34,8 @@ TextField( ); ``` -For more information on handling input and creating text fields, -see the [Forms][] section of the cookbook. +To learn more about handling input and creating text fields, +consult the [Forms][] section of the cookbook. ## Focus a text field when a button is tapped @@ -46,9 +46,9 @@ text field in response to an API call or a validation error. In this example, give focus to a text field after the user presses a button using the following steps: - 1. Create a `FocusNode`. - 2. Pass the `FocusNode` to a `TextField`. - 3. Give focus to the `TextField` when a button is tapped. +1. Create a `FocusNode`. +1. Pass the `FocusNode` to a `TextField`. +1. Give focus to the `TextField` when a button is tapped. ### 1. Create a `FocusNode` @@ -77,7 +77,7 @@ class MyCustomForm extends StatefulWidget { class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in // the initState method, and clean it up in the dispose method. - late FocusNode myFocusNode; + late final FocusNode myFocusNode; @override void initState() { @@ -126,6 +126,8 @@ FloatingActionButton( // When the button is pressed, // give focus to the text field using myFocusNode. onPressed: () => myFocusNode.requestFocus(), + tooltip: 'Focus Second Text Field', + child: const Icon(Icons.edit), ), ``` @@ -159,7 +161,7 @@ class MyCustomForm extends StatefulWidget { class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in // the initState method, and clean it up in the dispose method. - late FocusNode myFocusNode; + late final FocusNode myFocusNode; @override void initState() { @@ -198,21 +200,17 @@ class _MyCustomFormState extends State { onPressed: () => myFocusNode.requestFocus(), tooltip: 'Focus Second Text Field', child: const Icon(Icons.edit), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), ); } } ``` -[fix has landed]: {{site.repo.flutter}}/pull/50372 [`FocusNode`]: {{site.api}}/flutter/widgets/FocusNode-class.html [Forms]: /cookbook/forms -[flutter/flutter@bf551a3]: {{site.repo.flutter}}/commit/bf551a31fe7ef45c854a219686b6837400bfd94c -[Issue 52221]: {{site.repo.flutter}}/issues/52221 [`requestFocus()`]: {{site.api}}/flutter/widgets/FocusNode/requestFocus.html -[workaround]: {{site.repo.flutter}}/issues/52221#issuecomment-598244655 diff --git a/sites/docs/src/content/cookbook/forms/retrieve-input.md b/sites/docs/src/content/cookbook/forms/retrieve-input.md index 1c5f9f00961..9ee73f5fee6 100644 --- a/sites/docs/src/content/cookbook/forms/retrieve-input.md +++ b/sites/docs/src/content/cookbook/forms/retrieve-input.md @@ -9,9 +9,9 @@ In this recipe, learn how to retrieve the text a user has entered into a text field using the following steps: - 1. Create a `TextEditingController`. - 2. Supply the `TextEditingController` to a `TextField`. - 3. Display the current value of the text field. +1. Create a `TextEditingController`. +1. Supply the `TextEditingController` to a `TextField`. +1. Display the current value of the text field. ## 1. Create a `TextEditingController` @@ -20,9 +20,9 @@ create a [`TextEditingController`][] and supply it to a `TextField` or `TextFormField`. :::important -Call `dispose` of the `TextEditingController` when -you've finished using it. This ensures that you discard any resources -used by the object. +Call `dispose()` on the `TextEditingController` when +you've finished using it. +This ensures that you discard any resources used by the object. ::: @@ -69,9 +69,9 @@ return TextField(controller: myController); ## 3. Display the current value of the text field After supplying the `TextEditingController` to the text field, -begin reading values. Use the [`text`][] -property provided by the `TextEditingController` to retrieve the -String that the user has entered into the text field. +begin reading values. +Use the [`text`][] property provided by the `TextEditingController` +to retrieve the `String` that the user has entered into the text field. The following code displays an alert dialog with the current value of the text field when the user taps a floating action button. @@ -156,7 +156,7 @@ class _MyCustomFormState extends State { context: context, builder: (context) { return AlertDialog( - // Retrieve the text the that user has entered by using the + // Retrieve the text that the user has entered by using the // TextEditingController. content: Text(myController.text), ); @@ -172,7 +172,7 @@ class _MyCustomFormState extends State { ``` diff --git a/sites/docs/src/content/cookbook/forms/text-field-changes.md b/sites/docs/src/content/cookbook/forms/text-field-changes.md index 087b7f179e8..ba75e3123fb 100644 --- a/sites/docs/src/content/cookbook/forms/text-field-changes.md +++ b/sites/docs/src/content/cookbook/forms/text-field-changes.md @@ -13,8 +13,8 @@ results as the user types. How do you run a callback function every time the text changes? With Flutter, you have two options: - 1. Supply an `onChanged()` callback to a `TextField` or a `TextFormField`. - 2. Use a `TextEditingController`. +1. Supply an `onChanged()` callback to a `TextField` or a `TextFormField`. +1. Use a `TextEditingController`. ## 1. Supply an `onChanged()` callback to a `TextField` or a `TextFormField` @@ -25,10 +25,10 @@ Whenever the text changes, the callback is invoked. In this example, print the current value and length of the text field to the console every time the text changes. -It's important to use [characters][] when dealing with user input, -as text may contain complex characters. +It's important to use [`characters`][characters] when dealing with user input, +as text might contain complex characters. This ensures that every character is counted correctly -as they appear to the user. +as it appears to the user. ```dart @@ -39,6 +39,10 @@ TextField( ), ``` + + ## 2. Use a `TextEditingController` A more powerful, but more elaborate approach, is to supply a @@ -48,10 +52,10 @@ property of the `TextField` or a `TextFormField`. To be notified when the text changes, listen to the controller using the [`addListener()`][] method using the following steps: - 1. Create a `TextEditingController`. - 2. Connect the `TextEditingController` to a text field. - 3. Create a function to print the latest value. - 4. Listen to the controller for changes. +1. Create a `TextEditingController`. +1. Connect the `TextEditingController` to a text field. +1. Create a function to print the latest value. +1. Listen to the controller for changes. ### Create a `TextEditingController` @@ -166,7 +170,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - title: 'Retrieve Text Input', + title: 'Handle Text Field Changes', home: MyCustomForm(), ); } @@ -211,7 +215,7 @@ class _MyCustomFormState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Retrieve Text Input')), + appBar: AppBar(title: const Text('Handle Text Field Changes')), body: Padding( padding: const EdgeInsets.all(16), child: Column( diff --git a/sites/docs/src/content/cookbook/forms/text-input.md b/sites/docs/src/content/cookbook/forms/text-input.md index a48115b13fe..85bb2107737 100644 --- a/sites/docs/src/content/cookbook/forms/text-input.md +++ b/sites/docs/src/content/cookbook/forms/text-input.md @@ -10,7 +10,7 @@ They are used to build forms, send messages, create search experiences, and more. In this recipe, explore how to create and style text fields. -Flutter provides two text fields: +Flutter provides two Material Design text fields: [`TextField`][] and [`TextFormField`][]. ## `TextField` @@ -23,7 +23,7 @@ You can add a label, icon, inline hint text, and error text by supplying an property of the `TextField`. To remove the decoration entirely (including the underline and the space reserved for the label), -set the `decoration` to null. +set the `decoration` to `null`. ```dart @@ -35,8 +35,12 @@ TextField( ), ``` + + To retrieve the value when it changes, -see the [Handle changes to a text field][] recipe. +consult the [Handle changes to a text field][] recipe. ## `TextFormField` @@ -58,7 +62,7 @@ TextFormField( ## Interactive example - + ```dartpad title="Flutter text input hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; @@ -87,7 +91,7 @@ class MyCustomForm extends StatelessWidget { Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), child: TextField( @@ -112,15 +116,15 @@ class MyCustomForm extends StatelessWidget { } ``` -For more information on input validation, see the -[Building a form with validation][] recipe. +To learn more about input validation, +consult the [Build a form with validation][] recipe. -[Building a form with validation]: /cookbook/forms/validation/ +[Build a form with validation]: /cookbook/forms/validation [`decoration`]: {{site.material_ui}}/TextField/decoration.html [`Form`]: {{site.api}}/flutter/widgets/Form-class.html [`FormField`]: {{site.api}}/flutter/widgets/FormField-class.html -[Handle changes to a text field]: /cookbook/forms/text-field-changes/ +[Handle changes to a text field]: /cookbook/forms/text-field-changes [`InputDecoration`]: {{site.material_ui}}/InputDecoration-class.html [`TextField`]: {{site.material_ui}}/TextField-class.html [`TextFormField`]: {{site.material_ui}}/TextFormField-class.html diff --git a/sites/docs/src/content/cookbook/forms/validation.md b/sites/docs/src/content/cookbook/forms/validation.md index 4866c385fc6..d1d5f658079 100644 --- a/sites/docs/src/content/cookbook/forms/validation.md +++ b/sites/docs/src/content/cookbook/forms/validation.md @@ -18,9 +18,9 @@ wrong. In this example, learn how to add validation to a form that has a single text field using the following steps: - 1. Create a `Form` with a `GlobalKey`. - 2. Add a `TextFormField` with validation logic. - 3. Create a button to validate and submit the form. +1. Create a `Form` with a `GlobalKey`. +1. Add a `TextFormField` with validation logic. +1. Create a button to validate and submit the form. ## 1. Create a `Form` with a `GlobalKey` @@ -36,7 +36,7 @@ Create the form as a `StatefulWidget`. This allows you to create a unique `GlobalKey()` once. You can then store it as a variable and access it at different points. -If you made this a `StatelessWidget`, you'd need to store this key *somewhere*. +If you made this a `StatelessWidget`, you'd need to store this key _somewhere_. As it is resource expensive, you wouldn't want to generate a new `GlobalKey` each time you run the `build` method. @@ -49,19 +49,17 @@ class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override - MyCustomFormState createState() { - return MyCustomFormState(); - } + State createState() => _MyCustomFormState(); } // Define a corresponding State class. // This class holds data related to the form. -class MyCustomFormState extends State { +class _MyCustomFormState extends State { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a `GlobalKey`, - // not a GlobalKey. + // not a GlobalKey<_MyCustomFormState>. final _formKey = GlobalKey(); @override @@ -70,7 +68,7 @@ class MyCustomFormState extends State { return Form( key: _formKey, child: const Column( - children: [ + children: [ // Add TextFormFields and ElevatedButton here. ], ), @@ -91,14 +89,14 @@ access the form within nested widgets. Although the `Form` is in place, it doesn't have a way for users to enter text. That's the job of a [`TextFormField`][]. -The `TextFormField` widget renders a material design text field +The `TextFormField` widget renders a Material Design text field and can display validation errors when they occur. Validate the input by providing a `validator()` function to the `TextFormField`. If the user's input isn't valid, the `validator` function returns a `String` containing an error message. -If there are no errors, the validator must return null. +If there are no errors, the validator must return `null`. For this example, create a `validator` that ensures the `TextFormField` isn't empty. If it is empty, @@ -117,6 +115,13 @@ TextFormField( ), ``` +:::tip +To validate fields automatically as the user interacts with them +or when a field loses focus, configure the [`autovalidateMode`][] +property on your `Form` or `TextFormField` using [`AutovalidateMode`][] +(such as `AutovalidateMode.onUserInteraction` or `AutovalidateMode.onUnfocus`). +::: + ## 3. Create a button to validate and submit the form Now that you have a form with a text field, @@ -187,19 +192,17 @@ class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override - MyCustomFormState createState() { - return MyCustomFormState(); - } + State createState() => _MyCustomFormState(); } // Create a corresponding State class. // This class holds data related to the form. -class MyCustomFormState extends State { +class _MyCustomFormState extends State { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a GlobalKey, - // not a GlobalKey. + // not a GlobalKey<_MyCustomFormState>. final _formKey = GlobalKey(); @override @@ -243,13 +246,15 @@ class MyCustomFormState extends State { ``` -To learn how to retrieve these values, check out the +To learn how to retrieve these values, consult the [Retrieve the value of a text field][] recipe. +[`AutovalidateMode`]: {{site.api}}/flutter/widgets/AutovalidateMode.html +[`autovalidateMode`]: {{site.api}}/flutter/widgets/Form/autovalidateMode.html [Retrieve the value of a text field]: /cookbook/forms/retrieve-input [`Form`]: {{site.api}}/flutter/widgets/Form-class.html [`Form.of()`]: {{site.api}}/flutter/widgets/Form/of.html diff --git a/sites/docs/web/assets/images/docs/cookbook/focus.png b/sites/docs/web/assets/images/docs/cookbook/focus.png new file mode 100644 index 00000000000..762606be140 Binary files /dev/null and b/sites/docs/web/assets/images/docs/cookbook/focus.png differ diff --git a/sites/docs/web/assets/images/docs/cookbook/focus.webp b/sites/docs/web/assets/images/docs/cookbook/focus.webp deleted file mode 100644 index 2a634503d16..00000000000 Binary files a/sites/docs/web/assets/images/docs/cookbook/focus.webp and /dev/null differ diff --git a/sites/docs/web/assets/images/docs/cookbook/form-validation.png b/sites/docs/web/assets/images/docs/cookbook/form-validation.png new file mode 100644 index 00000000000..60dbec6e16e Binary files /dev/null and b/sites/docs/web/assets/images/docs/cookbook/form-validation.png differ diff --git a/sites/docs/web/assets/images/docs/cookbook/form-validation.webp b/sites/docs/web/assets/images/docs/cookbook/form-validation.webp deleted file mode 100644 index fdaaff5aced..00000000000 Binary files a/sites/docs/web/assets/images/docs/cookbook/form-validation.webp and /dev/null differ diff --git a/sites/docs/web/assets/images/docs/cookbook/retrieve-input.png b/sites/docs/web/assets/images/docs/cookbook/retrieve-input.png new file mode 100644 index 00000000000..e9208d0d1fe Binary files /dev/null and b/sites/docs/web/assets/images/docs/cookbook/retrieve-input.png differ diff --git a/sites/docs/web/assets/images/docs/cookbook/retrieve-input.webp b/sites/docs/web/assets/images/docs/cookbook/retrieve-input.webp deleted file mode 100644 index 440a466b992..00000000000 Binary files a/sites/docs/web/assets/images/docs/cookbook/retrieve-input.webp and /dev/null differ diff --git a/sites/docs/web/assets/images/docs/cookbook/text-field-changes.png b/sites/docs/web/assets/images/docs/cookbook/text-field-changes.png new file mode 100644 index 00000000000..5067d7040c8 Binary files /dev/null and b/sites/docs/web/assets/images/docs/cookbook/text-field-changes.png differ diff --git a/sites/docs/web/assets/images/docs/cookbook/text-input.png b/sites/docs/web/assets/images/docs/cookbook/text-input.png new file mode 100644 index 00000000000..97fed598fce Binary files /dev/null and b/sites/docs/web/assets/images/docs/cookbook/text-input.png differ