Flutter TextFormField Validation made easy
Tags: flutter, validation, textform, login
Published: 2021-07-24 15:28:44

## Hey There!!!    👋

If you are a flutter developer and if you have created an app that requires an authentication page where the user enters his credentials , you would have used a TextFormField widget. 

Now , let's say a user has entered his credentials (email, password, etc) and while doing so he accidentally enters an invalid email address ( for eg : "  namegmail.com  ")  , there has to be some way to notify the user to enter the correct email address right?

Well yeah , there is something called **validator** property in the TextFormField widget.
We use it to specify the kind of input that is to be accepted.

The traditional way of doing it is to put an *if-else* conditional and validate the input.

```dart
validator:(val){
  if(val.isEmpty){
    return "Required";
  }
  if(val.length < 6){
    return "Password must be atleast 6 characters long";
  }
  if(val.length > 20){
    return "Password must be less than 20 characters";
  }
  if(!val.contains(RegExp(r'[0-9]'))){
    return "Password must contain a number";
  }
}
```

A similar implementation can be done for email validation but the RegExp(regular expression) string would be different.

But if you're not a fan of *if-else* conditionals there's a similar way of handling validation using a package called **form_field_validator**

You can get the package from
`https://pub.dev/packages/form_field_validator`



>My flutter and dart versions :   
Flutter (Channel stable, 2.0.0, on Microsoft Windows [Version 10.0.19042.1110],
    locale en-IN)   
    • Flutter version 2.0.0 at C:\src\flutter   
    • Framework revision 60bd88df91 (5 months ago), 2021-03-03 09:13:17 -0800         
    • Engine revision 40441def69   
    • Dart version 2.12.0     
If you are using a different version , you might have to make some changes like adding null safety etc.


## Getting Started

To use that package we need to create a new flutter project. To those out there using VScode , follow the below steps to create a new Flutter project : 


1. Open VScode and Ctrl+Shift+P and start typing Flutter


![Screenshot (351).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1627111722970/ZQaYfv8cB.png)

2.Click on *Flutter:New Application Project* and give it a name. I'll be naming it *form_validator* .


![Screenshot (353).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1627111891522/fjy28vgfN.png)

3.Just sit back and let the magic happen.

Flutter will go ahead and create a project for you with all the necessary files and folders in it.
This is how the *main.dart* file inside *lib* folder will look like .

![main_initial.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1627112064290/9WrE1IDJg.jpeg)

4.Now delete all the comments and unnecessary code and modify your *main.dart* to this :

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

```

5.You'll get a red squiggly line under "LoginPage" and that's because it doesn't exist yet.

6.Now go to the lib folder and create a new file and name it *loginPage.dart*

7.Open *loginPage.dart* and paste the following code :

```dart
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({ Key key }) : super(key: key);

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    return Container(
      
    );
  }
}
```

Paste the following after  "
class _LoginPageState extends State<LoginPage> {" :

```dart
  GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  TextEditingController _nameController= TextEditingController();
  TextEditingController _emailController = TextEditingController();
  TextEditingController _passwordController = TextEditingController();
```
### Keys   
What is a key? Every flutter widget has a key.These keys are helpful to flutter when the state changes . These keys help keep track of the widgets in the widget tree.   
And yes keys are not necessary in stateless widgets because their state never changes and there is nothing to keep track of.Keys in flutter are of two types - LocalKey and GlobalKey.   
GlobalKeys are useful when a widget's information is to be accessed somewhere else in the widget tree.

### Form   
We come across situations where we need to use multiple TextFormFields in our app. Wouldn't it be nice if there was a way to control all those TextFormFields in a single place? That's where **Form** comes into picture. With **Form** we can control all those fields and their data can be evaluated and validated together at one place.

>_formKey is a GlobalKey to control the state of the Form(we will be implementing this later)

### TextEditingController

>_nameController is an object of class TextEditingController that gives us access to the input string typed in the "Name" input field. We can use it to notify the app about the changes made in the input field.

>_emailController and _passwordController are objects of the same TextEditingController class with the same functionality.


We will create a boolean value called "obscure" to hide or make the password visible.
Let's set it to true initially.

```dart
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key key}) : super(key: key);

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  TextEditingController _nameController = TextEditingController();
  TextEditingController _emailController = TextEditingController();
  TextEditingController _passwordController = TextEditingController();
  bool obscure=true;
  @override
  Widget build(BuildContext context) {
    return Scaffold();
   }
}

```


Let's go ahead and make a UI for the login page in our app.

```dart
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key key}) : super(key: key);

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  TextEditingController _nameController = TextEditingController();
  TextEditingController _emailController = TextEditingController();
  TextEditingController _passwordController = TextEditingController();
  bool obscure=true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[800],
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              SizedBox(
                height: 250.0,
              ),
              Container(
                margin: EdgeInsets.symmetric(horizontal: 30.0),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(20.0),
                ),
                child: Form(
                  key: _formKey,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Padding(
                        padding: EdgeInsets.only(top: 20.0),
                        child: Text(
                          "Welcome",
                          style: TextStyle(
                            fontSize: 30.0,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(
                          top: 10.0,
                          left: 30.0,
                          right: 30.0,
                          bottom: 20.0,
                        ),
                        child: TextFormField(
                          controller: _nameController,
                          cursorColor: Colors.black,
                          style: TextStyle(),
                          decoration: InputDecoration(
                            labelText: 'Name',
                            labelStyle: TextStyle(color: Colors.black),
                            focusedBorder: UnderlineInputBorder(
                              borderSide: new BorderSide(color: Colors.black),
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(
                          left: 30.0,
                          right: 30.0,
                          bottom: 20.0,
                        ),
                        child: TextFormField(
                          controller: _emailController,
                          cursorColor: Colors.black,
                          style: TextStyle(),
                          decoration: InputDecoration(
                            labelText: 'Email',
                            labelStyle: TextStyle(color: Colors.black),
                            focusedBorder: UnderlineInputBorder(
                              borderSide: new BorderSide(color: Colors.black),
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(
                          left: 30.0,
                          right: 30.0,
                          bottom: 20.0,
                        ),
                        child: TextFormField(
                          obscureText: obscure,
                          controller: _passwordController,
                          cursorColor: Colors.black,
                          style: TextStyle(),
                          decoration: InputDecoration(
			    suffixIcon: IconButton(
                              icon: Icon(
                                Icons.visibility,
                                color: Colors.black,
                              ),
                              onPressed: () {
                                setState(() {
                                  obscure = !obscure;
                                });
                              },
                            ),
                            labelText: 'Password',
                            labelStyle: TextStyle(color: Colors.black),
                            focusedBorder: UnderlineInputBorder(
                              borderSide: new BorderSide(color: Colors.black),
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(
                          left: 30.0,
                          right: 30.0,
                          bottom: 30.0,
                        ),
                        child: TextFormField(
                          obscureText: obscure,
                          cursorColor: Colors.black,
                          style: TextStyle(),
                          decoration: InputDecoration(
                            labelText: 'Re-type Password',
                            labelStyle: TextStyle(color: Colors.black),
                            focusedBorder: UnderlineInputBorder(
                              borderSide: new BorderSide(color: Colors.black),
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(bottom: 20.0),
                        child: ElevatedButton(
                          style: ButtonStyle(
                            backgroundColor: MaterialStateProperty.all<Color>(Colors.black),
                            shape: MaterialStateProperty.all<OutlinedBorder>(
                              RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(20.0),
                              ),
                            ),
                          ),
                          onPressed: () {},
                          child: Padding(
                            padding: EdgeInsets.symmetric(
                              horizontal: 60.0,
                            ),
                            child: Text("Submit"),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              SizedBox(
                height: 100.0,
              ),
            ],
          ),
        ),
      ),
    );
  }
}
``` 

>We've set the **key** property of **Form** widget to the **_formKey** and the **controller** properties of **TextFormField** widgets to their respective controllers.

>Note that the underscores before the variables **_formKey** , **_passwordController**, etc mean that they are private to the LoginPage class and are only accessible inside this class.

8.Go to main.dart and import loginPage.dart

```dart
import 'package:form_validator/loginPage.dart';
```

9.Go to pubspec.yaml file in your project and add the *form_field_validator* package 

![pubspec.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1627114026081/lywwGD8P_.jpeg)

10.Go to *loginPage.dart* and import the package 
```dart
import 'package:form_field_validator/form_field_validator.dart';
```

### Validators
11.Now let's go to the TextFormFields and add the validators.
```dart
TextFormField(
 validator: RequiredValidator(errorText: "Required"),
 controller: _nameController,
 cursorColor: Colors.black,
 style: TextStyle(),
 decoration: InputDecoration(
 labelText: 'Name',
 labelStyle: TextStyle(color: Colors.black),
 focusedBorder: UnderlineInputBorder(
 borderSide: new BorderSide(color: Colors.black),
      ),
    ),
  ),
```

>The RequiredValidator takes in a single argument of type String for the errorText property and makes sure that the input field isn't empty . If it is empty , it displays the errorText specified.

Name input field may need only the *RequiredValidator* but what about email and password fields? They'll need multiple validators(required validation and valid email address validation and input length validation etc) as we saw in the traditional *if-else* conditionals right?

We can achieve that using *MultiValidator* that comes with the package
```dart
TextFormField(
 validator: MultiValidator([]),
 controller: _emailController,
 cursorColor: Colors.black,
 style: TextStyle(),
 decoration: InputDecoration(
 labelText: 'Email',
 labelStyle: TextStyle(color: Colors.black),
 focusedBorder: UnderlineInputBorder(
 borderSide: new BorderSide(color: Colors.black),
      ),
    ),
  ),
```

>MultiValidator takes a list of validators as arguments.



We add multiple validators to the email input field this way ...
```dart
validator: MultiValidator([
  RequiredValidator(errorText: "Required"),
  EmailValidator(errorText: "Please enter a valid email address"),
]),
```
>The EmailValidator takes in a single argument of type String for the errorText property and makes sure that the email address entered matches the regular expression - r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$"

The regular expression looks scary right? Don't worry , just enter a valid email address and you'll be fine.

... and for the password input field : 
```dart
validator: MultiValidator([
  RequiredValidator(errorText: "Required"),
  MinLengthValidator(6,
      errorText:
          "Password must contain atleast 6 characters"),
  MaxLengthValidator(15,
      errorText:
          "Password cannot be more 15 characters"),
  PatternValidator(r'(?=.*?[#?!@$%^&*-])',
      errorText:
          "Password must have atleast one special character"),
]),
```
>The MinLengthValidator takes 2 input arguments   
1.The minimum length(int) of input.      
2.String for the errorText property if the input doesn't meet the required length.

>The MaxLengthValidator takes 2 input arguments   
1.The maximum length(int) of input.    
2.String for the errorText property if the input exceeds the required length.

>The PatternValidator takes 2 input arguments   
1.The pattern in the form of regular expression.    
2.String for the errorText property if the input doesn't contain the regexp characters .

For the Re-type Password field :
```dart
validator: (val) {
  if (val.isEmpty) {
    return "Required";
  }
  return MatchValidator(
          errorText: "Passwords don't match")
      .validateMatch(val, _passwordController.text);
},
```
>If the input is empty , it returns the string "Required".   
Else if the input isn't empty , it checks if the input matches the password entered earlier.

>Note that we access the input entered in the fields using the text property of the controllers.

Finally we implement the *onPressed* function inside *ElevatedButton* :
```dart
onPressed: () {
   if (_formKey.currentState.validate()) {
   ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
      	backgroundColor: Colors.white,
      	content: Text(
      	  'Validation Successful',
           style: TextStyle(
         	  color: Colors.black,
           ),
        ),
      ),
   );
  }
},
```
>Here we ask for the currentState of the form using **_formKey** and validate the form using the **validate()** function.   
If the **validate()** function return true , a SnackBar is displayed.   
Else , the repective errorText is displayed wherever the validation failed.


### Run the App 
It's time to see the code in action. Build the app on an emulator or your mobile phone.   

### Some Other Validators
The *form_field_validator* package comes with other validators :   

- **RangeValidator()**
This function is used to specify the numeric range of the input value.   
It takes 3 named arguments -   
min - Minimum numeric value the input must have.   
max - Maximum numeric value the input can have.
errorText - Text to be displayed if validation fails.

- **LengthRangeValidator()**
This function is like a combination of *MinLengthValidator* and *MaxLengthValidator*.It is used to specify the length of the input. It takes in 3 named arguments :   
min - Minimum length of input.   
max - Maximum length of the input.
errorText - Text to be displayed if validation fails.

### Next Steps
Full source code :      
{% github AdityaSubrahmanyaBhat/form %}

Well , That's all folks 😊 Thank you.