Flutter表单构建插件flex_form的使用

Flutter表单构建插件flex_form的使用

概述

表单开发通常涉及大量的样板代码,从定义表单UI到管理用户交互,再到数据加载、验证和提交。根据表单的复杂性和应用程序所需的表单数量,这些表单的开发和维护可能会变得具有挑战性。

这就是FlexForm诞生的原因。它旨在实现以下目标:

  1. ✅ 负责所有常见的任务,以便开发者无需为每个表单重复相同的工作。
  2. ✅ 通过使其高度可配置和可扩展来简化表单开发。
  3. ✅ 支持所有类型的表单输入,同时允许开发者自定义表单UI。
  4. ✅ 允许扩展FlexForm的功能,使开发者能够定义其表单所需的功能,如特定于每个表单的验证逻辑。
  5. ✅ 使表单代码模块化,以便表单组件可以独立维护和测试。

特点

  1. 灵活性 - 支持包括文本、单选、多选和切换在内的所有流行输入类型。
  2. 可配置 - 提供字段配置,可在运行时配置表单字段功能。例如,allowEmptyminCharsmaxCharsregexList等。
  3. 可扩展 - 简化从简单的登录表单到包含任意数量字段的表单的开发。
  4. 定制化 - 基于业务需求扩展FlexForm组件以定制表单逻辑。
  5. UI无关 - FlexForm不规定表单的外观和感觉。相反,它处理所有基本的表单输入交互,并提供应用用于重建表单的实时更新表单状态。
  6. 模块化 - FlexForm的架构使开发者可以将表单组件分离,以便它们可以独立维护。

开始使用

FlexForm的架构基于BLoC设计模式。具体来说,FlexForm依赖于blocflutter_bloc,这两个包实现了BLoC并提供了非常好的状态管理解决方案。

FlexForm支持Flutter支持的所有平台。只需在Flutter项目的pubspec.yaml中添加flex_form依赖即可。但你仍然需要阅读一些文档来了解如何使用FormFlex。

不用担心,我们会继续改进它,使其更易于使用。

使用方法

架构

以下是构成Flex Form的组件:

  1. FormBloc - 一个继承自Bloc类的基础类,提供表单的所有功能。它需要一个FormValidationProvider<FormDataEntity>和一个FormInputDataMapper,这些是扩展FormBloc所需的。
  2. FormValidationProvider - 定义验证功能的接口,用于基类FormBloc在验证表单时使用。
  3. FormDataProvider - 可选组件,用于加载和提交表单。这对于需要从远程API加载或提交数据的表单很有用。
  4. FormInputDataMapper - 定义将表单的“呈现”数据(即Map<FormFieldId, dynamic>)映射到表单的数据模型(即FormDataEntity)的功能的接口。
  5. FormDataEntity - 一个扩展Equatable的基础类,由表单扩展以表示表单的数据。
  6. FormFieldConfig - 定义一组配置,用于FormBloc处理表单字段触发的特定事件(例如,OnChange等)。
  7. InputViewModel - 包括定义基于字段类型的表单字段数据的类(例如,FormFieldConfig.textFormFieldConfig.singleSelectFormFieldConfig.multipleSelectFormFieldConfig.toggledValue)的联合类。
  8. FormVM - 在表单生命周期内包含整个表单的数据。
  9. FormComponent - 一个Flutter小部件,用于包裹要构建的表单UI。FormComponent允许表单指定FormDataBuilderFormInputBuilder。使用FormDataBuilder构建带有自定义UI的表单,使用FormInputBuilder构建带有TextInputs集的表单。
  10. validateField - 一个用于验证表单字段值的实用函数。
  11. getValidationMessage - 一个获取无效表单字段验证消息的实用函数。

如何实现表单

  1. 扩展FormDataEntity类以定义表单的数据,并覆盖Equatableprops getter。这是FormValidationProvider检查表单在输入更改时是否有效所必需的。
  2. 实现FormInputDataMapper接口,以提供表单输入值与相应的FormDataEntity之间的映射功能。
  3. 扩展FormBloc类,并提供一个构造函数,该构造函数可用于使用初始FormBlocStateMap<FormFieldId, FormFieldConfig>输入配置映射、InputDataMapper实例、可选的FormValidationProvider和可选的FormDataProvider创建表单区块的实例。
  4. 若要实现自定义验证逻辑,请扩展FormValidationProvider基础类,并向FormBloc提供一个实例。

要查看FormFlex的实际效果,请查看以下使用FormInputBuilder(即textInputBuilder)和FormDataBuilder(即dataBuilder)分别构建更改密码表单的代码示例。

示例:使用TextInputBuilder构建表单

FormComponent(
  bloc: _changePasswordFormBloc,
  textInputBuilder: ({
    required textInputs,
    required context,
    buttonText,
    canSubmit,
    cancelButtonText,
    onSubmit,
  }) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: [
        Text(
          _changePasswordFormBloc.state.data.title!,
          style: Theme.of(context).textTheme.titleLarge,
        ),
        const SizedBox(height: 25),
        ...textInputs,
        const SizedBox(
          height: 35,
        ),
        TextButton(
          onPressed: canSubmit == true ? () {} : null,
          style: TextButton.styleFrom(
            backgroundColor: Theme.of(context).colorScheme.primary,
            foregroundColor:
                Theme.of(context).textTheme.titleLarge!.color,
          ),
          child: Text(buttonText!),
        )
      ],
    );
  },
)

示例:使用FormDataBuilder构建表单

FormComponent(
  bloc: _changePasswordFormBloc,
  dataBuilder: (
      {
      required inputMap,
      required context,
      buttonText,
      canSubmit,
      cancelButtonText,
      focusNodeMap,
      isLoading,
      isSubmitting,
      onChangeCallbackMap,
      onSubmit,
      textControllerMap}) {
    final oldPasswordViewModel =
        inputMap[FormFieldId.fd0] as TextInputViewModel;
    final newPasswordViewModel =
        inputMap[FormFieldId.fd1] as TextInputViewModel;
    final confirmNewPasswordViewModel =
        inputMap[FormFieldId.fd2] as TextInputViewModel;

    obscurityInputMap ??= <FormFieldId, bool>{
      FormFieldId.fd0: oldPasswordViewModel.isInputObscured,
      FormFieldId.fd1: newPasswordViewModel.isInputObscured,
      FormFieldId.fd2: confirmNewPasswordViewModel.isInputObscured,
    };
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: [
        Text(
          _changePasswordFormBloc.state.data.title!,
          style: Theme.of(context).textTheme.titleLarge,
        ),
        const SizedBox(height: 25),
        ...inputMap.keys.map(
          (fieldId) => Column(
            children: [
              const SizedBox(height: 10),
              StatefulBuilder(
                builder: (context, setState) {
                  return TextInput(
                    theme: theme.textInputTheme,
                    viewModel:
                        (inputMap[fieldId] as TextInputViewModel)
                            .copyWith(
                                isInputObscured:
                                    obscurityInputMap![fieldId]!),
                    focusNode: focusNodeMap![fieldId]!,
                    controller: textControllerMap![fieldId],
                    onChange: onChangeCallbackMap![fieldId]!,
                    suffixIcon: IconButton(
                      onPressed: () => setState(() =>
                          obscurityInputMap![fieldId] =
                              !obscurityInputMap![fieldId]!),
                      icon: const Icon(Icons.visibility),
                    ),
                  );
                },
              ),
            ],
          ),
        ),
        const SizedBox(
          height: 35,
        ),
        TextButton(
          onPressed: canSubmit == true ? () {} : null,
          style: TextButton.styleFrom(
            backgroundColor: Theme.of(context).colorScheme.primary,
            foregroundColor:
                Theme.of(context).textTheme.titleLarge!.color,
          ),
          child: Text(buttonText!),
        )
      ],
    );
  },
)

额外信息

  1. 要运行示例的实时演示,配置melos并使用它来运行demo包。运行melos build:form-demo以构建包,然后运行flutter run -t demo/lib/main.dart -d chrome(或melos run:demo)以运行演示Flutter应用。

    Form Demo

  2. 有关FlexForm工作原理的更多详细信息,请参阅这篇Medium文章

开发者

如何构建FormFlex

  1. 运行以下命令以清理并构建项目中的所有包(即flex_formflex_form_demo)。

    melos clean-build:form
    
  2. 作为可选项,运行以下命令以单独构建每个包。

    melos build:form
    
    melos build:form-demo
    
  3. 在打开PR之前进行项目分析。

    melos analyze
    

如何测试表单更改

  1. 要查看表单示例的实时演示,请运行以下命令。

    melos run:demo
    

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

1 回复

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


当然,flex_form 是一个用于在 Flutter 中构建动态表单的插件。它允许开发者通过 JSON 配置来定义表单字段,这样可以大大简化表单的创建和管理过程。以下是一个使用 flex_form 插件的示例代码,展示了如何根据 JSON 配置来构建表单。

首先,确保你已经在 pubspec.yaml 文件中添加了 flex_form 依赖:

dependencies:
  flutter:
    sdk: flutter
  flex_form: ^latest_version  # 请替换为实际的最新版本号

然后,你可以按照以下步骤来构建表单:

  1. 定义表单配置 JSON
{
  "fields": [
    {
      "type": "text",
      "name": "name",
      "label": "Name",
      "required": true,
      "validation": {
        "pattern": "^[a-zA-Z ]+$",
        "message": "Name must contain only letters and spaces."
      }
    },
    {
      "type": "email",
      "name": "email",
      "label": "Email",
      "required": true,
      "validation": {
        "pattern": "^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$",
        "message": "Please enter a valid email address."
      }
    },
    {
      "type": "number",
      "name": "age",
      "label": "Age",
      "required": true,
      "validation": {
        "min": 1,
        "max": 120,
        "message": "Age must be between 1 and 120."
      }
    }
  ]
}
  1. 在 Dart 代码中加载 JSON 配置并构建表单
import 'package:flutter/material.dart';
import 'package:flex_form/flex_form.dart';
import 'dart:convert';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flex Form Example'),
        ),
        body: MyFormPage(),
      ),
    );
  }
}

class MyFormPage extends StatefulWidget {
  @override
  _MyFormPageState createState() => _MyFormPageState();
}

class _MyFormPageState extends State<MyFormPage> {
  final _formKey = GlobalKey<FormState>();
  late FlexFormController _formController;

  @override
  void initState() {
    super.initState();
    // 加载 JSON 配置
    final formConfig = jsonDecode('''
    {
      "fields": [
        {
          "type": "text",
          "name": "name",
          "label": "Name",
          "required": true,
          "validation": {
            "pattern": "^[a-zA-Z ]+$",
            "message": "Name must contain only letters and spaces."
          }
        },
        {
          "type": "email",
          "name": "email",
          "label": "Email",
          "required": true,
          "validation": {
            "pattern": "^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$",
            "message": "Please enter a valid email address."
          }
        },
        {
          "type": "number",
          "name": "age",
          "label": "Age",
          "required": true,
          "validation": {
            "min": 1,
            "max": 120,
            "message": "Age must be between 1 and 120."
          }
        }
      ]
    }
    ''');

    // 创建 FlexFormController 并加载配置
    _formController = FlexFormController.fromJson(formConfig);
  }

  @override
  void dispose() {
    _formController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Form(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 构建表单字段
            for (var field in _formController.fields)
              Padding(
                padding: const EdgeInsets.only(bottom: 16.0),
                child: FlexFormField(
                  field: field,
                  controller: _formController,
                ),
              ),
            ElevatedButton(
              onPressed: () {
                if (_formKey.currentState!.validate()) {
                  // 获取表单数据
                  final formData = _formController.toMap();
                  print('Form Data: $formData');
                }
              },
              child: Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们:

  1. pubspec.yaml 中添加了 flex_form 依赖。
  2. 定义了一个 JSON 配置,其中包含表单字段的定义。
  3. 在 Dart 代码中,加载了 JSON 配置并创建了 FlexFormController 实例。
  4. 使用 FlexFormField 小部件来根据配置动态构建表单字段。
  5. 在表单提交按钮的点击事件中,验证了表单并打印了表单数据。

这样,你就可以使用 flex_form 插件来构建和管理复杂的动态表单了。

回到顶部