Flutter表单辅助展示插件form_companion_presenter的使用

Flutter表单辅助展示插件form_companion_presenter的使用

通过form_companion_presenter,您可以轻松简化与Form相关的开发工作,并采用良好的应用程序结构。借助form_companion_generator,可以消除样板代码!

如果您使用了Flutter FormBuilder,可以查看其兄弟包form_builder_companion_presenter,它专门针对Flutter FormBuilder


特性

  • 使用form_companion_generator,可以消除样板代码。
  • 将“呈现逻辑”从Widget中分离出来,使其可测试。
    • 轻松绑定属性和事件处理器到表单字段。
  • 从表单使用代码中移除繁琐的工作。
    • 启用提交按钮,当没有验证错误时。
    • 组合多个验证器以应用于单个FormField
  • 支持异步验证,并带有节流和取消功能。
  • 提供一种最佳实践来获取经过验证的值。无需再考虑onSaveonChangeTextControllerKey等。
  • 即使使用DropdownButtonFormField,也可以支持状态恢复。

开始使用

安装

在您的pubspec.yaml文件中添加依赖项并运行以下命令:

flutter pub add form_companion_presenter
使用步骤
前提条件

本示例使用了riverpodform_companion_generator,这是推荐的方式。

  1. dependencies中添加以下包:

    dependencies:
      riverpod: # 替换为所需的版本号(高于2.0.0)
      riverpod_annotation: # 替换为所需的版本号
      form_companion_presenter: # 替换为所需的版本号
    
  2. dev_dependencies中添加以下包:

    dev_dependencies:
      build_runner: # 替换为所需的版本号(高于2.0.0)
      riverpod_generator: # 替换为所需的版本号
      form_companion_generator: # 替换为所需的版本号
    
  3. (可选)在项目的根目录下创建build.yaml文件,并进行配置(详见build_config文档和form_companion_generator文档)。

步骤
  1. 声明一个presenter类。本示例使用riverpodform_companion_generator

    [@riverpod](/user/riverpod)
    [@formCompanion](/user/formCompanion)
    class MyPresenter extends _$MyPresenter {
      MyPresenter() {}
    
      [@override](/user/override)
      FutureOr<MyPresenterFormProperties> build() async {}
    }
    
  2. presenter中使用with关键字引入CompanionPresenterMixinFormCompanionMixin

    [@riverpod](/user/riverpod)
    [@formCompanion](/user/formCompanion)
    class MyPresenter extends _$MyPresenter
      with CompanionPresenterMixin, FormCompanionMixin {
      MyPresenter() {}
    
      [@override](/user/override)
      FutureOr<MyPresenterFormProperties> build() async {}
    }
    
  3. presenter的构造函数中调用initializeCompanionMixin()方法,并声明属性:

    MyPresenter() {
        initializeCompanionMixin(
          PropertyDescriptorBuilder()
          ..string(
            name: 'name',
            validatorFactories: [
              (context) => (value) => (value ?? '').isEmpty ? 'Name is required.' : null,
            ],
          )
          ..integerText(
            name: 'age',
            validatorFactories: [
              (context) => (value) => (value ?? '').isEmpty ? 'Age is required.' : null,
              (context) => (value) => int.parse(value!) < 0 ? 'Age must not be negative.' : null,
            ],
          )
        );
    }
    
  4. 实现build方法以获取上游状态并将其填充为属性的初始状态:

    [@override](/user/override)
    FutureOr<MyPresenterFormProperties> build() async {
      final upstreamState = await ref.watch(upstreamStateProvider.future);
      return resetProperties(
        (properties.copyWith()
        ..name(upstreamState.name)
        ..age(upstreamState.age)
        ).build()
      );
    }
    
  5. 在文件顶部添加part指令:

    part 'example.fcp.dart';
    part 'example.g.dart';
    
  6. 运行build_runner(例如,运行flutter pub run build_runner build -d)。riverpod_generator会生成全局属性及相关类型,而form_companion_generator会生成$MyPresenterFormStates及相关扩展。

  7. presenter中实现doSubmit方法以处理整个表单的提交操作:

    [@override](/user/override)
    FutureOr<void> doSubmit(BuildContext context) async {
      // 获取已验证的输入值
      String name = properties.values.name;
      int age = properties.values.age;
      // 调用业务逻辑
      ...
      // 设置状态以暴露给应用的其他组件
      ref.read(anotherStateProvider).state = AsyncData(MyState(name: name, age: age));
      // 更多操作...
    }
    
  8. 创建一个Widget。这里我们使用ConsumerWidget。注意,必须将FormFormFields放在单独的Widget中:

    class MyForm extends StatelessWidget {
      [@override](/user/override)
      Widget build(BuildContext context) => Form(
        child: MyFormFields(),
      );
    }
    
    class MyFormFields extends ConsumerWidget {
      [@override](/user/override)
      Widget build(BuildContext context, WidgetRef ref) {
        final state = ref.watch(myPresenterProvider);
        return Column(
          children: [
            state.value.fields.name(
              context,
              decoration: InputDecoration(
                labelText: 'Name',
              ),
            ),
            state.value.fields.age(
              context,
              decoration: InputDecoration(
                labelText: 'Age',
              ),
            ),
            ElevatedButton(
              onTap: state.value.submit(context),
              child: Text('Submit'),
            ),
          ],
        );
      }
    }
    

如果设置AutovalidateMode.disabled(默认值)给Form,可以在doSubmit()开头执行验证:

[@override](/user/override)
FutureOr<void> doSubmit(BuildContext context) async{
  if (!await validateAndSave(context)) {
    return;
  }

  // 其他代码...
}

状态恢复

启用状态恢复后,可以改善表单输入体验,因为它会在应用被移动操作系统终止时恢复表单数据。试想一下,如果在打开浏览器查找如何填写表单字段时丢失了输入数据,这会是多么令人沮丧的事情?浏览器通常会占用大量内存,因此您的应用可能会频繁被终止。

要启用状态恢复,只需在Form下方放置FormPropertiesRestorationScope

class MyForm extends ConsumerWidget {
  [@override](/user/override)
  Widget build(BuildContext context, WidgetRef ref) {
    final presenter = ref.read(myPresenterProvider.notifier);
    return Form(
      child: FormPropertiesRestorationScope(
        presenter: presenter,
        child: MyFormFields(),
      ),
    );
  }
}

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

1 回复

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


form_companion_presenter 是一个用于 Flutter 的插件,旨在简化表单的创建和管理。它提供了一种声明式的方式来处理表单字段的验证、状态管理和错误展示。以下是如何使用 form_companion_presenter 插件的基本步骤:

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  form_companion_presenter: ^1.0.0  # 请使用最新版本

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

2. 创建表单

接下来,你可以使用 FormCompanionPresenter 来创建和管理表单。以下是一个简单的示例:

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

class MyForm extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Form Companion Presenter Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: FormCompanionPresenter(
          fields: [
            FormFieldCompanion(
              key: 'email',
              label: 'Email',
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your email';
                }
                if (!value.contains('@')) {
                  return 'Please enter a valid email';
                }
                return null;
              },
            ),
            FormFieldCompanion(
              key: 'password',
              label: 'Password',
              obscureText: true,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your password';
                }
                if (value.length < 6) {
                  return 'Password must be at least 6 characters';
                }
                return null;
              },
            ),
          ],
          onSubmit: (Map<String, String> values) {
            // 处理表单提交
            print('Form submitted with values: $values');
          },
          submitButton: ElevatedButton(
            onPressed: () {
              // 提交表单
              FormCompanionPresenter.of(context).submit();
            },
            child: Text('Submit'),
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: MyForm(),
  ));
}
回到顶部