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 Guide.
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 async queries |
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 nullable Validator
object.
If the ValidatorResult
returns null a validate
function
will return true, meaning that there are no problems. Otherwise, the ValidationResult 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_flutter.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 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_flutter.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;
// StatelessTextEditingControllerWrapper is a necessary Widget
// for FieldState's TextEditingController to work properly
return StatelessTextEditingControllerWrapper(
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.
Crafted specially for you by UtopiaSoftware π½