Flutter表单管理插件formz的使用

发布于 1周前 作者 h691938207 来自 Flutter

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

1 回复

更多关于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'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们创建了两个表单字段UsernamePassword,并定义了它们的验证规则。然后,我们创建了一个包含这些字段的LoginForm类,并在UI中使用这个表单状态来管理输入和验证。

注意:在实际应用中,你可能需要更复杂的验证逻辑和错误处理。formz库提供了一个灵活的基础,可以根据你的需求进行扩展。

回到顶部