{"id":13826277,"url":"https://github.com/joanpablo/reactive_forms","last_synced_at":"2026-02-23T06:39:44.600Z","repository":{"id":37272763,"uuid":"274817325","full_name":"joanpablo/reactive_forms","owner":"joanpablo","description":"This is a model-driven approach to handling form inputs and validations, heavily inspired in Angular's Reactive Forms","archived":false,"fork":false,"pushed_at":"2025-12-22T19:40:59.000Z","size":2557,"stargazers_count":494,"open_issues_count":106,"forks_count":104,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-12-24T07:46:54.938Z","etag":null,"topics":["dart","flutter","form","forms","reactive"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joanpablo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-06-25T03:00:13.000Z","updated_at":"2025-12-22T19:41:02.000Z","dependencies_parsed_at":"2023-02-13T00:16:06.755Z","dependency_job_id":"e835690c-52c9-4e6b-9419-1fb3882abe83","html_url":"https://github.com/joanpablo/reactive_forms","commit_stats":{"total_commits":849,"total_committers":22,"mean_commits":38.59090909090909,"dds":"0.11778563015312127","last_synced_commit":"31e30189720e350c3e1872304bafa68a8275ade5"},"previous_names":[],"tags_count":114,"template":false,"template_full_name":null,"purl":"pkg:github/joanpablo/reactive_forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanpablo%2Freactive_forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanpablo%2Freactive_forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanpablo%2Freactive_forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanpablo%2Freactive_forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joanpablo","download_url":"https://codeload.github.com/joanpablo/reactive_forms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanpablo%2Freactive_forms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29739024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-23T04:51:08.365Z","status":"ssl_error","status_checked_at":"2026-02-23T04:49:15.865Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","flutter","form","forms","reactive"],"created_at":"2024-08-04T09:01:34.963Z","updated_at":"2026-02-23T06:39:44.577Z","avatar_url":"https://github.com/joanpablo.png","language":"Dart","readme":"# Reactive Forms\n\nThis is a model-driven approach to handling form inputs and validations, heavily inspired by [Angular's Reactive Forms](https://angular.io/guide/reactive-forms).\n\n[![Pub Version](https://img.shields.io/pub/v/reactive_forms)](https://pub.dev/packages/reactive_forms) ![GitHub](https://img.shields.io/github/license/joanpablo/reactive_forms) ![GitHub top language](https://img.shields.io/github/languages/top/joanpablo/reactive_forms) ![flutter tests](https://github.com/joanpablo/reactive_forms/workflows/reactive_forms/badge.svg?branch=master) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/a4e40d632feb41b5af624cbd36064c83)](https://www.codacy.com/manual/joanpablo/reactive_forms?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=joanpablo/reactive_forms\u0026utm_campaign=Badge_Grade) [![codecov](https://codecov.io/gh/joanpablo/reactive_forms/branch/master/graph/badge.svg)](https://codecov.io/gh/joanpablo/reactive_forms)\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n  - [Minimum Requirements](#minimum-requirements)\n  - [Installation and Usage](#installation-and-usage)\n- [Creating a Form](#creating-a-form)\n- [Getting/Setting Form Data](#how-to-getset-form-data)\n- [Validators](#what-about-validators)\n  - [Predefined Validators](#predefined-validators)\n  - [Custom Validators](#custom-validators)\n  - [Pattern Validator](#pattern-validator)\n  - [FormGroup Validators](#formgroup-validators)\n  - [Password and Password Confirmation](#what-about-password-and-password-confirmation)\n  - [Asynchronous Validators](#asynchronous-validators-sunglasses)\n  - [Debounce Time in Async Validators](#debounce-time-in-async-validators)\n  - [Composing Validators](#composing-validators)\n- [Groups of Groups](#groups-of-groups-grin)\n- [Dynamic Forms with FormArray](#dynamic-forms-with-formarray)\n- [Arrays of Groups](#arrays-of-groups)\n- [FormBuilder](#formbuilder)\n  - [Groups](#groups)\n  - [Arrays](#arrays)\n  - [Control](#control)\n  - [Control State](#control-state)\n- [Reactive Form Widgets](#reactive-form-widgets)\n- [How to Customize Error Messages](#how-to-customize-error-messages)\n  - [Reactive Widget Level](#1-reactive-widget-level)\n  - [Global/Application Level](#2-globalapplication-level)\n  - [Parameterized Validation Messages](#parameterized-validation-messages)\n- [When do Validation Messages Appear?](#when-does-validation-messages-begin-to-show-up)\n  - [Touching a Control](#touching-a-control)\n  - [Overriding Reactive Widgets Show Errors Behavior](#overriding-reactive-widgets-show-errors-behavior)\n- [Enable/Disable Submit Button](#enabledisable-submit-button)\n  - [Submit Button in a Separate Widget](#separating-submit-button-in-a-different-widget)\n  - [ReactiveFormConsumer Widget](#using-reactiveformconsumer-widget)\n- [Focus/Unfocus a FormControl](#focusunfocus-a-formcontrol)\n- [Focus Flow Between Text Fields](#focus-flow-between-text-fields)\n- [Enable/Disable a Widget](#how-enabledisable-a-widget)\n- [How does ReactiveTextField differ from native TextFormField or TextField?](#how-does-reactivetextfield-differs-from-native-textformfieldhttpsapiflutterdevfluttermaterialtextformfield-classhtml-or-textfieldhttpsapiflutterdevfluttermaterialtextfield-classhtml)\n- [Reactive Form Field Widgets](#supported-reactive-form-field-widgets)\n- [Bonus Field Widgets](#bonus-field-widgets)\n- [Other Reactive Forms Widgets](#other-reactive-forms-widgets)\n- [Advanced Reactive Field Widgets](#advanced-reactive-field-widgets)\n- [ReactiveValueListenableBuilder for Listening to Value Changes in a FormControl](#reactivevaluelistenablebuilder-to-listen-when-value-changes-in-a-formcontrol)\n- [ReactiveForm vs. ReactiveFormBuilder: Which to Choose?](#reactiveform-vs-reactiveformbuilder-which-one)\n- [Reactive Forms + Provider Plugin](#reactive-forms--providerhttpspubdevpackagesprovider-plugin-muscle)\n- [Reactive Forms + Code Generation Plugin](#reactive-forms--code-generationhttpspubdevpackagesreactive_forms_generator-)\n- [How to Create a Custom Reactive Widget](#how-create-a-custom-reactive-widget)\n- [What Reactive Forms is Not](#what-is-not-reactive-forms)\n- [What Reactive Forms Is](#what-is-reactive-forms)\n- [Migration Versions](#migrate-versions)\n\n## Getting Started\n\nFor help getting started with Flutter, view the\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n\n## Minimum Requirements\n\n- Dart SDK: ^3.7.0\n- Flutter: \"\u003e=3.29.0\"\n\n\u003e For using **Reactive Forms** in projects below Flutter 2.8.0, please use version \u003c= 10.7.0 of\n\u003e **Reactive Forms**.\n\n\u003e For using **Reactive Forms** in projects below Flutter 2.2.0, please use version \u003c= 10.2.0 of\n\u003e **Reactive Forms**.\n\n\u003e For using **Reactive Forms** in projects with Flutter 1.17.0, please use version 7.6.3 of\n\u003e **Reactive Forms**.\n\n\u003e **Reactive Forms v8.x** includes the **intl** package. If a version conflict is present, you should use [**dependency_overrides**](https://dart.dev/tools/pub/dependencies#dependency-overrides) to temporarily override all references to **intl** and set the one that better fits your needs.\n\n## Installation and Usage\n\nOnce you're familiar with Flutter, you can install this package by adding `reactive_forms` to the dependencies list\nof your `pubspec.yaml` file as follows:\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n\n  reactive_forms: ^18.1.1\n```\n\nThen, run the command `flutter packages get` in the console.\n\n## Creating a Form\n\nA _form_ is composed of multiple fields or _controls_.\n\nTo declare a form with the fields _name_ and _email_, it's as simple as:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(value: 'John Doe'),\n  'email': FormControl\u003cString\u003e(),\n});\n```\n\n## Default Values\n\nNotice in the example above that for the _name_ field, we have set a default value. For the _email_ field, the default value is **null**.\n\n## How to Get/Set Form Data\n\nGiven the **FormGroup**:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(value: 'John Doe'),\n  'email': FormControl\u003cString\u003e(value: 'johndoe@email.com'),\n});\n```\n\nYou can get the value of a single **FormControl** as simply as:\n\n```dart\nString get name =\u003e this.form.control('name').value;\n```\n\nYou can also get the complete _Form_ data as follows:\n\n```dart\nprint(form.value);\n```\n\nThe previous code prints the following output:\n\n```json\n{\n  \"name\": \"John Doe\",\n  \"email\": \"johndoe@email.com\"\n}\n```\n\n\u003e **FormGroup.value** returns an instance of **Map\u003cString, dynamic\u003e** with each field and its value.\n\nTo set values to controls, you can use two approaches:\n\n```dart\n// Set the value directly to the control\nthis.form.control('name').value = 'John';\n\n// Set values to controls by setting the value to the form\nthis.form.value = {\n  'name': 'John',\n  'email': 'john@email.com',\n};\n```\n\n## What about Validators?\n\nYou can add validators to a **FormControl** as follows:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(validators: [Validators.required]),\n  'email': FormControl\u003cString\u003e(validators: [\n    Validators.required,\n    Validators.email,\n  ]),\n});\n```\n\n\u003e If at least one **FormControl** is **invalid**, then the FormGroup is **invalid**.\n\nThere are common predefined validators, but you can also implement custom validators.\n\n### Predefined Validators\n\n#### FormControl\n\n- Validators.required\n- Validators.requiredTrue\n- Validators.email\n- Validators.number\n- Validators.min\n- Validators.max\n- Validators.minLength\n- Validators.maxLength\n- Validators.pattern\n- Validators.creditCard\n- Validators.equals\n- Validators.compose\n- Validators.composeOR\n- Validators.any\n- Validators.contains\n- Validators.oneOf\n\n#### FormGroup\n\n- Validators.mustMatch\n- Validators.compare\n\n#### FormArray\n\n- Validators.minLength\n- Validators.maxLength\n- Validators.any\n- Validators.contains\n\n### Custom Validators\n\nAll validators are instances of classes that inherit from the `Validator` abstract class.\nTo implement a custom validator, you can follow two different approaches:\n\n1. Extend the `Validator` class and override the `validate` method.\n2. Implement a custom validator function/method and use it with the `Validators.delegate(...)` validator.\n\nLet's implement a custom validator that validates that a control's value must be `true`:\n\n### Inheriting from the `Validator` class:\n\nLet's create a class that extends `Validator` and overrides the `validate` method:\n\n```dart\n/// Validator that validates the control's value must be `true`.\nclass RequiredTrueValidator extends Validator\u003cdynamic\u003e {\n  const RequiredTrueValidator() : super();\n\n  @override\n  Map\u003cString, dynamic\u003e? validate(AbstractControl\u003cdynamic\u003e control) {\n    return control.isNotNull \u0026\u0026\n           control.value is bool \u0026\u0026\n           control.value == true\n    ? null\n    : {'requiredTrue': true};\n  }\n}\n```\n\nThe `validate` method is a function that receives the _control_ to validate and returns a `Map`. If the value of the _control_ is valid, the function returns `null`; otherwise, it returns a `Map` with the error key and custom information. In the previous example, we defined `requiredTrue` as the error key and `true` as the custom information.\n\nTo use the new validator class, we provide an instance of it in the FormControl definition.\n\n```dart\nfinal form = FormGroup({\n  'acceptLicense': FormControl\u003cbool\u003e(\n    value: false,\n    validators: [\n      RequiredTrueValidator(), // Providing the new custom validator\n    ],\n  ),\n});\n```\n\n### Using the `Validators.delegate()` validator:\n\nSometimes, it's more convenient to implement a custom validator in a separate method/function than in a new class. In that case, it's necessary to use the `Validators.delegate()` validator. It creates a validator that delegates the validation to the external function/method.\n\n```dart\nfinal form = FormGroup({\n  'acceptLicense': FormControl\u003cbool\u003e(\n    value: false,\n    validators: [\n      Validators.delegate(_requiredTrue) // Delegates validation to a custom function\n    ],\n  ),\n});\n```\n\n```dart\n/// Custom function that validates that the control's value must be `true`.\nMap\u003cString, dynamic\u003e? _requiredTrue(AbstractControl\u003cdynamic\u003e control) {\n  return control.isNotNull \u0026\u0026\n         control.value is bool \u0026\u0026\n         control.value == true\n  ? null\n  : {'requiredTrue': true};\n}\n```\n\n\u003e Check the [Migration Guide](https://github.com/joanpablo/reactive_forms/wiki/Migration-Guide/_edit#breaking-changes-in-15x) to learn more about custom validators after version 15.0.0 of the package.\n\n### Pattern Validator\n\n**_Validator.pattern_** is a validator that comes with **Reactive Forms**. Validation using regular expressions has always been a very useful tool to solve validation requirements. Let's see how we can validate American Express card numbers:\n\n\u003e American Express card numbers start with 34 or 37 and have 15 digits.\n\n```dart\nconst americanExpressCardPattern = r'^3[47][0-9]{13}$';\n\nfinal cardNumber = FormControl\u003cString\u003e(\n  validators: [Validators.pattern(americanExpressCardPattern)],\n);\n\ncardNumber.value = '395465465421'; // Not a valid number\n\nexpect(cardNumber.valid, false);\nexpect(cardNumber.hasError('pattern'), true);\n```\n\n\u003e The above code is a Unit Test extracted from **Reactive Forms** tests.\n\nIf we _print_ the value of **FormControl.errors**:\n\n```dart\nprint(cardNumber.errors);\n```\n\nWe will get a _Map_ like this:\n\n```json\n{\n  \"pattern\": {\n    \"requiredPattern\": \"^3[47][0-9]{13}$\",\n    \"actualValue\": 395465465421\n  }\n}\n```\n\n### FormGroup Validators\n\nThere are special validators that can be attached to a **FormGroup**. In the next section, we will see an example of that.\n\n## What about Password and Password Confirmation?\n\nThere are some cases where we want to implement a Form where the validation of one field depends on the value of another. For example, a sign-up form with _email_ and _emailConfirmation_ or _password_ and _passwordConfirmation_.\n\nFor those cases, we can implement a custom validator as a class and attach it to the **FormGroup**. Let's see an example:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(validators: [Validators.required]),\n  'email': FormControl\u003cString\u003e(validators: [Validators.required, Validators.email]),\n  'password': FormControl\u003cString\u003e(validators: [\n    Validators.required,\n    Validators.minLength(8),\n  ]),\n  'passwordConfirmation': FormControl\u003cString\u003e(),\n}, validators: [\n  MustMatchValidator(controlName: 'password', matchingControlName: 'passwordConfirmation')\n]);\n```\n\n\u003e Notice the use of **_Validators.minLength(8)_**\n\nIn the previous code, we added two more fields to the form: _password_ and _passwordConfirmation_. Both fields are required, and the password must be at least 8 characters long.\n\nHowever, the most important thing here is that we have attached a **validator** to the **FormGroup**. This validator is a custom validator, and the implementation is as follows:\n\n```dart\nclass MustMatchValidator extends Validator\u003cdynamic\u003e {\n  final String controlName;\n  final String matchingControlName;\n\n  MustMatchValidator({\n    required this.controlName,\n    required this.matchingControlName,\n  }) : super();\n\n  @override\n  Map\u003cString, dynamic\u003e? validate(AbstractControl\u003cdynamic\u003e control) {\n    final form = control as FormGroup;\n\n    final formControl = form.control(controlName);\n    final matchingFormControl = form.control(matchingControlName);\n\n    if (formControl.value != matchingFormControl.value) {\n      matchingFormControl.setErrors({'mustMatch': true});\n\n      // Force messages to show up as soon as possible\n      matchingFormControl.markAsTouched();\n    } else {\n      matchingFormControl.removeError('mustMatch');\n    }\n\n    return null;\n  }\n}\n```\n\nFortunately, you don't have to implement a custom _must match_ validator because we have already included it in the **reactive_forms** package, so you should reuse it. The previous form definition becomes:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(validators: [Validators.required]),\n  'email': FormControl\u003cString\u003e(validators: [Validators.required, Validators.email]),\n  'emailConfirmation': FormControl\u003cString\u003e(),\n  'password': FormControl\u003cString\u003e(validators: [Validators.required, Validators.minLength(8)]),\n  'passwordConfirmation': FormControl\u003cString\u003e(),\n}, validators: [\n  Validators.mustMatch('email', 'emailConfirmation'),\n  Validators.mustMatch('password', 'passwordConfirmation'),\n]);\n```\n\n## Asynchronous Validators :sunglasses:\n\nSometimes, you want to perform a validation against a remote server. These operations are more time-consuming and need to be done asynchronously.\n\nFor example, you want to validate that the _email_ the user is currently typing in a _registration form_ is unique and not already used in your application. **Asynchronous Validators** are just another tool, so use them wisely.\n\n**Asynchronous Validators** are very similar to their synchronous counterparts, with the following difference:\n\n- The validator function returns a [Future](https://api.dart.dev/stable/dart-async/Future-class.html).\n\nAsynchronous validation executes after synchronous validation and is performed only if the synchronous validation is successful. This check allows forms to avoid potentially expensive async validation processes (such as an HTTP request) if the more basic validation methods have already found invalid input.\n\nAfter asynchronous validation begins, the form control enters a **pending** state. You can inspect the control's `pending` property and use it to give visual feedback about the ongoing validation operation.\n\nCode speaks louder than words, so let's see an example.\n\nLet's implement the previously mentioned example: the user is typing an email in a registration form, and you want to validate that the _email_ is unique in your system. We will implement a _custom async validator_ for that purpose.\n\n```dart\nfinal form = FormGroup({\n  'email': FormControl\u003cString\u003e(\n    validators: [\n      Validators.required, // Traditional required and email validators\n      Validators.email,\n    ],\n    asyncValidators: [\n      UniqueEmailAsyncValidator(), // Custom asynchronous validator :)\n    ],\n  ),\n});\n```\n\nWe have declared a simple **Form** with an email **field** that is _required_ and must have a valid email value. We have also included a custom async validator that will validate if the email is unique. Let's see the implementation of our new async validator:\n\n```dart\n/// Validator that validates the user's email is unique by sending a request to\n/// the server.\nclass UniqueEmailAsyncValidator extends AsyncValidator\u003cdynamic\u003e {\n  @override\n  Future\u003cMap\u003cString, dynamic\u003e?\u003e validate(AbstractControl\u003cdynamic\u003e control) async {\n    final error = {'unique': false};\n\n    final isUniqueEmail = await _getIsUniqueEmail(control.value.toString());\n    if (!isUniqueEmail) {\n      control.markAsTouched();\n      return error;\n    }\n\n    return null;\n  }\n\n  /// Simulates a time-consuming operation (e.g., a server request).\n  Future\u003cbool\u003e _getIsUniqueEmail(String email) {\n    // Simple array that simulates emails stored in the server DB.\n    final storedEmails = ['johndoe@email.com', 'john@email.com'];\n\n    return Future.delayed(\n      const Duration(seconds: 5),\n      () =\u003e !storedEmails.contains(email),\n    );\n  }\n}\n```\n\n\u003e Note the use of **control.markAsTouched()** to force the validation message to show up as soon as possible.\n\nThe previous implementation was a simple validator that receives the **AbstractControl** and returns a [Future](https://api.dart.dev/stable/dart-async/Future-class.html) that completes 5 seconds after its call and performs a simple check: if the _value_ of the _control_ is contained in the _server_ array of emails.\n\n\u003e If you want to see **Async Validators** in action with a **full example** using widgets and animations to provide feedback to the user, we strongly advise you to visit our [Wiki](https://github.com/joanpablo/reactive_forms/wiki/Asynchronous-Validators). We have not included the full example in this README.md file to simplify things here and not to anticipate things that we will see later in this doc.\n\n\u003e The validator `Validators.delegateAsync()` is another way to implement a custom validator. For more reference,\n\u003e check the [Custom Validators](#custom-validators) section.\n\n### Debounce Time in Async Validators\n\nAsynchronous validators have a debounce time that is useful if you want to minimize requests to a remote API. The debounce time is set in milliseconds, and the default value is 250 milliseconds.\n\nYou can set a different debounce time as an optional argument in the **FormControl** constructor.\n\n```dart\nfinal control = FormControl\u003cString\u003e(\n  asyncValidators: [UniqueEmailAsyncValidator()],\n  asyncValidatorsDebounceTime: 1000, // Sets a 1-second debounce time.\n);\n```\n\n### Custom Debounce Time in Async Validators\n\nYou can also specify a custom debounce time for a single async validator. This is useful when you have multiple async validators with different debounce time requirements.\n\n```dart\nfinal control = FormControl\u003cString\u003e(\n  asyncValidators: [\n    Validators.debounced(\n      Validators.delegateAsync((control) async {\n        // Your validation logic here\n        return null;\n      }),\n      500, // Debounce time in milliseconds\n    ),\n  ],\n);\n```\n\n### Custom Debounce Time in delegateAsync Validator\n\nThe `Validators.delegateAsync()` function now accepts an optional `debounceTime` parameter, defaulting to 0. This allows for immediate execution or custom debouncing for asynchronous validation.\n\n```dart\nfinal form = fb.group({\n  'userName': FormControl\u003cString\u003e(\n    asyncValidators: [\n      Validators.delegateAsync((control) async {\n        // Simulate a call to a backend service\n        await Future\u003cvoid\u003e.delayed(Duration(seconds: 1));\n        if (control.value == 'existingUser') {\n          return {'unique': true};\n        }\n        return null;\n      }, debounceTime: 300),\n    ],\n  ),\n});\n```\n\nYou can also use it without a debounce time:\n\n```dart\nfinal form = fb.group({\n  'userName': FormControl\u003cString\u003e(\n    asyncValidators: [\n      Validators.delegateAsync((control) async {\n        // Simulate a call to a backend service\n        await Future\u003cvoid\u003e.delayed(Duration(seconds: 1));\n        if (control.value == 'existingUser') {\n          return {'unique': true};\n        }\n        return null;\n      }), // No debounce time\n    ],\n  ),\n});\n```\n\n## Composing Validators\n\nTo explain what Composing Validators is, let's see an example:\n\nWe want to validate a text field of an authentication form.\nIn this text field, the user can write an **email** or a **phone number**, and we want to make sure that the information is correctly formatted. We must validate that the input is a valid email or a valid phone number.\n\n```dart\nfinal phonePattern = '\u003csome phone regex pattern\u003e';\n\nfinal form = FormGroup({\n  'user': FormControl\u003cString\u003e(\n    validators: [\n      Validators.composeOR([\n        Validators.email,\n        Validators.pattern(phonePattern),\n      ])\n    ],\n  ),\n});\n```\n\n\u003e Note that **Validators.composeOR** receives a collection of validators as an argument and returns a validator.\n\nWith **Validators.composeOR**, we are telling the **FormControl** that **if at least one validator evaluates as VALID, then the control is VALID**. It's not necessary for both validators to evaluate to valid.\n\nAnother example could be to validate multiple Credit Card numbers. In that case, you have several regular expression patterns for each type of credit card. So, the user can introduce a card number, and if the information matches at least one pattern, then the information is considered valid.\n\n```dart\nfinal form = FormGroup({\n  'cardNumber': FormControl\u003cString\u003e(\n    validators: [\n      Validators.composeOR([\n        Validators.pattern(americanExpressCardPattern),\n        Validators.pattern(masterCardPattern),\n        Validators.pattern(visaCardPattern),\n      ])\n    ],\n  ),\n});\n```\n\n### One Of Validator\n\nThe `oneOf` validator is used to validate that the control's value is one of the values in the provided collection. For `String` values, the comparison can be made case-sensitive or insensitive.\n\n```dart\nfinal form = FormGroup({\n  'fruit': FormControl\u003cString\u003e(\n    validators: [\n      Validators.oneOf(['apple', 'banana', 'orange']),\n    ],\n  ),\n});\n```\n\n## Groups of Groups :grin:\n\n**FormGroup** is not restricted to containing only **FormControl**; it can nest other **FormGroups**, so you can create more complex **Forms**.\n\nSuppose you have a _Registration Wizard_ with several screens. Each screen collects specific information, and at the end, you want to collect all that information as one piece of data:\n\n```dart\nfinal form = FormGroup({\n  'personal': FormGroup({\n    'name': FormControl\u003cString\u003e(validators: [Validators.required]),\n    'email': FormControl\u003cString\u003e(validators: [Validators.required]),\n  }),\n  'phone': FormGroup({\n    'phoneNumber': FormControl\u003cString\u003e(validators: [Validators.required]),\n    'countryIso': FormControl\u003cString\u003e(validators: [Validators.required]),\n  }),\n  'address': FormGroup({\n    'street': FormControl\u003cString\u003e(validators: [Validators.required]),\n    'city': FormControl\u003cString\u003e(validators: [Validators.required]),\n    'zip': FormControl\u003cString\u003e(validators: [Validators.required]),\n  }),\n});\n```\n\n\u003e Note how we have set the _data type_ to a **FormControl**. Although this is not mandatory when\n\u003e declaring a _Form_, we highly recommend this syntax as a good practice or to use the FormBuilder\n\u003e syntax.\n\nUsing **FormBuilder** _(read the FormBuilder section below)_:\n\n```dart\nfinal form = fb.group({\n  'personal': fb.group({\n    'name': ['', Validators.required],\n    'email': ['', Validators.required],\n  }),\n  'phone': fb.group({\n    'phoneNumber': ['', Validators.required],\n    'countryIso': ['', Validators.required],\n  }),\n  'address': fb.group({\n    'street': ['', Validators.required],\n    'city': ['', Validators.required],\n    'zip': ['', Validators.required],\n  }),\n});\n```\n\nYou can collect all data using **FormGroup.value**:\n\n```dart\nvoid _printFormData(FormGroup form) {\n  print(form.value);\n}\n```\n\nThe previous method outputs a _Map_ like the following one:\n\n```json\n{\n  \"personal\": {\n    \"name\": \"...\",\n    \"email\": \"...\"\n  },\n  \"phone\": {\n    \"phoneNumber\": \"...\",\n    \"countryIso\": \"...\"\n  },\n  \"address\": {\n    \"street\": \"...\",\n    \"city\": \"...\",\n    \"zip\": \"...\"\n  }\n}\n```\n\nAnd of course, you can access a nested **FormGroup** as follows:\n\n```dart\nFormGroup personalForm = form.control('personal');\n```\n\nA simple way to create a wizard is, for example, to wrap a [PageView](https://api.flutter.dev/flutter/widgets/PageView-class.html) within a **ReactiveForm**, and each _Page_ inside the [PageView](https://api.flutter.dev/flutter/widgets/PageView-class.html) can contain a **ReactiveForm** to collect specific data.\n\n## Dynamic Forms with **FormArray**\n\nFormArray is an alternative to **FormGroup** for managing any number of unnamed controls. As with **FormGroup** instances, you can dynamically insert and remove controls from **FormArray** instances, and the form array instance value and validation status are calculated from its child controls.\n\nYou don't need to define a _key_ for each control by _name_, so this is a great option if you don't know the number of child values in advance.\n\nLet's see a simple example:\n\n```dart\nfinal form = FormGroup({\n  'emails': FormArray\u003cString\u003e([]), // An empty array of emails\n});\n```\n\nWe have defined just an empty array. Let's define another array with two controls:\n\n```dart\nfinal form = FormGroup({\n  'emails': FormArray\u003cString\u003e([\n    FormControl\u003cString\u003e(value: 'john@email.com'),\n    FormControl\u003cString\u003e(value: 'susan@email.com'),\n  ]),\n});\n```\n\n\u003e Note that you don't have to specify the name of the controls inside the array.\n\nIf we output the _value_ of the previous form group, we will get something like this:\n\n```dart\nprint(form.value);\n```\n\n```json\n{\n  \"emails\": [\"john@email.com\", \"susan@email.com\"]\n}\n```\n\nLet's dynamically add another control:\n\n```dart\nfinal array = form.control('emails') as FormArray\u003cString\u003e;\n\n// Adding another email\narray.add(\n  FormControl\u003cString\u003e(value: 'caroline@email.com'),\n);\n\nprint(form.value);\n```\n\n```json\n{\n  \"emails\": [\"john@email.com\", \"susan@email.com\", \"caroline@email.com\"]\n}\n```\n\nAnother way to add controls is to assign values directly to the array:\n\n```dart\n// Given: an empty array of strings\nfinal array = FormArray\u003cString\u003e([]);\n\n// When: set value to the array\narray.value = [\"john@email.com\", \"susan@email.com\", \"caroline@email.com\"];\n\n// Then: the array is no longer empty\nexpect(array.controls.length, 3);\n\n// And: the array has a control for each inserted value\nexpect(array.controls('0').value, \"john@email.com\");\nexpect(array.controls('1').value, \"susan@email.com\");\nexpect(array.controls('2').value, \"caroline@email.com\");\n```\n\n\u003e To get a control from the array, you must pass the index position as a _String_. This is because **FormGroup** and **FormArray** inherit from the same parent class, and **FormControl** gets the controls by name (String).\n\nA more advanced example:\n\n```dart\n// An array of contacts\nfinal contacts = ['john@email.com', 'susan@email.com', 'caroline@email.com'];\n\n// A form with a list of selected emails\nfinal form = FormGroup({\n  'selectedEmails': FormArray\u003cbool\u003e([], // An empty array of controls\n    validators: [emptyAddressee], // Validates that at least one email is selected\n  ),\n});\n\n// Get the array of controls\nfinal formArray = form.control('selectedEmails') as FormArray\u003cbool\u003e;\n\n// Populates the array of controls.\n// For each contact, add a boolean form control to the array.\nformArray.addAll(\n  contacts.map((email) =\u003e FormControl\u003cbool\u003e(value: true)).toList(),\n);\n```\n\n```dart\n// Validates that at least one email is selected\nMap\u003cString, dynamic\u003e emptyAddressee(AbstractControl control) {\n  final emails = (control as FormArray\u003cbool\u003e).value;\n  return emails.any((isSelected) =\u003e isSelected)\n      ? null\n      : {'emptyAddressee': true};\n}\n```\n\n## Arrays of Groups\n\nYou can also create arrays of groups:\n\n```dart\n// An array of groups\nfinal addressArray = FormArray([\n  FormGroup({\n    'city': FormControl\u003cString\u003e(value: 'Sofia'),\n    'zipCode': FormControl\u003cint\u003e(value: 1000),\n  }),\n  FormGroup({\n    'city': FormControl\u003cString\u003e(value: 'Havana'),\n    'zipCode': FormControl\u003cint\u003e(value: 10400),\n  }),\n]);\n```\n\nAnother example using **FormBuilder**:\n\n```dart\n// An array of groups using FormBuilder\nfinal addressArray = fb.array([\n  fb.group({'city': 'Sofia', 'zipCode': 1000}),\n  fb.group({'city': 'Havana', 'zipCode': 10400}),\n]);\n```\n\nor just:\n\n```dart\n// An array of groups using a very simple syntax\nfinal addressArray = fb.array([\n  {'city': 'Sofia', 'zipCode': 1000},\n  {'city': 'Havana', 'zipCode': 10400},\n]);\n```\n\nYou can iterate over groups as follows:\n\n```dart\nfinal cities = addressArray.controls\n        .map((control) =\u003e control as FormGroup)\n        .map((form) =\u003e form.control('city').value)\n        .toList();\n```\n\n\u003e A common mistake is to declare an _array_ of groups as _FormArray\u003cFormGroup\u003e_.\n\u003e An array of _FormGroup_ must be declared as **FormArray()** or as **FormArray\u003cMap\u003cString, dynamic\u003e\u003e()**.\n\n## FormBuilder\n\nThe **FormBuilder** provides syntactic sugar that shortens the creation of instances of a FormGroup, FormArray, and FormControl. It reduces the amount of boilerplate needed to build complex forms.\n\n### Groups\n\n```dart\n// Creates a group\nfinal form = fb.group({\n  'name': 'John Doe',\n  'email': ['', Validators.required, Validators.email],\n  'password': Validators.required,\n});\n```\n\nThe previous code is equivalent to the following:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(value: 'John Doe'),\n  'email': FormControl\u003cString\u003e(value: '', validators: [Validators.required, Validators.email]),\n  'password': FormControl\u003cString\u003e(validators: [Validators.required]),\n});\n```\n\n### Arrays\n\n```dart\n// Creates an array\nfinal aliases = fb.array(['john', 'little john']);\n```\n\n### Control\n\n```dart\n// Creates a control of type String with a required validator\nfinal control = fb.control\u003cString\u003e('', [Validators.required]);\n```\n\n### Control State\n\n```dart\n// Create a group\nfinal group = fb.group(\n  // Creates a control with a default value and disabled state\n  'name': fb.state(value: 'john', disabled: true),\n);\n```\n\n## Nested Controls\n\nTo retrieve nested controls, you can specify the name of the control as a dot-delimited string that defines the path to the control:\n\n```dart\nfinal form = FormGroup({\n  'address': FormGroup({\n    'city': FormControl\u003cString\u003e(value: 'Sofia'),\n    'zipCode': FormControl\u003cint\u003e(value: 1000),\n  }),\n});\n\n// Get nested control value\nfinal city = form.control('address.city');\n\nprint(city.value); // Outputs: Sofia\n```\n\n## Reactive Form Widgets\n\nSo far, we have only defined our model-driven form, but how do we bind the form definition with our Flutter widgets? Reactive Form Widgets are the answer.\n\nLet's see an example:\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveTextField(\n          formControlName: 'name',\n        ),\n        ReactiveTextField(\n          formControlName: 'email',\n        ),\n        ReactiveTextField(\n          formControlName: 'password',\n          obscureText: true,\n        ),\n      ],\n    ),\n  );\n}\n```\n\n\u003e The example above ignores the _emailConfirmation_ and _passwordConfirmation_ fields previously seen for simplicity.\n\n## How to Customize Error Messages?\n\nValidation messages can be defined at two different levels:\n\n1. Reactive Widget level.\n2. Global/Application level.\n\n### 1. Reactive Widget Level.\n\nEach reactive widget, like `ReactiveTextField`, `ReactiveDropdownField`, and all others, has the\n`validationMessages` property as an argument of their constructors. To define custom\nvalidation messages at the widget level, just provide the `validationMessages` property with the\ncorresponding text values for each error, as shown below:\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveTextField(\n          formControlName: 'name',\n          validationMessages: {\n            'required': (error) =\u003e 'The name must not be empty'\n          },\n        ),\n        ReactiveTextField(\n          formControlName: 'email',\n          validationMessages: {\n            'required': (error) =\u003e 'The email must not be empty',\n            'email': (error) =\u003e 'The email value must be a valid email'\n          },\n        ),\n        ReactiveTextField(\n          formControlName: 'password',\n          obscureText: true,\n          validationMessages: {\n            'required': (error) =\u003e 'The password must not be empty',\n            'minLength': (error) =\u003e 'The password must have at least 8 characters'\n          },\n        ),\n      ],\n    ),\n  );\n}\n```\n\n\u003e **Reactive Forms** has a utility class called **ValidationMessage** that provides access to\n\u003e common _validation messages_: _required_, _email_, _pattern_, and so on. So instead of writing 'required', you\n\u003e could use _ValidationMessage.required_ as the key of validation messages:\n\u003e\n\u003e ```dart\n\u003e return ReactiveTextField(\n\u003e    formControlName: 'email',\n\u003e    validationMessages: {\n\u003e      ValidationMessage.required: (error) =\u003e 'The email must not be empty',\n\u003e      ValidationMessage.email: (error) =\u003e 'The email value must be a valid email',\n\u003e    },\n\u003e );\n\u003e ```\n\u003e\n\u003e Nice, isn't it? ;)\n\n### 2. Global/Application Level.\n\nYou can also define custom validation messages at a higher level, for example, at the application\nlevel. When a reactive widget looks for an error message text, it first looks at the widget-level\ndefinition. If it doesn't find any config at the widget level, then it looks at the global config\ndefinition.\n\nThe global definition of validation messages allows you to define error messages in a centralized\nway and relieves you from defining validation messages on each reactive widget of your application.\n\nTo define these configs at a higher level, use the widget **ReactiveFormConfig** and\ndefine the `validationMessages`.\n\nHere is an example of the global definition for custom validation messages:\n\n### Validation Messages with Error Arguments:\n\n```dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return ReactiveFormConfig(\n      validationMessages: {\n        ValidationMessage.required: (error) =\u003e 'Field must not be empty',\n        ValidationMessage.email: (error) =\u003e 'Must enter a valid email',\n      },\n      child: MaterialApp(\n        home: Scaffold(\n          body: const Center(\n            child: Text('Hello Flutter Reactive Forms!'),\n          ),\n        ),\n      ),\n    );\n  }\n}\n```\n\n### Parameterized Validation Messages\n\nYou can enrich the validation messages using parameters of the error instance. In the next example,\nwe are giving a more complete validation error to the user:\n\n```dart\nfinal form = FormGroup({\n  'password': FormControl\u003cString\u003e(\n    validators: [Validators.minLength(8)],\n  ),\n});\n```\n\n```dart\nReactiveTextField(\n  formControlName: 'password',\n  validationMessage: {\n    ValidationMessages.minLength: (error) =\u003e\n    'The password must be at least ${(error as Map)['requiredLength']} characters long'\n  },\n)\n```\n\nThis will show the message: `The password must be at least 8 characters long`\n\n## When do Validation Messages Appear?\n\n### Touching a Control\n\nEven when the **FormControl** is invalid, validation messages will begin to show up when the **FormControl** is **touched**. That means when the user taps on the **ReactiveTextField** widget and then removes focus or completes the text editing.\n\nYou can initialize a **FormControl** as **touched** to force the validation messages to show up the very first time the widget builds.\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(\n    value: 'John Doe',\n    validators: [Validators.required],\n    touched: true,\n  ),\n});\n```\n\nWhen you set a _value_ to a **FormControl** from code and want to show validation messages,\nyou must call the _FormControl.markAsTouched()_ method:\n\n```dart\nset name(String newName) {\n  final formControl = this.form.control('name');\n  formControl.value = newName;\n  formControl.markAsTouched(); // If newName is invalid, then validation messages will show up in the UI\n}\n```\n\n\u003e To mark all children controls of a **FormGroup** and **FormArray** as touched, you must call **markAllAsTouched()**.\n\u003e\n\u003e ```dart\n\u003e final form = FormGroup({\n\u003e   'name': FormControl\u003cString\u003e(\n\u003e     value: 'John Doe',\n\u003e     validators: [Validators.required],\n\u003e     touched: true,\n\u003e   ),\n\u003e });\n\u003e\n\u003e // Marks all children as touched\n\u003e form.markAllAsTouched();\n\u003e ```\n\n### Overriding Reactive Widgets Show Errors Behavior\n\nThe second way to customize when to show error messages is to override the **showErrors** method in reactive widgets.\n\nLet's suppose you want to show validation messages not only when it is **invalid** and **touched** (default behavior), but also when it's **dirty**:\n\n```dart\nReactiveTextField(\n  formControlName: 'email',\n  // Override default behavior and show errors when: INVALID, TOUCHED, and DIRTY\n  showErrors: (control) =\u003e control.invalid \u0026\u0026 control.touched \u0026\u0026 control.dirty,\n),\n```\n\n\u003e A control becomes **dirty** when its value changes through the UI.\n\u003e The **setErrors** method of the controls can optionally mark it as dirty too.\n\n## Enable/Disable Submit Button\n\nFor a better User Experience, sometimes we want to enable/disable the _Submit_ button based on the validity of the _Form_. Achieving this behavior, even in a great framework like Flutter, can sometimes be hard and can lead to individual implementations for each _Form_ of the same application, plus boilerplate code.\n\nWe will show you two different approaches to accomplish this very easily:\n\n1. Separating the Submit Button into a different Widget.\n2. Using the **ReactiveFormConsumer** widget.\n\n### Separating Submit Button in a Separate Widget:\n\nLet's add a submit button to our _Form_:\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveTextField(\n          formControlName: 'email',\n        ),\n        ReactiveTextField(\n          formControlName: 'password',\n          obscureText: true,\n        ),\n        MySubmitButton(),\n      ],\n    ),\n  );\n}\n```\n\n\u003e The above is a simple sign-in form with _email_, _password_, and a _submit_ button.\n\nNow let's see the implementation of the **MySubmitButton** widget:\n\n```dart\nclass MySubmitButton extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final form = ReactiveForm.of(context);\n    return RaisedButton(\n      child: Text('Submit'),\n      onPressed: form.valid ? _onPressed : null,\n    );\n  }\n\n  void _onPressed() {\n    print('Hello Reactive Forms!!!');\n  }\n}\n```\n\n\u003e Notice the use of **_ReactiveForm.of(context)_** to get access to the nearest **FormGroup** up the widget's tree.\n\nIn the previous example, we separated the implementation of the _submit_ button into a different widget. The reason behind this is that we want to rebuild the _submit_ button each time the _validity_ of the **FormGroup** changes. We don't want to rebuild the entire _Form_, just the button.\n\nHow is that possible? Well, the answer is in the expression:\n\n```dart\nfinal form = ReactiveForm.of(context);\n```\n\nThe expression above has two important responsibilities:\n\n- Obtains the nearest **FormGroup** up the widget's tree.\n- Registers the current **context** with the changes in the **FormGroup** so that if the validity of the **FormGroup** changes, the current **context** is _rebuilt_.\n\n### Using the **ReactiveFormConsumer** widget:\n\nThe **ReactiveFormConsumer** widget is a wrapper around the **ReactiveForm.of(context)** expression so that we can reimplement the previous example as follows:\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveTextField(\n          formControlName: 'email',\n        ),\n        ReactiveTextField(\n          formControlName: 'password',\n          obscureText: true,\n        ),\n        ReactiveFormConsumer(\n          builder: (context, form, child) {\n            return RaisedButton(\n              child: Text('Submit'),\n              onPressed: form.valid ? _onSubmit : null,\n            );\n          },\n        ),\n      ],\n    ),\n  );\n}\n\nvoid _onSubmit() {\n  print('Hello Reactive Forms!!!');\n}\n```\n\n\u003e It is entirely up to you to decide which of the above two approaches to use, but note that to access the **FormGroup** via **ReactiveForm.of(context)**, the consumer widget must always be down in the tree of the **ReactiveForm** widget.\n\n## Focus/Unfocus a **FormControl**\n\nThere are some cases where we want to add or remove focus on a UI TextField without the user's interaction. For those particular cases, you can use the **FormControl.focus()** or **FormControl.unfocus()** methods.\n\n```dart\nfinal form = fb.group({'name': 'John Doe'});\n\nFormControl control = form.control('name');\n\ncontrol.focus(); // UI text field gets focus, and the device keyboard pops up\n\ncontrol.unfocus(); // UI text field loses focus\n```\n\nYou can also set focus directly from the Form like this:\n\n```dart\nfinal form = fb.group({'name': ''});\n\nform.focus('name'); // UI text field gets focus, and the device keyboard pops up\n```\n\n```dart\nfinal form = fb.group({\n  'person': fb.group({\n    'name': '',\n  }),\n});\n\n// Set focus to a nested control\nform.focus('person.name');\n```\n\n## Focus Flow Between Text Fields\n\nAnother example is when you have a form with several text fields, and each time the user completes editing in one field, you want to request the next focus field using keyboard actions:\n\n```dart\nfinal form = fb.group({\n  'name': ['', Validators.required],\n  'email': ['', Validators.required, Validators.email],\n  'password': ['', Validators.required],\n});\n```\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveTextField(\n          formControlName: 'name',\n          textInputAction: TextInputAction.next,\n          onSubmitted: () =\u003e this.form.focus('email'),\n        ),\n        ReactiveTextField(\n          formControlName: 'email',\n          textInputAction: TextInputAction.next,\n          onSubmitted: () =\u003e this.form.focus('password'),\n        ),\n        ReactiveTextField(\n          formControlName: 'password',\n          obscureText: true,\n        ),\n      ],\n    ),\n  );\n}\n```\n\n\u003e When you remove focus from a control, the control is marked as touched. This means that the validation error messages will show up in the UI. To prevent validation messages from showing up, you can optionally set the **touched** argument to _false_.\n\u003e\n\u003e ```dart\n\u003e // Remove the focus from the control and mark it as untouched.\n\u003e this.form.unfocus(touched: false);\n\u003e ```\n\n## How to Enable/Disable a Widget\n\nTo disable a widget like **ReactiveTextField**, all you need to do is _mark_ the _control_ as disabled:\n\n```dart\nfinal form = FormGroup({\n  'name': FormControl\u003cString\u003e(),\n});\n\nFormControl control = form.control('name');\n\n// The control is disabled, and the widget in the UI is also disabled.\ncontrol.markAsDisabled();\n```\n\n\u003e When a control is disabled, it is exempt from validation checks and excluded from the aggregate\n\u003e value of any parent. Its status is **DISABLED**.\n\u003e\n\u003e To retrieve all values of a FormGroup or FormArray regardless of the disabled status of children, use\n\u003e **FormControl.rawValue** or **FormArray.rawValue**, respectively.\n\n## How does **ReactiveTextField** differ from native [TextFormField](https://api.flutter.dev/flutter/material/TextFormField-class.html) or [TextField](https://api.flutter.dev/flutter/material/TextField-class.html)?\n\n**ReactiveTextField** has more in common with _TextFormField_ than with _TextField_. As we all know, _TextFormField_ is a wrapper around the _TextField_ widget that brings some extra capabilities, such as _Form validations_ with properties like _autovalidate_ and _validator_. In the same way, **ReactiveTextField** is a wrapper around _TextField_ that handles the features of validations in its own different way.\n\n**ReactiveTextField** has all the properties that you can find in a common _TextField_. It can be customized as much as you want, just like a simple _TextField_ or a _TextFormField_. In fact, most of the code was taken from the original TextFormField and ported to have a reactive behavior that binds itself to a **FormControl** in a **two-way** binding.\n\nBelow is an example of how to create some **ReactiveTextField** with some common properties:\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveTextField(\n          formControlName: 'name',\n          decoration: InputDecoration(\n            labelText: 'Name',\n          ),\n          textCapitalization: TextCapitalization.words,\n          textAlign: TextAlign.center,\n          style: TextStyle(backgroundColor: Colors.white),\n        ),\n        ReactiveTextField(\n          formControlName: 'phoneNumber',\n          decoration: InputDecoration(\n            labelText: 'Phone number',\n          ),\n          keyboardType: TextInputType.number,\n        ),\n        ReactiveTextField(\n          formControlName: 'password',\n          obscureText: true,\n          decoration: InputDecoration(\n            labelText: 'Password',\n          ),\n        ),\n      ],\n    ),\n  );\n}\n```\n\n\u003e Because of the **two-way binding** capability of the **ReactiveTextField** with a **FormControl**,\n\u003e the widget **does not** include properties like _controller_, _validator_, _autovalidate_, or _onSaved_.\n\u003e The **FormControl** is responsible for handling validation as well as change\n\u003e notifications.\n\u003e\n\u003e It does include some events like **onChanged**, **onTap**, **onEditingComplete**,\n\u003e and **onSubmitted**.\n\n## Supported Reactive Form Field Widgets\n\n- ReactiveTextField\n- ReactiveDropdownField\n- ReactiveSwitch\n- ReactiveCheckbox\n- ReactiveRadio\n- ReactiveSlider\n- ReactiveCheckboxListTile\n- ReactiveSwitchListTile\n- ReactiveRadioListTile\n\n## Bonus Field Widgets\n\n- ReactiveDatePicker\n- ReactiveTimePicker\n\n## Other Reactive Forms Widgets\n\n- ReactiveForm\n- ReactiveFormConsumer\n- ReactiveFormBuilder\n- ReactiveFormArray\n- ReactiveValueListenableBuilder\n- ReactiveStatusListenableBuilder\n\n## Advanced Reactive Field Widgets\n\nWe are trying to keep `reactive_forms` from bloating with third-party dependencies. This is why there is\na separate library, [`reactive_forms_widgets`](https://pub.dev/packages/reactive_forms_widgets), which is still under construction, that provides\na variety of more advanced field widgets. To know more about how to install it, please visit the library repo and read the documentation about the widgets it contains.\n\n- **[ReactiveAdvancedSwitch](https://pub.dev/packages/reactive_advanced_switch)** - wrapper around [`flutter_advanced_switch`](https://pub.dev/packages/flutter_advanced_switch)\n- **[ReactiveDateRangePicker](https://pub.dev/packages/reactive_date_range_picker)** - wrapper around [showDateRangePicker](https://api.flutter.dev/flutter/material/showDateRangePicker.html)\n- **[ReactiveDateTimePicker](https://pub.dev/packages/reactive_date_time_picker)** - wrapper around [showDatePicker](https://api.flutter.dev/flutter/material/showDatePicker.html) and [showTimePicker](https://api.flutter.dev/flutter/material/showTimePicker.html)\n- **[ReactiveDropdownSearch](https://pub.dev/packages/reactive_dropdown_search)** - wrapper around [`dropdown_search`](https://pub.dev/packages/dropdown_search)\n- **[ReactiveFilePicker](https://pub.dev/packages/reactive_file_picker)** - wrapper around [`file_picker`](https://pub.dev/packages/file_picker)\n- **[ReactiveImagePicker](https://pub.dev/packages/reactive_image_picker)** - wrapper around [`image_picker`](https://pub.dev/packages/image_picker)\n- **[ReactiveMultiImagePicker](https://pub.dev/packages/reactive_multi_image_picker)** - wrapper around [`multi_image_picker`](https://pub.dev/packages/multi_image_picker)\n- **[ReactiveSegmentedControl](https://pub.dev/packages/reactive_segmented_control)** - wrapper around [`CupertinoSegmentedControl`](https://api.flutter.dev/flutter/cupertino/CupertinoSegmentedControl-class.html)\n- **[ReactiveSignature](https://pub.dev/packages/reactive_signature)** - wrapper around [`signature`](https://pub.dev/packages/signature)\n- **[ReactiveTouchSpin](https://pub.dev/packages/reactive_touch_spin)** - wrapper around [`flutter_touch_spin`](https://pub.dev/packages/flutter_touch_spin)\n- **[ReactiveRangeSlider](https://pub.dev/packages/reactive_range_slider)** - wrapper around [`RangeSlider`](https://api.flutter.dev/flutter/material/RangeSlider-class.html)\n- **[ReactiveSleekCircularSlider](https://pub.dev/packages/reactive_sleek_circular_slider)** - wrapper around [`sleek_circular_slider`](https://pub.dev/packages/sleek_circular_slider)\n- **[ReactiveCupertinoTextField](https://pub.dev/packages/reactive_cupertino_text_field)** - wrapper around [`CupertinoTextField`](https://api.flutter.dev/flutter/cupertino/CupertinoTextField-class.html)\n- **[ReactiveRatingBar](https://pub.dev/packages/reactive_flutter_rating_bar)** - wrapper around [`flutter_rating_bar`](https://pub.dev/packages/flutter_rating_bar)\n- **[ReactiveMacosUi](https://pub.dev/packages/reactive_macos_ui)** - wrapper around [`macos_ui`](https://pub.dev/packages/macos_ui)\n- **[ReactivePinPut](https://pub.dev/packages/reactive_pinput)** - wrapper around [`pinput`](https://pub.dev/packages/pinput)\n- **[ReactiveCupertinoSwitch](https://pub.dev/packages/reactive_cupertino_switch)** - wrapper around [`CupertinoSwitch`](https://api.flutter.dev/flutter/cupertino/CupertinoSwitch-class.html)\n- **[ReactivePinCodeTextField](https://pub.dev/packages/reactive_pin_code_fields)** - wrapper around [`pin_code_fields`](https://pub.dev/packages/pin_code_fields)\n- **[ReactiveSlidingSegmentedControl](https://pub.dev/packages/reactive_sliding_segmented)** - wrapper around [`CupertinoSlidingSegmentedControl`](https://api.flutter.dev/flutter/cupertino/CupertinoSlidingSegmentedControl-class.html)\n- **[ReactiveCupertinoSlider](https://pub.dev/packages/reactive_cupertino_slider)** - wrapper around [`CupertinoSlider`](https://api.flutter.dev/flutter/cupertino/CupertinoSlider-class.html)\n- **[ReactiveColorPicker](https://pub.dev/packages/reactive_color_picker)** - wrapper around [`flutter_colorpicker`](https://pub.dev/packages/flutter_colorpicker)\n- **[ReactiveMonthPickerDialog](https://pub.dev/packages/reactive_month_picker_dialog)** - wrapper around [`month_picker_dialog`](https://pub.dev/packages/month_picker_dialog)\n- **[ReactiveRawAutocomplete](https://pub.dev/packages/reactive_raw_autocomplete)** - wrapper around [`RawAutocomplete`](https://api.flutter.dev/flutter/widgets/RawAutocomplete-class.html)\n- **[ReactiveFlutterTypeahead](https://pub.dev/packages/reactive_flutter_typeahead)** - wrapper around [`flutter_typeahead`](https://pub.dev/packages/flutter_typeahead)\n- **[ReactivePinInputTextField](https://pub.dev/packages/reactive_pin_input_text_field)** - wrapper around [`pin_input_text_field`](https://pub.dev/packages/pin_input_text_field)\n- **[ReactiveDirectSelect](https://pub.dev/packages/reactive_direct_select)** - wrapper around [`direct_select`](https://pub.dev/packages/direct_select)\n- **[ReactiveMarkdownEditableTextInput](https://pub.dev/packages/reactive_md_editable_textinput)** - wrapper around [`markdown_editable_textinput`](https://pub.dev/packages/markdown_editable_textinput)\n- **[ReactiveCodeTextField](https://pub.dev/packages/reactive_code_text_field)** - wrapper around [`code_text_field`](https://pub.dev/packages/code_text_field)\n- **[ReactivePhoneFormField](https://pub.dev/packages/reactive_phone_form_field)** - wrapper around [`phone_form_field`](https://pub.dev/packages/phone_form_field)\n- **[ReactiveExtendedTextField](https://pub.dev/packages/reactive_extended_text_field)** - wrapper around [`extended_text_field`](https://pub.dev/packages/extended_text_field)\n- **[ReactiveCupertinoSlidingSegmentedControl](https://pub.dev/packages/reactive_cup_slide_segmented)** - wrapper around [`CupertinoSlidingSegmentedControl`](https://api.flutter.dev/flutter/cupertino/CupertinoSlidingSegmentedControl-class.html)\n\n### ReactiveTextField\n\nWe have explained the common usage of a **ReactiveTextField** throughout this documentation.\n\n### ReactiveDropdownField\n\n**ReactiveDropdownField**, like all the other _reactive field widgets_, is almost the same as its native version, [DropdownButtonFormField](https://api.flutter.dev/flutter/material/DropdownButtonFormField-class.html), but adds two-way binding capabilities. The code is ported from the original native implementation. It has all the capability of styles and themes of the native version.\n\n```dart\nfinal form = FormGroup({\n  'payment': FormControl\u003cint\u003e(validators: [Validators.required]),\n});\n\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveDropdownField\u003cint\u003e(\n          formControlName: 'payment',\n          hint: Text('Select payment...'),\n          items: [\n            DropdownMenuItem(\n              value: 0,\n              child: Text('Free'),\n            ),\n            DropdownMenuItem(\n              value: 1,\n              child: Text('Visa'),\n            ),\n            DropdownMenuItem(\n              value: 2,\n              child: Text('Mastercard'),\n            ),\n            DropdownMenuItem(\n              value: 3,\n              child: Text('PayPal'),\n            ),\n          ],\n        ),\n      ],\n    ),\n  );\n}\n```\n\n\u003e As you can see from the above example, the usage of **ReactiveDropdownField** is almost the same as the usage of a common [DropdownButtonFormField](https://api.flutter.dev/flutter/material/DropdownButtonFormField-class.html), except for the additional _formControlName_ and _validationMessages_ properties.\n\n## **ReactiveValueListenableBuilder** to Listen for Value Changes in a **FormControl**\n\nIf you want to rebuild a widget each time a FormControl value changes, you can use the **ReactiveValueListenableBuilder** widget.\n\nIn the following example, we are listening for changes in _lightIntensity_. We change that value with a **ReactiveSlider** and show the value in a **Text** widget all the time:\n\n```dart\nfinal form = FormGroup({\n  'lightIntensity': FormControl\u003cdouble\u003e(value: 50.0),\n});\n\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: Column(\n      children: \u003cWidget\u003e[\n        ReactiveValueListenableBuilder\u003cdouble\u003e(\n          formControlName: 'lightIntensity',\n          builder: (context, value, child) {\n            return Text('lights at ${value?.toStringAsFixed(2)}%');\n          },\n        ),\n        ReactiveSlider(\n          formControlName: 'lightIntensity',\n          max: 100.0,\n        ),\n      ],\n    )\n  );\n}\n```\n\n## **ReactiveForm** vs. **ReactiveFormBuilder**: Which to Choose?\n\nBoth widgets are responsible for exposing the **FormGroup** to descendant widgets in the tree. Let's see an example:\n\n```dart\n// Using ReactiveForm\n@override\nWidget build(BuildContext context) {\n  return ReactiveForm(\n    formGroup: this.form,\n    child: ReactiveTextField(\n      formControlName: 'email',\n    ),\n  );\n}\n```\n\n```dart\n// Using ReactiveFormBuilder\n@override\nWidget build(BuildContext context) {\n  return ReactiveFormBuilder(\n    form: () =\u003e this.form,\n    builder: (context, form, child) {\n      return ReactiveTextField(\n        formControlName: 'email',\n      );\n    },\n  );\n}\n```\n\nThe main differences are that **ReactiveForm** is a _StatelessWidget_, so it doesn't save the instance of the **FormGroup**. You must declare the instance of the **FormGroup** in a StatefulWidget or resolve it from some Provider (state management library).\n\n```dart\n// Using ReactiveForm in a StatelessWidget and resolving the FormGroup from a provider\nclass SignInForm extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final viewModel = Provider.of\u003cSignInViewModel\u003e(context, listen: false);\n\n    return ReactiveForm(\n      formGroup: viewModel.form,\n      child: ReactiveTextField(\n        formControlName: 'email',\n      ),\n    );\n  }\n}\n```\n\n```dart\n// Using ReactiveForm in a StatefulWidget and declaring the FormGroup in the state.\nclass SignInForm extends StatefulWidget {\n  @override\n  _SignInFormState createState() =\u003e _SignInFormState();\n}\n\nclass _SignInFormState extends State\u003cSignInForm\u003e {\n  final form = fb.group({\n    'email': Validators.email,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return ReactiveForm(\n      formGroup: this.form,\n      child: ReactiveTextField(\n        formControlName: 'email',\n      ),\n    );\n  }\n}\n```\n\n\u003e If you declare a **FormGroup** in a _StatelessWidget_, the _group_ will be destroyed and recreated each time the instance of the _StatelessWidget_ is destroyed and created. Therefore, you must preserve the **FormGroup** in a state or in a Bloc/Provider/etc.\n\nOn the other hand, **ReactiveFormBuilder** is implemented as a _StatefulWidget_, so it holds the created **FormGroup** in its state. That way, it's safe to declare the **FormGroup** in a StatelessWidget or get it from a Bloc/Provider/etc.\n\n```dart\nclass SignInForm extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return ReactiveFormBuilder(\n      form: () =\u003e fb.group({'email': Validators.email}),\n      builder: (context, form, child) {\n        return ReactiveTextField(\n          formControlName: 'email',\n        );\n      },\n    );\n  }\n}\n```\n\nYou should use **ReactiveForm** if:\n\n- The form is complex enough.\n- You need to listen for changes in some child control to execute some business logic.\n- You are using a State Management library like Provider or Bloc.\n- Using a StatefulWidget to declare a very simple form is something that really doesn't bother you.\n\nYou should use **ReactiveFormBuilder** if:\n\n- The form is simple enough and doesn't need a separate Provider/Bloc state.\n- You don't want to use a StatefulWidget to declare the FormGroup.\n\nBut the final decision is really up to you; you can use any of them in any situation.\n\n## Widget Testing\n\n**Note: Mark your fields with `Key`s for easy access via the widget tester.**\n\n### Example Component\n\n```dart\nclass LoginForm extends StatefulWidget {\n  const LoginForm({Key? key}) : super(key: key);\n\n  @override\n  LoginFormState createState() =\u003e LoginFormState();\n}\n\nclass LoginFormState extends State\u003cLoginForm\u003e {\n  final form = FormGroup({\n    'email': FormControl\u003cString\u003e(validators: [Validators.required, Validators.email]),\n    'password': FormControl\u003cString\u003e(validators: [Validators.required]),\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return ReactiveForm(\n      formGroup: form,\n      child: Column(\n        children: \u003cWidget\u003e[\n          ReactiveTextField(\n            key: const Key('email'),\n            formControlName: 'email',\n          ),\n          ReactiveTextField(\n            key: const Key('password'),\n            formControlName: 'password',\n            obscureText: true,\n          ),\n          ElevatedButton(\n            key: const Key('submit'),\n            onPressed: () {},\n            child: const Text('Submit'),\n          ),\n        ],\n      ),\n    );\n  }\n}\n```\n\n### Example Test\n\n```dart\nvoid main() {\n  testWidgets('LoginForm should pass with correct values', (tester) async {\n    // Build the widget.\n    await tester.pumpWidget(const MaterialApp(\n      home: Scaffold(body: LoginForm()),\n    ));\n\n    await tester.enterText(find.byKey(const Key('email')), 'etc@test.qa');\n    await tester.enterText(find.byKey(const Key('password')), 'password');\n\n    await tester.tap(find.byKey(const Key('submit')));\n\n    await tester.pump();\n\n    // Expect to find the item on screen if needed\n    expect(find.text('etc@test.qa'), findsOneWidget);\n\n    // Get form state\n    final LoginFormState loginFormState = tester.state(find.byType(LoginForm));\n\n    // Check form state\n    expect(loginFormState.form.valid, true);\n  });\n}\n```\n\n## Reactive Forms + [Provider](https://pub.dev/packages/provider) plugin :muscle:\n\nAlthough **Reactive Forms** can be used with any state management library or even without one at all, **Reactive Forms** reaches its maximum potential when used in combination with a state management library like the [Provider](https://pub.dev/packages/provider) plugin.\n\nThis way, you can separate UI logic from business logic, and you can define the **FormGroup** inside a business logic class and then expose that class to widgets with a mechanism like the one the [Provider](https://pub.dev/packages/provider) plugin provides.\n\n## Reactive Forms + [Code Generation](https://pub.dev/packages/reactive_forms_generator) 🤖\n\n[ReactiveFormsGenerator](https://pub.dev/packages/reactive_forms_generator) is the code generator for reactive_forms that will save you tons of time and make your forms type-safe.\n\nThere is no reason to write code manually! Let the code generation work for you.\n\n## How to Create a Custom Reactive Widget?\n\n**Reactive Forms** is not limited to just common widgets in _Forms_ like text, dropdowns, sliders, switch fields, etc. You can easily create **custom widgets** that **two-way** bind to **FormControls** and create your own set of _Reactive Widgets_.\n\nIn our [Wiki](https://github.com/joanpablo/reactive_forms/wiki/Custom-Reactive-Widgets), you can find a tutorial on how to create your custom Reactive Widget.\n\nYou can also check the [Star Rating with Flutter Reactive Forms](https://dev.to/joanpablo/star-rating-with-flutter-reactive-forms-2d52) post as another example of a custom reactive widget.\n\n## What **Reactive Forms** is Not\n\n- **Reactive Forms** is not a fancy widgets package. It is not a library that brings new Widgets with new shapes, colors, or animations. It lets you decide the shapes, colors, and animations you want for your widgets but frees you from the responsibility of gathering and validating the data. It also keeps the data in sync between your model and your widgets.\n\n- **Reactive Forms** does not pretend to replace the native widgets that you commonly use in your Flutter projects, like _TextFormField_, _DropdownButtonFormField_, or _CheckboxListTile_. Instead, it brings new two-way binding capabilities and many more features to those same widgets.\n\n## What **Reactive Forms** Is\n\n- **Reactive Forms** provides a model-driven approach to handling form inputs whose values change over time. It's heavily inspired by Angular Reactive Forms.\n- It lets you focus on business logic and saves you time from collecting, validating, and maintaining synchronization between your models and widgets.\n- It removes boilerplate code and gives you the possibility to write clean code by defining a separation between model and UI with minimal effort.\n- It integrates perfectly well with common state management libraries like [Provider](https://pub.dev/packages/provider), [Bloc](https://pub.dev/packages/bloc), and many other good libraries the community has created.\n\n## Migration Versions\n\nVisit the [Migration Guide](https://github.com/joanpablo/reactive_forms/wiki/Migration-Guide) to see\nmore details about different version breaking changes.\n","funding_links":[],"categories":["组件","Dart","Components","Angular-Inspired Solutions"],"sub_categories":["表单","Forms","Wrappers"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoanpablo%2Freactive_forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoanpablo%2Freactive_forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoanpablo%2Freactive_forms/lists"}