Form Validation
How to write a Form Validation Flutter application with UtopiaHooks.
To visit Form Validation repository press HERE.
Project Structure
|- form_validation.dart
|- validation
| |- form_validation_page.dart - Coordinator between state & view layers
| |- state
| | |- form_validation_page_state.dart - Layer that definies State and Hook responsible for business-logic
| |- view
| | |- form_validation_page_view.dart
|- util
| |- app_validators.dart
| |- app_regex_patterns.dart
For more info about our recommended directory structure visit our Guides.
FormValidationPageState 🔗
form_validation_page_state.dart consists of two segments: FormValidationPageState object
and useFormValidationPageState Hook responsible
for state management.
FormValidationPageState contains everything necessary for View, including variables, functions and getters.
useFormValidationPageState serves as a wrapper for all the necessary hooks for FormValidationPage's business-logic.
In this use-case
it's consisted of two hooks:
| Hooks | Description |
|---|---|
| useFieldState | Hook responsible for TextField state management |
| useSubmitState | Hook responsible for handling form submission |
Whole validation is wrapped in a SubmitState. In this case it simulates synchronous validation combined with
an asynchronous API query. shouldSubmit is made of two segments. First checking whether SubmitState is already in
progress and second validating FieldStates.
Field validation is created through FieldState's built-in error handling mechanism. It implements a validate
function, which accepts a Validator and returns a nullable ValidatorResult.
If the ValidatorResult returns null a validate function
will return true, meaning that there are no problems. Otherwise, the ValidatorResult will be assigned to
the FieldState errorMessage and a validate function will return false, meaning that a problem has occurred.
ValidatorResult is a builder of an error message, that can be used in the View layer of the application. Underneath it
is a simple String Function(BuildContext) function.
import 'package:utopia_hooks/utopia_hooks.dart';
import 'package:utopia_hooks_example/form_validation/util/app_validators.dart';
import 'package:utopia_validation/utopia_validation.dart';
class FormValidationPageState {
final FieldState emailState, passwordState, repeatPasswordState;
final bool isInProgress;
final void Function() onSubmitPressed;
const FormValidationPageState({
required this.emailState,
required this.passwordState,
required this.repeatPasswordState,
required this.isInProgress,
required this.onSubmitPressed,
});
}
FormValidationPageState useFormValidationPageState() {
//declaration of FieldStates
final emailState = useFieldState();
final passwordState = useFieldState();
final repeatPasswordState = useFieldState();
//declaration of SubmitState
final submitState = useSubmitState();
//extracted FieldStates validation
bool validate() {
return [
emailState.validate(AppValidators.emailValidator),
passwordState.validate(AppValidators.passwordValidator),
repeatPasswordState.validate(AppValidators.repeatPasswordValidator(passwordState.value)),
].every((e) => e);
}
// mock async query for demonstration purposes
Future<void> mockQuery() async => Future.delayed(const Duration(seconds: 1));
// submit function implementing SubmitState's run function
Future<void> submit() async {
await submitState.runSimple<void, Never>(
shouldSubmit: () => !submitState.inProgress && validate(),
submit: mockQuery,
);
}
return FormValidationPageState(
emailState: emailState,
passwordState: passwordState,
repeatPasswordState: repeatPasswordState,
isInProgress: submitState.inProgress,
onSubmitPressed: submit,
);
}
AppValidators 🔗
AppValidators file is a helper extracting all static validations that are used in the FormValidatorPageState.
To get more info consider checking the utopia_validation package.
import 'package:utopia_hooks_example/form_validation/util/app_regex_patterns.dart';
import 'package:utopia_validation/utopia_validation.dart';
class AppValidators {
AppValidators._();
static Validator<String> passwordValidator = Validators.combine<String>([
notEmpty,
Validators.conditional<String>(
(value) => value.length < 8 || !RegexPattern.passwordExp.hasMatch(value),
onFalse: (context) =>
"Password needs to be 8 characters long and consist of small letters, big letters, a number and a special character",
),
]);
static Validator<String> repeatPasswordValidator(String password) {
return Validators.combine([
notEmpty,
Validators.conditional(
(value) => value != password,
onFalse: (context) => "Passwords do not match",
),
]);
}
static Validator<String> emailValidator = Validators.combine([
notEmpty,
Validators.conditional(
(value) => !RegexPattern.emailExp.hasMatch(value),
onFalse: (context) => "Incorrect email structure",
),
]);
static final notEmpty = Validators.notEmpty(onEmpty: (context) => "Field cannot be empty");
}
FormValidationPage - Coordinator 🔗
We start with a simple wrapper that serves as a bridge between our state and view. In this simple case it's only
responsible
for initializing FormValidationPageState.
In more complex structures, it also provides Flutter-related functions, such
as Navigator.of(context).push to useFormValidationPageState Hook.
class FormValidationPage extends StatelessWidget {
const FormValidationPage();
Widget build(BuildContext context) {
return const HookCoordinator(
use: useFormValidationPageState,
builder: FormValidationPageView.new,
);
}
}
FormValidationPageView 🔗
FormValidationPageView is responsible for displaying TextFields and a SubmitButton It uses the FormValidationPageState
passed by FormValidationPage.
import 'package:flutter/material.dart';
import 'package:utopia_hooks/utopia_hooks.dart';
import 'package:utopia_hooks_example/form_validation/validation/state/form_validation_page_state.dart';
class FormValidationPageView extends StatelessWidget {
final FormValidationPageState state;
const FormValidationPageView(this.state);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("Flutter demo form validation page"),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildTextField(state.emailState, "E-mail"),
_buildTextField(state.passwordState, "Password"),
_buildTextField(state.repeatPasswordState, "Repeat password"),
const Spacer(),
_buildButton(),
],
),
);
}
// handle submit and loading state
Widget _buildButton() {
return ElevatedButton(
onPressed: state.onSubmitPressed,
child: state.isInProgress ? const CircularProgressIndicator() : const Text("Validate"),
);
}
Widget _buildTextField(FieldState state, String label) {
return Builder(
builder: (context) {
final errorMessage = state.errorMessage;
// TextEditingControllerWrapper is a necessary Widget
// for FieldState's TextEditingController to work properly
return TextEditingControllerWrapper(
text: state,
builder: (controller) => TextField(
controller: controller,
decoration: InputDecoration(
label: Text(label),
// display error message if not null
error: errorMessage != null ? Text(errorMessage(context)) : null,
),
),
);
},
);
}
}
AppRegexPatterns
AppRegexPatterns file contains necessary regex patterns for the validation. In this case it consists of password and e-mail patterns.
class RegexPattern {
static final RegExp emailExp = RegExp(
r"^([a-zA-Z0-9_\-])([a-zA-Z0-9_\-.]*)@(\[((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}|((([a-zA-Z0-9\-]+)\.)+))([a-zA-Z]{2,}|(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])])$",
);
static final RegExp passwordExp = RegExp(r"(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)");
}
Final words
Keep in mind that this is only our proposed combination of a state implementing both error handling and TextField state
management. If this does not look clear, do not hesitate to play around and separate error handling with a
simple useState<String> Hook.
See also
- useFieldState - field value plus built-in error handling for each
TextField - useSubmitState - tracks the in-progress flag and runs the submit
- TextEditingControllerWrapper - bridges a
FieldStateto aTextEditingController - Forms and fields - the wider guide to building forms with hooks
Crafted for you by UtopiaSoftware 👾