Flutter表单构建插件former的使用

Flutter表单构建插件former的使用

Motivation

在React中,Formik 是一个非常受欢迎的表单库。它通过大幅减少跟踪字段值、验证和表单提交所需的样板代码,极大简化了表单的开发过程。

在Flutter中,表单构建也面临类似的问题:

  • 开发者需要手动跟踪字段值,例如使用 TextEditingController
  • 验证和错误处理需要编写显式的逻辑。

为了解决这些问题,former 应运而生。


Installation

当前最新版本为:0.2.0

注意:此包仍处于预发布阶段,未来API可能会发生重大变化。

在你的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  # 其他依赖...
  former: # 可选锁定版本

dev_dependencies:
  # 其他依赖...
  former_gen: # 可选锁定版本

然后运行以下命令以安装依赖:

flutter pub get

Features

former 提供以下功能:

  • 全局启用/禁用表单
  • 声明式表单验证
  • 自动跟踪字段值
  • 使用 FormerError 小部件轻松处理错误
  • 类型安全的表单访问

Usage

以下是 former 的使用方法。

创建表单

former 通过分析你的表单类并生成相应的代码来与 former API 配合使用。

首先,在 my_form.dart 中创建表单类:

import 'package:former_gen/former_gen.dart';

@Formable()
abstract class _MyForm extends FormerForm {
}

需要注意以下几点:

  • 表单类是抽象且私有的,因为需要混合一些逻辑才能被 former 使用。
  • 表单类扩展自 FormerForm,它将表单类与 former 内部进行接口化。

FormerForm 要求子类实现方括号运算符。由于这是抽象类,这种负担将由 former 的代码生成器处理。我们只需要实现 submit 方法即可。例如,可以提交表单到某个API进行进一步处理。

为了简单起见,我们的 submit 方法仅返回一个空的 Future

@Formable()
abstract class _MyForm extends FormerForm {
  [@override](/user/override)
  Future<void> submit(BuildContext context) {
    // TODO: 实现提交逻辑
    return Future.value();
  }
}

接下来,为表单添加一些字段:

@Formable()
abstract class _MyForm extends FormerForm {
  String username = '';
  String email = '';

  [@override](/user/override)
  Future<void> submit(BuildContext context) {
    return Future.value();
  }
}

直到我们混入生成的混合类(使表单支持方括号运算符并包含字段类型信息),该表单类才可用。在类声明之前添加以下代码:

class MyForm = _MyForm with _$MyForm;

同时添加以下导入语句以引入生成的代码:

part 'my_form.g.dart';

Dart 分析器会提示未识别的符号和导入。要解决此问题,请通过 build_runner 启动代码生成:

flutter pub run build_runner build

指定表单需求

假设我们的表单有以下要求:

  • 用户名长度应在10到50个字符之间。
  • 邮箱字段必须包含有效的邮箱地址。

my_form.dart 中创建表单模式类:

final schema = MyFormSchema(
  username: StringMust()
    ..hasMinLength(10)
    ..hasMaxLength(50),
  email: StringMust()
    ..beAnEmail(),
);

可以看到,API 非常直观。注意使用了级联操作符 ..,在 Dart 中,它比返回 this 更常用。


构建表单控件

former 导出了各种与表单交互的小部件。首先,创建表单小部件:

import 'package:flutter/material.dart';
import 'package:former/former.dart';

import 'my_form.dart';

class Form extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      children: [
        FormerTextField<MyForm>(field: MyFormField.username),
        FormerTextField<MyForm>(field: MyFormField.email),
        ElevatedButton(
            onPressed: () {
              Former.of<MyForm>(context, listen: false).submit();
            },
            child: Text('提交表单')),
      ],
    );
  }
}

表单包含两个文本字段,分别控制 usernameemail 字段。MyFormField 类是自动生成的,无需手动创建。

当按钮被点击时,调用 MyFormsubmit 方法提交表单。此外,Former.of(context) 还提供了以下功能:

  • 访问当前表单的 .form 属性。例如,可以通过 Former.of<MyForm>(context).form.username 获取用户名字段的当前值。
  • 使用 .isFormEnabled 属性启用或禁用表单。
  • 使用 .errorOf(field) 获取指定字段的错误信息。

这是一个极简化的表单示例,实际应用中每个 Former 控件应附带描述其用途的标签。目前,这需要手动完成。


完整示例

最后,将表单包裹在 Former 小部件中:

import 'package:flutter/material.dart';
import 'package:former/former.dart';

import 'my_form.dart';

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Former(
          form: () => MyForm(),
          schema: () => schema, // 导出自 my_form.dart
          child: _MyForm(),
        ),
      ),
    );
  }
}

Source Code

完整的源代码可以在 example 文件夹中找到。


API

可用的小部件

以下小部件可用于 former

  • FormerTextField
  • FormerCheckbox
  • FormerSwitch
  • FormerSlider

正在开发中的小部件:

  • FormerRadio
  • FormerDropdownButton

Schema

以下验证器可用于表单字段。每个验证器都有各种方法对给定值施加额外的要求(称为“需求方法”)。

每种需求方法都接受可选的错误消息参数,当值不符合要求时返回该消息。

可用的验证器:

  • StringMust
  • NumberMust
  • BoolMust

可以通过实现 Validator 类来自定义验证逻辑。


示例代码

以下是完整示例代码:

import 'package:example/my_form.dart';
import 'package:flutter/material.dart';
import 'package:former/former.dart';
import 'package:former/validators.dart';

void main() {
  runApp(FormerExampleApp());
}

class FormerExampleApp extends StatelessWidget {
  const FormerExampleApp();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Former 示例',
      home: Scaffold(
        body: SafeArea(
          child: Former<MyForm>(
            form: () => MyForm(),
            schema: () => MyFormSchema(
              username: StringMust()
                ..hasMinLength(10)
                ..hasMaxLength(50),
              email: StringMust()..beAnEmail(),
              age: NumberMust()
                ..beAtLeast(1)
                ..beAtMost(150),
              shouldEnableAnalytics: BoolMust()..exist(),
              shouldSendNewsletter: BoolMust()..exist(),
            ),
            child: _Form(),
          ),
        ),
      ),
    );
  }
}

class _Form extends StatefulWidget {
  const _Form();

  [@override](/user/override)
  __FormState createState() => __FormState();
}

class __FormState extends State<_Form> {
  bool _isFormEnabled = true;

  void _toggleForm(bool isEnabled) {
    Former.of(context, listen: false).isFormEnabled = isEnabled;
    setState(() {
      _isFormEnabled = isEnabled;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('启用表单?'),
            Switch(
              value: _isFormEnabled,
              onChanged: _toggleForm,
            ),
          ],
        ),
        FormerTextField<MyForm>(field: MyFormField.username),
        FormerError<MyForm>(field: MyFormField.username),
        FormerTextField<MyForm>(field: MyFormField.email),
        FormerError<MyForm>(field: MyFormField.email),
        FormerSlider<MyForm>(field: MyFormField.age, min: 1, max: 100),
        FormerError<MyForm>(field: MyFormField.age),
        FormerCheckbox<MyForm>(field: MyFormField.shouldEnableAnalytics),
        FormerError<MyForm>(field: MyFormField.shouldEnableAnalytics),
        FormerSwitch<MyForm>(field: MyFormField.shouldSendNewsletter),
        FormerError<MyForm>(field: MyFormField.shouldSendNewsletter),
        ElevatedButton(
          onPressed: () {
            Former.of<MyForm>(context, listen: false).submit();
          },
          child: Text('提交'),
        ),
      ],
    );
  }
}

更多关于Flutter表单构建插件former的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter表单构建插件former的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


former 是一个用于简化 Flutter 表单构建的插件,它提供了一种声明式的方式来处理表单的验证、状态管理和提交逻辑。使用 former,你可以更轻松地管理表单字段的状态,并减少样板代码。

安装 former

首先,你需要在 pubspec.yaml 文件中添加 former 依赖:

dependencies:
  flutter:
    sdk: flutter
  former: ^0.1.0  # 请检查最新版本

然后运行 flutter pub get 来安装依赖。

使用 former 构建表单

1. 创建表单模型

首先,你需要创建一个表单模型类,该类将定义表单中的字段及其验证规则。这个类需要继承自 FormerForm

import 'package:former/former.dart';

class LoginForm extends FormerForm {
  final email = FormerField(
    validator: (value) {
      if (value == null || value.isEmpty) {
        return 'Email is required';
      }
      if (!value.contains('@')) {
        return 'Invalid email address';
      }
      return null;
    },
  );

  final password = FormerField(
    validator: (value) {
      if (value == null || value.isEmpty) {
        return 'Password is required';
      }
      if (value.length < 6) {
        return 'Password must be at least 6 characters';
      }
      return null;
    },
  );

  [@override](/user/override)
  List<FormerField> get fields => [email, password];
}

2. 使用 Former 组件

接下来,你可以在你的 UI 中使用 Former 组件来构建表单。Former 组件需要一个 form 参数,它是你之前定义的表单模型。

import 'package:flutter/material.dart';
import 'package:former/former.dart';

class LoginScreen extends StatelessWidget {
  final form = LoginForm();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Login'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Former(
          form: form,
          child: Column(
            children: [
              TextFormField(
                decoration: InputDecoration(labelText: 'Email'),
                onChanged: (value) => form.email.value = value,
                validator: (value) => form.email.error,
              ),
              SizedBox(height: 16),
              TextFormField(
                decoration: InputDecoration(labelText: 'Password'),
                obscureText: true,
                onChanged: (value) => form.password.value = value,
                validator: (value) => form.password.error,
              ),
              SizedBox(height: 24),
              ElevatedButton(
                onPressed: () {
                  if (form.validate()) {
                    // 表单验证通过,执行登录逻辑
                    print('Email: ${form.email.value}');
                    print('Password: ${form.password.value}');
                  }
                },
                child: Text('Login'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
回到顶部