Newby Coder header banner

Flutter Form Validation

Form validation in Flutter can be implemented using simple regex validators as shown in provided application

Text validators can be implemented as functions of type FormFieldValidator which return a string in case of validation error or null if validated

Simple example which validates if input string contains at least one non-space character

static FormFieldValidator<String> validateNotEmpty(String input) {
    if (input != null && input.trim().length > 0)
        return null;
    return "Error: Null/Empty string"
}

Creating new Flutter App

Check Flutter installation to setup Flutter

Use flutter create command to create a new Flutter project (here form_validation_app) :

flutter create form_validation_app

Implementation

Declare Form

Create a class variable of type GlobalKey<FormState>

final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();

Declare a Form widget and assign key as declared variable

Use one or more widgets containing validator property as child widget(s) of Form

 new Form(
  key: this._formKey,
  child: new TextFormField(
        keyboardType: TextInputType.emailAddress, // Use email input type for emails.
        decoration: new InputDecoration(hintText: 'email', labelText: 'Email'),
        validator: Validator.validateEmail(),
        onSaved: (String value) { this.userData.email = value; }
  ),
)

Declare a function to check whether value entered for each child widget is valid and save form state using currentState of key variable

   void submit() {
    if (this._formKey.currentState.validate()) {
      _formKey.currentState.save();
    }
  }

Assign function as callback to a button denoting Submit

new RaisedButton(
  child: new Text('Validate', style: new TextStyle(color: Colors.white),),
  onPressed: this.submit,
  color: Colors.blue,
)

Validation

A validateRegex function is declared with parameters regex, errorMsg and a boolean not

It returns a new function which takes an argument value and matches it with the regex pattern

If boolean not is false, then it checks for presence of regex pattern in value and returns error message if not present

If boolean not is true, then it returns error message if pattern is present

static FormFieldValidator<String> validateRegex(String regex, String errorMsg, bool not) {
    return (value) {
      if (RegExp(regex).hasMatch(value))
        return not? errorMsg:null;
      return not? null:errorMsg;
    };
}
static FormFieldValidator<String> validatePassword() {
    return (value) {
        Function validate = validateRegex(r"[A-Z]+", "Password should contain an uppercase character", true) ;
        String err = validate(value);
        if(err == null) {
            validate = validateNotRegex(r"[#]+", "Password should not contain #", false);
            err = validate(value);
        }
        return err;
    };
}

A function validatePassword() can be declared which also returns a function and uses validateRegex() to check for two patterns for input value

Later validatePassowrd() method can be used as validator for a TextFormField

new TextFormField(
    obscureText: true, // To display typed char with *
    decoration: new InputDecoration(
      hintText: 'Password',
      labelText: 'Enter your password'
    ),
    validator: validatePassword(),
    onSaved: (String value) { this.userData.password = value; }
)

Similarly more patterns can be added such that subsequent patterns are checked only if previous validation returns null

App Code

Following example app code contains function to take a list of validateRegex() methods to validate different fields like username, email, password

import 'package:flutter/material.dart';
import 'package:validate/validate.dart';
import 'dart:convert';
import 'dart:async';
import 'dart:typed_data';


void main() => runApp(new MaterialApp(
  title: 'Nc Form Validation',
  home: new LoginPage(),
));

class UserData {
  String username = '';
  String email = '';
  String password = '';
}

class LoginPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
  UserData userData = new UserData();

  void submit() {
    if (this._formKey.currentState.validate()) {
      _formKey.currentState.save();
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => UserPage(userData),),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    final Size screenSize = MediaQuery.of(context).size;
    return new Scaffold(
      appBar: new AppBar(title: new Text('Login'),),
      body: new Container(
        padding: new EdgeInsets.all(20.0),
        child: new Form(
          key: this._formKey,
          child: new ListView(
            children: <Widget>[
              new TextFormField(
                keyboardType: TextInputType.emailAddress, // Use email input type for emails.
                decoration: new InputDecoration(hintText: 'email', labelText: 'Email'),
                validator: Validator.validateEmail(),
                onSaved: (String value) { this.userData.email = value; }
              ),
              new TextFormField(
                obscureText: true, // To display typed char with *
                decoration: new InputDecoration(
                  hintText: 'Password',
                  labelText: 'Enter your password'
                ),
                validator: Validator.validatePassword(),
                onSaved: (String value) { this.userData.password = value; }
              ),
              new TextFormField(
                decoration: new InputDecoration(hintText: 'ign', labelText: 'Username'),
                onSaved: (String value) { this.userData.username = value; },
                validator: Validator.validateUsername(),
              ),
              new Container(
                width: screenSize.width,
                child: new RaisedButton(
                  child: new Text('Validate', style: new TextStyle(color: Colors.white),),
                  onPressed: this.submit,
                  color: Colors.blue,
                ),
                margin: new EdgeInsets.only(top: 20.0),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class UserPage extends StatelessWidget {
  UserData userData = new UserData();

  UserPage(this.userData);

  @override
  Widget build(BuildContext context) {
    final Size screenSize = MediaQuery.of(context).size;
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Next page'),
      ),
      body: Center(
        child: new Container(
          height: 90,
          color: Colors.teal[400],
          child: new Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text("Next page of previous page"),
              Text("Initially configured to be navigated into after validation"),
              Text("Password ${userData.password}"),
            ],
          ),
        ),
      ),
    );
  }
}

class Validator {
  static FormFieldValidator<String> _validateRegex(String regex, String errorMsg, bool not) {
    return (value) {
      if (RegExp(regex).hasMatch(value))
        return not? errorMsg:null;
      return not? null:errorMsg;
    };
  }

  static FormFieldValidator<String> validateRegex(String regex, String errorMsg) {
    return _validateRegex(regex, errorMsg, false);
  }

  static FormFieldValidator<String> validateNotRegex(String regex, String errorMsg) {
    return _validateRegex(regex, errorMsg, true);
  }

  static FormFieldValidator<String> validate(List<FormFieldValidator<String>> validators) {
    return (value) {
      value = value.trim();
      for (final validator in validators) {
        final validateMsg = validator(value);
        if (validateMsg != null)
          return validateMsg;
      }
      return null;
    };
  }
  static FormFieldValidator<String> validateEmail() {
    return validate([
      validateRegex(r".+", "Email should not be empty"),
      validateRegex(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$",
        "Invalid email format")
    ]);
  }

  static FormFieldValidator<String> validatePassword() {
    return validate([
      validateRegex(r".+", "Password should not be empty"),
      validateRegex(r".{6}", "Password should be minimum 6 characters"),
      validateRegex(r"^.{6,12}$", "Password should be maximum 12 characters"),
      validateRegex(r"[A-Z]+", "Password should contain an uppercase character"),
      validateRegex(r"[a-z]+", "Password should contain a lowercase character"),
      validateRegex(r"[0-9]+", "Password should contain a digit"),
      validateRegex(r"[\.!$%&\'*+/=?^_`{|}~\-:;@]+", "Password should contain minimum one of !\$%&\'*+/=?^_`{|}~\-:;@"),
      validateNotRegex(r"[#]+", "Password should not contain #"),
    ]);
  }

  static FormFieldValidator<String> validateUsername() {
    return validate([
      validateRegex(r".+", "Username should not be empty"),
      validateRegex(r"^username$", "Username should be username")
    ]);
  }
}

Run instructions

Ensure a supported device is connected or emulator/simulator is started

Go to project directory

Use flutter run command to run

flutter run

It builds and runs app on an available android/ios device


Screenshot/image

Android

cl-flutter-form-validation

iOS

cm-flutter-form-validation-as1cm-flutter-form-validation-as2cm-flutter-form-validation-as3
cm-flutter-form-validation-as4