Flutter Form Validation - Beyond Basics
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
- Reusable: We should be able to use it in different places or projects.
- Maintainable: We should be able to improve it without changing the existing code.
- 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 useString
specifically, generics offer flexibility for different widgets, such asTextFormField
andDropdownFormField
. This adaptability makes our validation system more versatile. - Abstract Method: The
validate
method, which must be implemented by subclasses, takes aBuildContext
and a value of typeT
. It returns an error message if validation fails ornull
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.