Flutter Formz Explained — The Complete Crash Course

Tomic Riedel

--

Formz is a Dart package developed by Very Good Ventures to simplify form representation and validation in Dart applications. This package provides a unified form representation that makes it easier to manage both the data of form fields as well as their validation status.

Once you fully understand how to use it, you never want to miss it again.

And because of that, we are going to take a deep look at Formz today!

Let’s get started!

Installation

The first thing we have to do after creating our app is to add formz. To do so, we will use the command flutter pub add formz. The version of formz when writing this article is 0.7.0, but it’s quite likely that this tutorial will also work if you use a higher version because the basics won’t change.

Create a Formz Module

Formz is structured into models, where each module validates different things. E.g., a module can be used to validate an email address, password, or date. It looks like this:

import 'package:formz/formz.dart';

// This represents all of the wrong states or field can have.
// A password can be too short, does not have any digit or uppercase letter.
enum PasswordInputError { tooShort, noDigit, noUppercase }

class PasswordInput extends FormzInput<String, PasswordInputError> {
// PasswordInput.pure represents an unmodified form input
const PasswordInput.pure() : super.pure('');

// While super.dirty represents a modified form input
const PasswordInput.dirty({String value = ''}) : super.dirty(value);

// Override validator to handle validating a given input value.
@override
PasswordInputError validator(String value) {
// This part is where all our validation logic is going to be
}
}

Maybe you have one question: What does .pure and .dirty do?

It’s pretty simple: Let’s say you have a text field. When you initialize the widget, you use PasswordInput.pure(), because the user has not inputted anything yet. When the user types something in the text field, you use PasswordInput.dirty(value: 'Inputted Value'). So dirty is pretty much just a way of saying that the input has changed. And this is going to help change the state of buttons (e.g. from disabled to enabled) or other kinds of things.

Now that we know that, we want to validate our input of course.

For our password example, this could look something like this:

class PasswordInput extends FormzInput<String, PasswordInputError> {
// [...] - form input representations

@override
PaswordInputError? validator(String value) {
// Check if the password is too short
if (value.length < 8) {
return PasswordInputError.tooShort;
}

// Check if the password contains a digit
if (!value.contains(RegExp(r'[0-9]'))) {
return PasswordInputError.noDigit;
}

// Check if the password contains an uppercase letter
if (!value.contains(RegExp(r'[A-Z]'))) {
return PasswordInputError.noUppercase;
}

// If everything is fine, return null
return null;
}
}

That’s it! You are done. Well… partly. We need to interact with our Input of course:

Interact with FormzInput

First, we need to create a variable that stores our PasswordInput:

PasswordInput passwordInputValue = PasswordInput.pure();

As already said above, when the user types something in the text field, we simply assign a new value to the variable:

passwordInputValue = PasswordInput.dirty(value: newValue);

Now with all of this set up, we can finally use formz nicely:

// Returns the value of the input
print(passwordInputValue.value);

// Checks if the input is valid or not.
// If the returned value of your validator is null, it is valid. If not, it's not.
print(paswordInputValue.isValid);
print(PasswordInputValue.isNotValid);

// Returns the error of your input. It it's null, the input is valid.
// Example output: PasswordInputError.tooShort
print(passwordInputValue.error);

Especially the last one, passwordInputValue.error, is going to help you tremendously by increasing your user experience. You do not only have to give him a message saying “Your password is incorrect”, but now you can easily say “Your password needs to have at least 8 characters”, “You need to include at least one number”, etc.

And this is the power of Formz. Constructing our tiny model gives you so much control over your form and its validation of it.

More Formz features

Automatic Input Validation

One special Formz feature is the automatic validation of multiple inputs. To do so, create a class that extends FormzMixin and add as parameters different Formz Models:

class LoginForm with FormzMixin {
LoginForm({
this.email= const EmailInput.pure(),
this.password = const PasswordInput.pure(),
});

final EmailInput email;
final PasswordInput password;

@override
List<FormzInput> get inputs => [email, password];
}

void main() {
// The output would be false in this case
print(LoginForm().isValid);
}

Caching Validations

Some validations are quite expensive to compute, e.g. the input is pretty complex. To avoid unnecessary validations, you can simply cache your validations of different inputs by using FormzInputErrorCacheMixin mixin.

Here is an example from the formz documentation:

import 'package:formz/formz.dart';

enum EmailValidationError { invalid }

class Email extends FormzInput<String, EmailValidationError>
with FormzInputErrorCacheMixin { // It's literally just this, nothing else
Email.pure([super.value = '']) : super.pure();

Email.dirty([super.value = '']) : super.dirty();

static final _emailRegExp = RegExp(
r'^[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*$',
);

@override
EmailValidationError? validator(String value) {
return _emailRegExp.hasMatch(value) ? null : EmailValidationError.invalid;
}
}

Further Reading

Thank you for reading through this article.

If you’ve enjoyed this article, you are probably going to like other articles of me too. So consider following me.

You are probably going to like these articles a lot:

--

--

Tomic Riedel
Tomic Riedel

Written by Tomic Riedel

Sharing the process of building a portfolio of apps to make people more productive.

No responses yet