Flutter表单构建插件flex_form的使用
Flutter表单构建插件flex_form的使用
概述
表单开发通常涉及大量的样板代码,从定义表单UI到管理用户交互,再到数据加载、验证和提交。根据表单的复杂性和应用程序所需的表单数量,这些表单的开发和维护可能会变得具有挑战性。
这就是FlexForm诞生的原因。它旨在实现以下目标:
- ✅ 负责所有常见的任务,以便开发者无需为每个表单重复相同的工作。
- ✅ 通过使其高度可配置和可扩展来简化表单开发。
- ✅ 支持所有类型的表单输入,同时允许开发者自定义表单UI。
- ✅ 允许扩展FlexForm的功能,使开发者能够定义其表单所需的功能,如特定于每个表单的验证逻辑。
- ✅ 使表单代码模块化,以便表单组件可以独立维护和测试。
特点
- ✅ 灵活性 - 支持包括文本、单选、多选和切换在内的所有流行输入类型。
- ✅ 可配置 - 提供字段配置,可在运行时配置表单字段功能。例如,
allowEmpty
、minChars
、maxChars
、regexList
等。 - ✅ 可扩展 - 简化从简单的登录表单到包含任意数量字段的表单的开发。
- ✅ 定制化 - 基于业务需求扩展FlexForm组件以定制表单逻辑。
- ✅ UI无关 - FlexForm不规定表单的外观和感觉。相反,它处理所有基本的表单输入交互,并提供应用用于重建表单的实时更新表单状态。
- ✅ 模块化 - FlexForm的架构使开发者可以将表单组件分离,以便它们可以独立维护。
开始使用
FlexForm的架构基于BLoC设计模式。具体来说,FlexForm依赖于bloc和flutter_bloc,这两个包实现了BLoC并提供了非常好的状态管理解决方案。
FlexForm支持Flutter支持的所有平台。只需在Flutter项目的pubspec.yaml
中添加flex_form
依赖即可。但你仍然需要阅读一些文档来了解如何使用FormFlex。
不用担心,我们会继续改进它,使其更易于使用。
使用方法
架构
以下是构成Flex Form的组件:
- FormBloc - 一个继承自
Bloc
类的基础类,提供表单的所有功能。它需要一个FormValidationProvider<FormDataEntity>
和一个FormInputDataMapper
,这些是扩展FormBloc
所需的。 - FormValidationProvider - 定义验证功能的接口,用于基类
FormBloc
在验证表单时使用。 - FormDataProvider - 可选组件,用于加载和提交表单。这对于需要从远程API加载或提交数据的表单很有用。
- FormInputDataMapper - 定义将表单的“呈现”数据(即
Map<FormFieldId, dynamic>
)映射到表单的数据模型(即FormDataEntity
)的功能的接口。 - FormDataEntity - 一个扩展
Equatable
的基础类,由表单扩展以表示表单的数据。 - FormFieldConfig - 定义一组配置,用于
FormBloc
处理表单字段触发的特定事件(例如,OnChange等)。 - InputViewModel - 包括定义基于字段类型的表单字段数据的类(例如,
FormFieldConfig.text
、FormFieldConfig.singleSelect
、FormFieldConfig.multipleSelect
和FormFieldConfig.toggledValue
)的联合类。 - FormVM - 在表单生命周期内包含整个表单的数据。
- FormComponent - 一个Flutter小部件,用于包裹要构建的表单UI。
FormComponent
允许表单指定FormDataBuilder
或FormInputBuilder
。使用FormDataBuilder
构建带有自定义UI的表单,使用FormInputBuilder
构建带有TextInputs
集的表单。 - validateField - 一个用于验证表单字段值的实用函数。
- getValidationMessage - 一个获取无效表单字段验证消息的实用函数。
如何实现表单
- 扩展
FormDataEntity
类以定义表单的数据,并覆盖Equatable
的props
getter。这是FormValidationProvider
检查表单在输入更改时是否有效所必需的。 - 实现
FormInputDataMapper
接口,以提供表单输入值与相应的FormDataEntity
之间的映射功能。 - 扩展
FormBloc
类,并提供一个构造函数,该构造函数可用于使用初始FormBlocState
、Map<FormFieldId, FormFieldConfig>
输入配置映射、InputDataMapper
实例、可选的FormValidationProvider
和可选的FormDataProvider
创建表单区块的实例。 - 若要实现自定义验证逻辑,请扩展
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!),
)
],
);
},
)
额外信息
-
要运行示例的实时演示,配置melos并使用它来运行
demo
包。运行melos build:form-demo
以构建包,然后运行flutter run -t demo/lib/main.dart -d chrome
(或melos run:demo
)以运行演示Flutter应用。 -
有关FlexForm工作原理的更多详细信息,请参阅这篇Medium文章。
开发者
如何构建FormFlex
-
运行以下命令以清理并构建项目中的所有包(即
flex_form
和flex_form_demo
)。melos clean-build:form
-
作为可选项,运行以下命令以单独构建每个包。
melos build:form
melos build:form-demo
-
在打开PR之前进行项目分析。
melos analyze
如何测试表单更改
-
要查看表单示例的实时演示,请运行以下命令。
melos run:demo
更多关于Flutter表单构建插件flex_form的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于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 # 请替换为实际的最新版本号
然后,你可以按照以下步骤来构建表单:
- 定义表单配置 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."
}
}
]
}
- 在 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'),
),
],
),
),
);
}
}
在这个示例中,我们:
- 在
pubspec.yaml
中添加了flex_form
依赖。 - 定义了一个 JSON 配置,其中包含表单字段的定义。
- 在 Dart 代码中,加载了 JSON 配置并创建了
FlexFormController
实例。 - 使用
FlexFormField
小部件来根据配置动态构建表单字段。 - 在表单提交按钮的点击事件中,验证了表单并打印了表单数据。
这样,你就可以使用 flex_form
插件来构建和管理复杂的动态表单了。