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.
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 :
- Open VScode and Ctrl+Shift+P and start typing Flutter
2.Click on Flutter:New Application Project and give it a name. I'll be naming it form_validator .
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 .
4.Now delete all the comments and unnecessary code and modify your main.dart to this :
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 :
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 {" :
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.
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.
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
import 'package:form_validator/loginPage.dart';
9.Go to pubspec.yaml file in your project and add the form_field_validator package
10.Go to loginPage.dart and import the package
import 'package:form_field_validator/form_field_validator.dart';
Validators
11.Now let's go to the TextFormFields and add the validators.
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
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 ...
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 :
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 :
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 :
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 :
AdityaSubrahmanyaBhat / form
An app to demonstrate form validation
form
A new Flutter project.
Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.
Well , That's all folks 😊 Thank you.
Top comments (2)
Very informative! Good work 👍🏻
Thank you😊