Flutter Form Validation - Beyond Basics

Payam Zahedi
Persian Flutter
Published in
7 min readJul 9, 2024

Form validation in Flutter can be like navigating a maze. The methods we typically use may get the job done, but they often lack optimization and reusability. Think about having to redo validation logic every time you create a new form in your Flutter project — it’s not efficient or ideal.

In this article, we aim to change how we do form validation in Flutter. Our goal is to break free from the limitations of traditional methods and introduce a new approach — one that’s optimized and reusable.

Our destination? A clean and reusable Validator class, designed to simplify validation across projects and forms. By the end of our journey, you'll see the flaws in traditional validation methods and gain a powerful tool to enhance your Flutter form validation experience.

You can find full source code of this article in this repo on GitHub.

Existing Practices

We’ll explore the current practices in form validation, reflect on their effectiveness, and adapt them for reusability across various projects.

Let’s start with the most straightforward approach.

Simple Checks and Extension Methods

In the image above, we utilized Flutter’s basic validation checks for text input. While this method effectively handles straightforward validations, it lacks reusability, leading to manual copy-and-paste when applying similar validations elsewhere.

To address this, we can use extension methods in Dart to create reusable validation logic for each field.

This promotes code reuse and cleaner implementations. However, this approach comes with its own set of challenges. You’re still stuck copying and pasting code for each new field. Plus, updating the validation rules can be a hassle. For instance, if you’re making an extension method to check passwords and the rules keep changing, you’ll need to adjust the code every time, which can be a hassle.

Beyond Basics

Now it’s time to show our magic. Before we dive in, let’s think about what we want to achieve:

Goals

  1. Reusable: We should be able to use it in different places or projects.
  2. Maintainable: We should be able to improve it without changing the existing code.
  3. Easy to use: Using the API should be simple and straightforward.

With these goals in mind, let’s explore how to create a validation system that meets these criteria and takes our Flutter form validation to the next level.

Core Concepts

To create our validation system, we need to focus on two core classes: Validator and Validation.

Validator Class:

  • The Validator class is responsible for managing and running multiple validation checks.
  • It will sequentially apply a series of Validation subclasses.
  • If any validation fails, it will stop the process and return the error.

Validation Class:

  • Each Validation subclass will contain a specific validation rule.
  • These subclasses will be combined and used by the Validator class to check form inputs.

Validation

First, let’s define the Validation base class. Our goal is to create a system that's easy to maintain and extend with new validation rules.

The Validation base class serves as a blueprint for all our validation rules. Here are its core concepts:

  • Generic Class: The Validation class uses generics (<T>), enabling validation of various input types (e.g., strings, integers). While we could use String specifically, generics offer flexibility for different widgets, such as TextFormField and DropdownFormField. This adaptability makes our validation system more versatile.
  • Abstract Method: The validate method, which must be implemented by subclasses, takes a BuildContext and a value of type T. It returns an error message if validation fails or null if it passes.
  • Extensibility: Subclasses can be created to add new validation rules, making the system modular and easy to maintain without modifying existing code.

Implementation Examples

Here are some implementation examples of validation classes using the Validation base class:

These examples demonstrate how to implement specific validation rules by extending the Validation base class. Each subclass (RequiredValidation and EmailValidation) implements the validate method to define its own validation logic tailored to different types of input (T and String, respectively).

Now, you can see that maintaining this validation system is straightforward. Whenever you need a new validation rule, you can simply implement a new subclass. For example, you can try to make your own validation for password and username.

Validator

The Validator class encapsulates a utility method to apply multiple validation rules to a form field. Here’s an overview of its implementation:

The Validator class is a handy utility with just one static method. We built it like this to make things smoother for users. The apply method takes a build context and a list of validations. It goes through each validation one by one. If any validation finds an error, it stops right there and returns the error message. This way, applying and managing form field validations in Flutter becomes straightforward and hassle-free.

But the fun part is when you want to use it:

You can simply create a list of validation rules like checking if a field is required or if an email is valid. Then, just plug them into Validator.apply when setting up your form fields. It’s like adding superpowers to your forms with minimal effort!

Another common use case we encounter is validating passwords. But hey, you already know how simple it is, right? You just need a new subclass of Validation and plug it in!

Validation on Dropdown

As you might know, TextFormField is not the only widget that can have validation. We can also apply validation to our dropdowns(DropdownButtonFormField). The key difference here is that dropdowns work with a generic <T> type instead of a String, meaning you can use different data types in your dropdown.

Let’s look at an example:

Just imagine your app includes a feature where users need to select a role from a dropdown menu. For simplicity in our example, we’ve defined a straightforward enum called Role. Your dropdown implementation would resemble the example shown above.

However, the next question is: how do we effectively utilize validator and validation classes in this scenario?

Since our dropdown is based on the Role enum, we'll need to implement a validation class specifically tailored to handle this generic type.

And this is our RoleValidation class. Here, we simply ensure that the selected role is more than just a 'member'. If the user selects 'member', we display an error message.

And the image above illustrates how we can integrate it into our widget, following the same approach as we did for the text field.

Build Context: Why Do We Need It?

You might be wondering why we pass BuildContext to our validator and validation classes. It's not just for show! Here's the scoop:

In our examples, we returned simple error messages as strings. But in real-world apps, you often need more than that. Think about localization — making your app speak different languages. To fetch those localized error messages, you need access to the BuildContext.

Conclusion

In this article, we’ve leveled up Flutter form validation using the Validator and Validation classes. These tools tackle common issues with traditional methods by giving us a more organized way to handle validation logic.

With the Validator class coordinating checks and Validation subclasses defining rules, Flutter devs can tidy up their code, make updates easier, and expand validation across different projects without the usual headaches.

This approach makes form validation in Flutter simpler and more flexible, making our lives as developers a bit smoother.

--

--

Payam Zahedi
Persian Flutter

I’m a Software Engineer, Flutter Developer, Speaker & Open Source Lover. find me in PayamZahedi.com