Flutter表单管理插件formz的使用
Flutter表单管理插件formz的使用
Formz是由Very Good Ventures开发的一个Dart库,旨在以统一的方式简化表单表示和验证。它允许开发者通过定义输入类型和错误类型来创建自定义的表单输入,并提供了方便的方法来进行输入验证。
创建一个FormzInput
首先,您需要定义输入验证错误枚举,然后扩展FormzInput
类并提供输入类型和错误类型。例如,创建一个用于用户名验证的NameInput
:
import 'package:formz/formz.dart';
// Define input validation errors
enum NameInputError { empty }
// Extend FormzInput and provide the input type and error type.
class NameInput extends FormzInput<String, NameInputError> {
// Call super.pure to represent an unmodified form input.
const NameInput.pure() : super.pure('');
// Call super.dirty to represent a modified form input.
const NameInput.dirty({String value = ''}) : super.dirty(value);
// Override validator to handle validating a given input value.
@override
NameInputError? validator(String value) {
return value.isEmpty ? NameInputError.empty : null;
}
}
与FormzInput交互
一旦定义了FormzInput
,您可以轻松地与其交互,检查其值是否有效或获取任何验证错误:
const name = NameInput.pure();
print(name.value); // ''
print(name.isValid); // false
print(name.error); // NameInputError.empty
print(name.displayError); // null
const joe = NameInput.dirty(value: 'joe');
print(joe.value); // 'joe'
print(joe.isValid); // true
print(joe.error); // null
print(name.displayError); // null
验证多个FormzInput项
如果您的表单包含多个输入项,可以使用Formz.validate()
方法一次性验证所有输入项:
const validInputs = <FormzInput>[
NameInput.dirty(value: 'jan'),
NameInput.dirty(value: 'jen'),
NameInput.dirty(value: 'joe'),
];
print(Formz.validate(validInputs)); // true
const invalidInputs = <FormzInput>[
NameInput.dirty(),
NameInput.dirty(),
NameInput.dirty(),
];
print(Formz.validate(invalidInputs)); // false
自动验证
对于更复杂的表单,您可以将FormzMixin
与状态类一起使用,以便自动处理表单的验证逻辑:
class LoginForm with FormzMixin {
LoginForm({
this.username = const Username.pure(),
this.password = const Password.pure(),
});
final Username username;
final Password password;
@override
List<FormzInput> get inputs => [username, password];
}
void main() {
print(LoginForm().isValid); // false
}
缓存验证结果
在某些情况下,验证逻辑可能较为复杂且耗时。这时可以使用FormzInputErrorCacheMixin
来缓存验证结果,提高性能:
import 'package:formz/formz.dart';
enum EmailValidationError { invalid }
class Email extends FormzInput<String, EmailValidationError>
with FormzInputErrorCacheMixin {
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;
}
}
示例代码:完整的登录表单
以下是一个使用formz
构建的完整登录表单示例:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:formz/formz.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Formz Example')),
body: Padding(
padding: const EdgeInsets.all(24),
child: SingleChildScrollView(child: MyForm()),
),
),
);
}
}
class MyForm extends StatefulWidget {
MyForm({super.key, Random? seed}) : seed = seed ?? Random();
final Random seed;
@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 (widget.seed.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(
key: const Key('myForm_emailInput'),
controller: _emailController,
decoration: const InputDecoration(
icon: Icon(Icons.email),
labelText: 'Email',
helperText: 'A valid email e.g. joe.doe@gmail.com',
),
validator: (value) => _state.email.validator(value ?? '')?.text(),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
),
TextFormField(
key: const Key('myForm_passwordInput'),
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: (value) =>
_state.password.validator(value ?? '')?.text(),
obscureText: true,
textInputAction: TextInputAction.done,
),
const SizedBox(height: 24),
if (_state.status.isInProgress)
const CircularProgressIndicator()
else
ElevatedButton(
key: const Key('myForm_submit'),
onPressed: _onSubmit,
child: const Text('Submit'),
),
],
),
);
}
}
class MyFormState with FormzMixin {
MyFormState({
Email? email,
this.password = const Password.pure(),
this.status = FormzSubmissionStatus.initial,
}) : email = email ?? Email.pure();
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<dynamic, dynamic>> get inputs => [email, password];
}
enum EmailValidationError { invalid, empty }
class Email extends FormzInput<String, EmailValidationError>
with FormzInputErrorCacheMixin {
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) {
if (value.isEmpty) {
return EmailValidationError.empty;
} else if (!_emailRegExp.hasMatch(value)) {
return EmailValidationError.invalid;
}
return null;
}
}
enum PasswordValidationError { invalid, empty }
class Password extends FormzInput<String, PasswordValidationError> {
const Password.pure([super.value = '']) : super.pure();
const Password.dirty([super.value = '']) : super.dirty();
static final _passwordRegex =
RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$');
@override
PasswordValidationError? validator(String value) {
if (value.isEmpty) {
return PasswordValidationError.empty;
} else if (!_passwordRegex.hasMatch(value)) {
return PasswordValidationError.invalid;
}
return null;
}
}
extension on EmailValidationError {
String text() {
switch (this) {
case EmailValidationError.invalid:
return 'Please ensure the email entered is valid';
case EmailValidationError.empty:
return 'Please enter an email';
}
}
}
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''';
case PasswordValidationError.empty:
return 'Please enter a password';
}
}
}
这个例子展示了如何使用formz
创建一个带有电子邮件和密码字段的简单登录表单,并进行相应的验证。希望这对您有所帮助!如果您有任何问题或需要进一步的帮助,请随时提问。
更多关于Flutter表单管理插件formz的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter表单管理插件formz的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter中使用formz
库进行表单管理的示例代码。formz
是一个用于在Flutter应用中管理表单状态和验证的库。它提供了一种声明式的方式来定义表单字段、它们的初始值和验证规则。
首先,确保你已经在pubspec.yaml
文件中添加了formz
库的依赖:
dependencies:
flutter:
sdk: flutter
formz: ^x.y.z # 请替换为最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,我们将创建一个简单的登录表单示例,其中包含用户名和密码字段。
1. 定义表单字段
首先,我们需要定义表单字段,包括它们的初始值和验证规则。
import 'package:formz/formz.dart';
class Username extends Input<String> {
final String validationMessage = 'Username is required';
@override
String get value => super.value ?? '';
Username(String value) : super(value);
static Username of(String value) {
return Username(value?.trim());
}
@override
List<ValidationMessage> validate() {
if (value.isEmpty) {
return [ValidationMessage(validationMessage)];
}
return [];
}
}
class Password extends Input<String> {
final String validationMessage = 'Password is required';
final int minLength = 6;
@override
String get value => super.value ?? '';
Password(String value) : super(value);
static Password of(String value) {
return Password(value?.trim());
}
@override
List<ValidationMessage> validate() {
if (value.isEmpty) {
return [ValidationMessage(validationMessage)];
}
if (value.length < minLength) {
return [ValidationMessage('Password must be at least $minLength characters long')];
}
return [];
}
}
2. 创建表单状态
接下来,我们创建一个包含这些字段的表单状态类。
import 'package:formz/formz.dart';
class LoginForm extends Form {
Username username = Username('');
Password password = Password('');
LoginForm() : super([username, password]);
// 可以添加自定义验证逻辑,如果需要的话
@override
List<ValidationMessage> validate() {
var messages = <ValidationMessage>[];
messages.addAll(username.validate());
messages.addAll(password.validate());
return messages;
}
// 提交表单时的逻辑
void submit() {
if (isValid) {
// 执行提交逻辑,比如发送数据到服务器
print('Username: ${username.value}');
print('Password: ${password.value}');
}
}
}
3. 创建UI
最后,我们在UI中使用这个表单状态。
import 'package:flutter/material.dart';
import 'package:formz/formz.dart';
import 'your_form_classes.dart'; // 替换为你的表单字段和状态类的实际路径
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginScreen(),
);
}
}
class LoginScreen extends StatefulWidget {
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final LoginForm form = LoginForm();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Username'),
value: form.username.value,
onChanged: (value) => form.username.updateValue(Username.of(value)),
validator: (value) =>
form.username.invalid ? form.username.validationMessage : null,
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
value: form.password.value,
onChanged: (value) => form.password.updateValue(Password.of(value)),
validator: (value) =>
form.password.invalid ? form.password.validationMessage : null,
),
SizedBox(height: 16),
ElevatedButton(
onPressed: form.isValid ? () => form.submit() : null,
child: Text('Submit'),
),
],
),
),
);
}
}
在这个示例中,我们创建了两个表单字段Username
和Password
,并定义了它们的验证规则。然后,我们创建了一个包含这些字段的LoginForm
类,并在UI中使用这个表单状态来管理输入和验证。
注意:在实际应用中,你可能需要更复杂的验证逻辑和错误处理。formz
库提供了一个灵活的基础,可以根据你的需求进行扩展。