formz 0.5.0-dev.1 formz: ^0.5.0-dev.1 copied to clipboard
A unified form representation in Dart which aims to simplify form representation and validation in a generic way.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:formz/formz.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Formz Example')),
body: const Padding(
padding: EdgeInsets.all(24),
child: SingleChildScrollView(child: MyForm()),
),
),
);
}
}
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
@override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _key = GlobalKey<FormState>();
late MyFormState _state;
late final TextEditingController _emailController;
late final TextEditingController _passwordController;
void _onEmailChanged() {
setState(() {
_state = _state.copyWith(email: Email.dirty(_emailController.text));
});
}
void _onPasswordChanged() {
setState(() {
_state = _state.copyWith(
password: Password.dirty(_passwordController.text),
);
});
}
Future<void> _onSubmit() async {
if (!_key.currentState!.validate()) return;
setState(() {
_state = _state.copyWith(status: FormzSubmissionStatus.inProgress);
});
try {
await _submitForm();
_state = _state.copyWith(status: FormzSubmissionStatus.success);
} catch (_) {
_state = _state.copyWith(status: FormzSubmissionStatus.failure);
}
if (!mounted) return;
setState(() {});
FocusScope.of(context)
..nextFocus()
..unfocus();
const successSnackBar = SnackBar(
content: Text('Submitted successfully! 🎉'),
);
const failureSnackBar = SnackBar(
content: Text('Something went wrong... 🚨'),
);
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
_state.status.isSuccess ? successSnackBar : failureSnackBar,
);
if (_state.status.isSuccess) _resetForm();
}
Future<void> _submitForm() async {
await Future<void>.delayed(const Duration(seconds: 1));
if (Random().nextInt(2) == 0) throw Exception();
}
void _resetForm() {
_key.currentState!.reset();
_emailController.clear();
_passwordController.clear();
setState(() => _state = MyFormState());
}
@override
void initState() {
super.initState();
_state = MyFormState();
_emailController = TextEditingController(text: _state.email.value)
..addListener(_onEmailChanged);
_passwordController = TextEditingController(text: _state.password.value)
..addListener(_onPasswordChanged);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _key,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
icon: Icon(Icons.email),
labelText: 'Email',
helperText: 'A valid email e.g. joe.doe@gmail.com',
),
validator: (_) => _state.email.displayError?.text(),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
icon: Icon(Icons.lock),
helperText:
'At least 8 characters including one letter and number',
helperMaxLines: 2,
labelText: 'Password',
errorMaxLines: 2,
),
validator: (_) => _state.password.displayError?.text(),
obscureText: true,
textInputAction: TextInputAction.done,
),
const SizedBox(height: 24),
if (_state.status.isInProgress)
const CircularProgressIndicator()
else
ElevatedButton(
onPressed: _onSubmit,
child: const Text('Submit'),
),
],
),
);
}
}
class MyFormState with FormzMixin {
MyFormState({
this.email = const Email.pure(),
this.password = const Password.pure(),
this.status = FormzSubmissionStatus.initial,
});
final Email email;
final Password password;
final FormzSubmissionStatus status;
MyFormState copyWith({
Email? email,
Password? password,
FormzSubmissionStatus? status,
}) {
return MyFormState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
);
}
@override
List<FormzInput> get inputs => [email, password];
}
enum EmailValidationError { invalid }
class Email extends FormzInput<String, EmailValidationError> {
const Email.pure([String value = '']) : super.pure(value);
const Email.dirty([String value = '']) : super.dirty(value);
static final _emailRegExp = RegExp(
r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
);
@override
EmailValidationError? validator(String? value) {
return _emailRegExp.hasMatch(value ?? '')
? null
: EmailValidationError.invalid;
}
}
enum PasswordValidationError { invalid }
class Password extends FormzInput<String, PasswordValidationError> {
const Password.pure([String value = '']) : super.pure(value);
const Password.dirty([String value = '']) : super.dirty(value);
static final _passwordRegex =
RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$');
@override
PasswordValidationError? validator(String? value) {
return _passwordRegex.hasMatch(value ?? '')
? null
: PasswordValidationError.invalid;
}
}
extension on EmailValidationError {
String text() {
switch (this) {
case EmailValidationError.invalid:
return 'Please ensure the email entered is valid';
}
}
}
extension on PasswordValidationError {
String text() {
switch (this) {
case PasswordValidationError.invalid:
return '''Password must be at least 8 characters and contain at least one letter and number''';
}
}
}