Custom Validation Rules

Forms are central to user interaction in applications. From login and signup pages to checkout processes and profile updates, forms collect critical information. But simply collecting input is not enough; developers must ensure that the information provided is valid, accurate, and meaningful. This is where custom validation rules come in.

Flutter provides robust support for building forms and validating input through the Form widget, FormField, and validator functions. However, the real power comes when you implement custom validation rules to handle specific needs such as validating emails, passwords, and phone numbers.

In this detailed guide, we will explore:

  • Why custom validation is needed
  • Email, password, and phone number validation rules
  • Using regex for flexible validation
  • Implementing real-time feedback for users
  • Best practices and common pitfalls

By the end, you will be confident in implementing advanced, user-friendly validation in your Flutter applications.


Why Custom Validation is Needed

Built-in vs Custom Validation

Flutter’s TextFormField supports validation through a simple validator function. For example:

TextFormField(
  validator: (value) {
if (value == null || value.isEmpty) {
  return 'This field cannot be empty';
}
return null;
}, )

While this works for basic cases, most real-world applications require specific validation rules.

Scenarios Requiring Custom Validation

  • Email format validation: Ensure input contains @ and a domain.
  • Password validation: Require minimum length, uppercase, digits, and special characters.
  • Phone number validation: Enforce digit count and country-specific formats.
  • Regex rules: Match complex patterns like postal codes or usernames.
  • Real-time validation: Provide immediate feedback instead of waiting for form submission.

Without custom validation, applications risk collecting invalid or incomplete data, leading to poor user experience and possible system errors.


Email Validation

Why Email Validation Matters

Emails are one of the most common inputs in applications, used for login, registration, and communication. Collecting invalid emails can lead to:

  • Failed authentication
  • Lost communication
  • Fake account creation

Simple Email Validation

String? validateEmail(String? value) {
  if (value == null || value.isEmpty) {
return 'Please enter your email';
} if (!value.contains('@')) {
return 'Invalid email: missing "@"';
} return null; }

Regex-Based Email Validation

For stricter validation, regex is used:

String? validateEmail(String? value) {
  final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
  if (value == null || value.isEmpty) {
return 'Please enter your email';
} if (!emailRegex.hasMatch(value)) {
return 'Enter a valid email address';
} return null; }

This ensures the input contains proper structure like [email protected].


Password Validation

Why Password Validation is Crucial

Weak passwords compromise security. Applications should enforce strong password rules to protect user accounts.

Basic Password Rule

String? validatePassword(String? value) {
  if (value == null || value.isEmpty) {
return 'Please enter your password';
} if (value.length < 6) {
return 'Password must be at least 6 characters';
} return null; }

Advanced Password Validation with Regex

Rules:

  • At least 8 characters
  • One uppercase letter
  • One lowercase letter
  • One number
  • One special character
String? validatePassword(String? value) {
  final passwordRegex =
  RegExp(r'^(?=.*&#91;A-Z])(?=.*&#91;a-z])(?=.*\d)(?=.*&#91;@$!%*?&amp;]).{8,}$');
if (value == null || value.isEmpty) {
return 'Please enter your password';
} if (!passwordRegex.hasMatch(value)) {
return 'Password must include uppercase, lowercase, number, and special character';
} return null; }

Real-Life Use

This ensures passwords like P@ssw0rd123 are accepted, while weak ones like 12345 are rejected.


Phone Number Validation

Importance of Phone Validation

Phone numbers are used for:

  • Account verification (via OTP)
  • Contact information
  • Delivery addresses

Invalid numbers create communication breakdowns.

Basic Validation

String? validatePhone(String? value) {
  if (value == null || value.isEmpty) {
return 'Please enter your phone number';
} if (value.length < 10) {
return 'Phone number must be at least 10 digits';
} return null; }

Regex-Based Phone Validation

For stricter formatting:

String? validatePhone(String? value) {
  final phoneRegex = RegExp(r'^\+?[0-9]{10,13}$');
  if (value == null || value.isEmpty) {
return 'Please enter your phone number';
} if (!phoneRegex.hasMatch(value)) {
return 'Enter a valid phone number';
} return null; }

This allows optional country codes like +1, +91, and enforces 10–13 digits.


Regex-Based Validations

Why Use Regex

Regex (regular expressions) is a pattern-matching technique that makes complex validations simple. Instead of writing multiple conditions, regex allows concise rules.

Common Regex Patterns

  • Email: ^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$
  • Password: ^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$
  • Phone: ^\+?[0-9]{10,13}$
  • Username: ^[a-zA-Z0-9_]{3,16}$ (3–16 characters, letters, numbers, underscores)
  • Postal Code: ^[0-9]{5}(?:-[0-9]{4})?$ (US ZIP format)

Example: Username Validation

String? validateUsername(String? value) {
  final usernameRegex = RegExp(r'^[a-zA-Z0-9_]{3,16}$');
  if (value == null || value.isEmpty) {
return 'Please enter a username';
} if (!usernameRegex.hasMatch(value)) {
return 'Username must be 3–16 characters, letters, numbers, or underscores';
} return null; }

Regex ensures concise, powerful validation rules.


Showing Real-Time Feedback

Validation is not only about correctness but also user experience. Instead of waiting until the form is submitted, apps should provide real-time feedback as users type.

Real-Time Email Feedback

TextFormField(
  decoration: InputDecoration(labelText: 'Email'),
  onChanged: (value) {
if (validateEmail(value) != null) {
  print('Invalid email: $value');
} else {
  print('Valid email: $value');
}
}, )

Using Controllers for Feedback

final emailController = TextEditingController();

@override
void initState() {
  super.initState();
  emailController.addListener(() {
final text = emailController.text;
if (validateEmail(text) != null) {
  print('Invalid email');
} else {
  print('Valid email');
}
}); }

Displaying Error Messages Dynamically

You can dynamically update error messages under the input field by setting errorText inside InputDecoration.

TextField(
  decoration: InputDecoration(
labelText: 'Email',
errorText: validateEmail(emailText),
), onChanged: (value) {
setState(() {
  emailText = value;
});
}, )

This approach gives users immediate visual feedback, preventing frustration during form submission.


Complete Example: Signup Form with Custom Validations

class SignupForm extends StatefulWidget {
  @override
  _SignupFormState createState() => _SignupFormState();
}

class _SignupFormState extends State<SignupForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _phoneController = TextEditingController();

  @override
  Widget build(BuildContext context) {
return Scaffold(
  appBar: AppBar(title: Text('Signup')),
  body: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Form(
      key: _formKey,
      child: Column(
        children: &#91;
          TextFormField(
            controller: _emailController,
            decoration: InputDecoration(labelText: 'Email'),
            validator: validateEmail,
          ),
          TextFormField(
            controller: _passwordController,
            obscureText: true,
            decoration: InputDecoration(labelText: 'Password'),
            validator: validatePassword,
          ),
          TextFormField(
            controller: _phoneController,
            decoration: InputDecoration(labelText: 'Phone'),
            validator: validatePhone,
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                print('Email: ${_emailController.text}');
                print('Password: ${_passwordController.text}');
                print('Phone: ${_phoneController.text}');
              }
            },
            child: Text('Register'),
          ),
        ],
      ),
    ),
  ),
);
} String? validateEmail(String? value) {
final emailRegex = RegExp(r'^&#91;\w-\.]+@(&#91;\w-]+\.)+&#91;\w-]{2,4}$');
if (value == null || value.isEmpty) {
  return 'Please enter your email';
}
if (!emailRegex.hasMatch(value)) {
  return 'Enter a valid email address';
}
return null;
} String? validatePassword(String? value) {
final passwordRegex =
    RegExp(r'^(?=.*&#91;A-Z])(?=.*&#91;a-z])(?=.*\d)(?=.*&#91;@$!%*?&amp;]).{8,}$');
if (value == null || value.isEmpty) {
  return 'Please enter your password';
}
if (!passwordRegex.hasMatch(value)) {
  return 'Password must include uppercase, lowercase, number, and special character';
}
return null;
} String? validatePhone(String? value) {
final phoneRegex = RegExp(r'^\+?&#91;0-9]{10,13}$');
if (value == null || value.isEmpty) {
  return 'Please enter your phone number';
}
if (!phoneRegex.hasMatch(value)) {
  return 'Enter a valid phone number';
}
return null;
} }

This form validates email, password, and phone number with regex and shows real-time feedback through validators.


Best Practices for Custom Validation

  1. Keep validation reusable
    • Write functions for each validation rule and reuse across forms.
  2. Use regex carefully
    • Test regex patterns to avoid false positives.
  3. Provide clear error messages
    • Avoid technical jargon; use user-friendly text.
  4. Real-time feedback improves UX
    • Validate on typing, not only on submission.
  5. Balance strictness and usability
    • Overly strict rules frustrate users.
  6. Dispose controllers
    • Prevent memory leaks by disposing controllers in dispose().

Common Mistakes with Validation

  1. Over-validating
    • Example: Rejecting valid email formats due to overly strict regex.
  2. Weak password rules
    • Accepting short or simple passwords.
  3. Ignoring localization
    • Phone formats differ by country; make validation flexible.
  4. Not giving real-time feedback
    • Forcing users to wait until submission causes frustration.
  5. Hardcoding error messages
    • Instead, store messages in constants or localization files.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *