Flutter表单管理插件flutter_form_bloc的使用

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

Flutter表单管理插件flutter_form_bloc的使用

简介

flutter_form_bloc 是一个用于Flutter应用中轻松实现表单状态管理的插件。它基于BLoC(Business Logic Component)模式,将表单的状态和业务逻辑从业务界面中分离出来,从而简化了表单处理流程,并提高了代码的可维护性和复用性。

特性

  • ✅ 同步字段验证
  • ✅ 异步字段验证
  • ✅ 易于加载和初始化
  • ✅ 向导/分步表单
  • ✅ 提交进度跟踪
  • ✅ 成功/失败响应
  • ✅ 可序列化表单
  • ✅ 将提交错误映射到字段
  • ✅ 动态字段支持
  • ✅ 条件字段支持
  • ✅ 列表字段支持
  • ✅ 分组字段支持
  • ✅ CRUD操作支持
  • ✅ 内置美观的小部件

安装与配置

在您的pubspec.yaml文件中添加依赖项:

dependencies:
  flutter_form_bloc: ^最新版本号

确保您查看官方文档获取最新的安装指南及教程。

示例代码

下面是一个完整的示例应用程序,展示了如何使用flutter_form_bloc创建包含多种输入控件的表单。

main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        inputDecorationTheme: InputDecorationTheme(
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
          ),
        ),
      ),
      builder: (context, child) {
        return FormThemeProvider(
          theme: FormTheme(
            checkboxTheme: CheckboxFieldTheme(
              canTapItemTile: true,
            ),
            radioTheme: RadioFieldTheme(
              canTapItemTile: true,
            ),
          ),
          child: child!,
        );
      },
      home: AllFieldsForm(),
    );
  }
}

// 表单逻辑部分
class AllFieldsFormBloc extends FormBloc<String, String> {
  // 定义各个字段
  final text1 = TextFieldBloc();
  final boolean1 = BooleanFieldBloc();
  final boolean2 = BooleanFieldBloc();
  final select1 = SelectFieldBloc(
    items: ['Option 1', 'Option 2'],
    validators: [FieldBlocValidators.required],
  );
  final select2 = SelectFieldBloc(
    items: ['Option 1', 'Option 2'],
    validators: [FieldBlocValidators.required],
  );
  final multiSelect1 = MultiSelectFieldBloc<String, dynamic>(
    items: [
      'Option 1',
      'Option 2',
      'Option 3',
      'Option 4',
      'Option 5',
    ],
  );
  final file = InputFieldBloc<File?, String>(initialValue: null);
  final date1 = InputFieldBloc<DateTime?, Object>(initialValue: null);
  final dateAndTime1 = InputFieldBloc<DateTime?, Object>(initialValue: null);
  final time1 = InputFieldBloc<TimeOfDay?, Object>(initialValue: null);
  final double1 = InputFieldBloc<double, dynamic>(initialValue: 0.5);

  // 构造函数中注册所有字段
  AllFieldsFormBloc() : super(autoValidate: false) {
    addFieldBlocs(fieldBlocs: [
      text1,
      boolean1,
      boolean2,
      select1,
      select2,
      multiSelect1,
      date1,
      dateAndTime1,
      time1,
      double1,
    ]);
  }

  void addErrors() {
    // 添加错误信息给各个字段
    text1.addFieldError('Awesome Error!');
    boolean1.addFieldError('Awesome Error!');
    boolean2.addFieldError('Awesome Error!');
    select1.addFieldError('Awesome Error!');
    select2.addFieldError('Awesome Error!');
    multiSelect1.addFieldError('Awesome Error!');
    date1.addFieldError('Awesome Error!');
    dateAndTime1.addFieldError('Awesome Error!');
    time1.addFieldError('Awesome Error!');
  }

  @override
  void onSubmitting() async {
    try {
      await Future<void>.delayed(Duration(milliseconds: 500));
      emitSuccess(canSubmitAgain: true);
    } catch (e) {
      emitFailure();
    }
  }
}

// UI展示部分
class AllFieldsForm extends StatelessWidget {
  const AllFieldsForm({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => AllFieldsFormBloc(),
      child: Builder(
        builder: (context) {
          final formBloc = BlocProvider.of<AllFieldsFormBloc>(context);

          return Scaffold(
            appBar: AppBar(title: Text('Built-in Widgets')),
            floatingActionButton: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                FloatingActionButton.extended(
                  heroTag: null,
                  onPressed: formBloc.addErrors,
                  icon: Icon(Icons.error_outline),
                  label: Text('ADD ERRORS'),
                ),
                SizedBox(height: 12),
                FloatingActionButton.extended(
                  heroTag: null,
                  onPressed: formBloc.submit,
                  icon: Icon(Icons.send),
                  label: Text('SUBMIT'),
                ),
              ],
            ),
            body: FormBlocListener<AllFieldsFormBloc, String, String>(
              onSubmitting: (context, state) {
                LoadingDialog.show(context);
              },
              onSuccess: (context, state) {
                LoadingDialog.hide(context);
                Navigator.of(context).pushReplacement(
                    MaterialPageRoute(builder: (_) => SuccessScreen()));
              },
              onFailure: (context, state) {
                LoadingDialog.hide(context);
                ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text(state.failureResponse!)));
              },
              child: ScrollableFormBlocManager(
                formBloc: formBloc,
                child: SingleChildScrollView(
                  physics: ClampingScrollPhysics(),
                  padding: EdgeInsets.all(24.0),
                  child: Column(
                    children: <Widget>[
                      // 文本框
                      TextFieldBlocBuilder(
                        textFieldBloc: formBloc.text1,
                        suffixButton: SuffixButton.obscureText,
                        decoration: InputDecoration(
                          labelText: 'TextFieldBlocBuilder',
                          prefixIcon: Icon(Icons.text_fields),
                        ),
                      ),
                      // 单选按钮
                      RadioButtonGroupFieldBlocBuilder<String>(
                        selectFieldBloc: formBloc.select2,
                        decoration: InputDecoration(
                          labelText: 'RadioButtonGroupFieldBlocBuilder',
                        ),
                        groupStyle: FlexGroupStyle(),
                        itemBuilder: (context, item) => FieldItem(
                          child: Text(item),
                        ),
                      ),
                      // 复选框
                      CheckboxGroupFieldBlocBuilder<String>(
                        multiSelectFieldBloc: formBloc.multiSelect1,
                        decoration: InputDecoration(
                          labelText: 'CheckboxGroupFieldBlocBuilder',
                        ),
                        groupStyle: ListGroupStyle(
                          scrollDirection: Axis.horizontal,
                          height: 64,
                        ),
                        itemBuilder: (context, item) => FieldItem(
                          child: Text(item),
                        ),
                      ),
                      // 日期选择器
                      DateTimeFieldBlocBuilder(
                        dateTimeFieldBloc: formBloc.date1,
                        format: DateFormat('dd-MM-yyyy'),
                        initialDate: DateTime.now(),
                        firstDate: DateTime(1900),
                        lastDate: DateTime(2100),
                        decoration: InputDecoration(
                          labelText: 'DateTimeFieldBlocBuilder',
                          prefixIcon: Icon(Icons.calendar_today),
                          helperText: 'Date',
                        ),
                      ),
                      // 日期时间选择器
                      DateTimeFieldBlocBuilder(
                        dateTimeFieldBloc: formBloc.dateAndTime1,
                        canSelectTime: true,
                        format: DateFormat('dd-MM-yyyy  hh:mm'),
                        initialDate: DateTime.now(),
                        firstDate: DateTime(1900),
                        lastDate: DateTime(2100),
                        decoration: InputDecoration(
                          labelText: 'DateTimeFieldBlocBuilder',
                          prefixIcon: Icon(Icons.date_range),
                          helperText: 'Date and Time',
                        ),
                      ),
                      // 时间选择器
                      TimeFieldBlocBuilder(
                        timeFieldBloc: formBloc.time1,
                        format: DateFormat('hh:mm a'),
                        initialTime: TimeOfDay.now(),
                        decoration: InputDecoration(
                          labelText: 'TimeFieldBlocBuilder',
                          prefixIcon: Icon(Icons.access_time),
                        ),
                      ),
                      // 开关
                      SwitchFieldBlocBuilder(
                        booleanFieldBloc: formBloc.boolean2,
                        body: Text('SwitchFieldBlocBuilder'),
                      ),
                      // 下拉菜单
                      DropdownFieldBlocBuilder<String>(
                        selectFieldBloc: formBloc.select1,
                        decoration: InputDecoration(
                          labelText: 'DropdownFieldBlocBuilder',
                        ),
                        itemBuilder: (context, value) => FieldItem(
                          isEnabled: value != 'Option 1',
                          child: Text(value),
                        ),
                      ),
                      Row(
                        children: [
                          IconButton(
                            onPressed: () => formBloc.addFieldBloc(
                                fieldBloc: formBloc.select1),
                            icon: Icon(Icons.add),
                          ),
                          IconButton(
                            onPressed: () => formBloc.removeFieldBloc(
                                fieldBloc: formBloc.select1),
                            icon: Icon(Icons.delete),
                          ),
                        ],
                      ),
                      // 复选框
                      CheckboxFieldBlocBuilder(
                        booleanFieldBloc: formBloc.boolean1,
                        body: Text('CheckboxFieldBlocBuilder'),
                      ),
                      CheckboxFieldBlocBuilder(
                        booleanFieldBloc: formBloc.boolean1,
                        body: Text('CheckboxFieldBlocBuilder trailing'),
                        controlAffinity: FieldBlocBuilderControlAffinity.trailing,
                      ),
                      // 滑块
                      SliderFieldBlocBuilder(
                        inputFieldBloc: formBloc.double1,
                        divisions: 10,
                        labelBuilder: (context, value) =>
                            value.toStringAsFixed(2),
                      ),
                      SliderFieldBlocBuilder(
                        inputFieldBloc: formBloc.double1,
                        divisions: 10,
                        labelBuilder: (context, value) =>
                            value.toStringAsFixed(2),
                        activeColor: Colors.red,
                        inactiveColor: Colors.green,
                      ),
                      ChoiceChipFieldBlocBuilder<String>(
                        selectFieldBloc: formBloc.select2,
                        itemBuilder: (context, value) => ChipFieldItem(
                          label: Text(value),
                        ),
                      ),
                      FilterChipFieldBlocBuilder<String>(
                        multiSelectFieldBloc: formBloc.multiSelect1,
                        itemBuilder: (context, value) => ChipFieldItem(
                          label: Text(value),
                        ),
                      ),
                      BlocBuilder<InputFieldBloc<File?, String>,
                          InputFieldBlocState<File?, String>>(
                          bloc: formBloc.file,
                          builder: (context, state) {
                        return Container();
                      })
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

// 加载对话框
class LoadingDialog extends StatelessWidget {
  static void show(BuildContext context, {Key? key}) =>
      showDialog<void>(
        context: context,
        useRootNavigator: false,
        barrierDismissible: false,
        builder: (_) => LoadingDialog(key: key),
      ).then((_) => FocusScope.of(context).requestFocus(FocusNode()));

  static void hide(BuildContext context) => Navigator.pop(context);

  const LoadingDialog({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async => false,
      child: Center(
        child: Card(
          child: Container(
            width: 80,
            height: 80,
            padding: EdgeInsets.all(12.0),
            child: CircularProgressIndicator(),
          ),
        ),
      ),
    );
  }
}

// 成功页面
class SuccessScreen extends StatelessWidget {
  const SuccessScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Icon(Icons.tag_faces, size: 100),
            SizedBox(height: 10),
            Text(
              'Success',
              style: TextStyle(fontSize: 54, color: Colors.black),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 10),
            ElevatedButton.icon(
              onPressed: () => Navigator.of(context).pushReplacement(
                  MaterialPageRoute(builder: (_) => const AllFieldsForm())),
              icon: Icon(Icons.replay),
              label: Text('AGAIN'),
            ),
          ],
        ),
      ),
    );
  }
}

该示例展示了如何定义不同类型的表单字段、设置验证规则、处理表单提交事件以及根据表单状态更新UI。通过这个例子,您可以了解如何利用flutter_form_bloc来构建复杂且功能丰富的表单界面。


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

1 回复

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


当然,flutter_form_bloc 是一个强大的 Flutter 库,用于管理和验证表单状态。它基于 BLoC(Business Logic Component)架构模式,使得表单状态管理更加清晰和模块化。

以下是一个简单的代码示例,展示了如何使用 flutter_form_bloc 来管理一个登录表单。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 flutter_form_blocflutter_bloc 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0
  flutter_form_bloc: ^3.0.0

2. 创建表单字段枚举

定义一个枚举来表示表单字段:

enum LoginFormFields { email, password }

3. 创建表单字段初始值

定义表单字段的初始值:

final initialLoginFormState = LoginFormState(
  email: Formz.empty(LoginFormFields.email.toString()),
  password: Formz.empty(LoginFormFields.password.toString()),
);

4. 创建表单状态类

创建一个类来表示表单的状态:

import 'package:formz/formz.dart';

class LoginFormState extends FormzStatus {
  final FormzField<String> email;
  final FormzField<String> password;

  LoginFormState({
    required this.email,
    required this.password,
    Status? status,
  }) : super(status ?? Status.pure);

  @override
  List<Object?> get props => [email, password, status];

  LoginFormState copyWith({
    FormzField<String>? email,
    FormzField<String>? password,
    Status? status,
  }) {
    return LoginFormState(
      email: email ?? this.email,
      password: password ?? this.password,
      status: status ?? this.status,
    );
  }
}

5. 创建 BLoC 类

定义一个 BLoC 类来处理表单的逻辑:

import 'package:bloc/bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:equatable/equatable.dart';

part 'login_form_event.dart';
part 'login_form_state.dart';

class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
  LoginFormBloc() : super(initialLoginFormState);

  @override
  Stream<LoginFormState> mapEventToState(
    LoginFormEvent event,
  ) async* {
    if (event is EmailChanged) {
      yield* _mapEmailChangedToState(event.email);
    } else if (event is PasswordChanged) {
      yield* _mapPasswordChangedToState(event.password);
    } else if (event is Submitted) {
      yield* _mapFormSubmittedToState();
    }
  }

  Stream<LoginFormState> _mapEmailChangedToState(String email) async* {
    final emailField = FormzField.from(email);
    yield state.copyWith(
      email: emailField,
      status: Formz.validate([state.email, state.password]),
    );
  }

  Stream<LoginFormState> _mapPasswordChangedToState(String password) async* {
    final passwordField = FormzField.from(password);
    yield state.copyWith(
      password: passwordField,
      status: Formz.validate([state.email, state.password]),
    );
  }

  Stream<LoginFormState> _mapFormSubmittedToState() async* {
    if (state.status == Status.valid) {
      // Perform login action
      yield state.copyWith(status: Status.submitting);
    }
  }
}

6. 定义事件类

定义事件类来表示用户交互:

// login_form_event.dart
part of 'login_form_bloc.dart';

abstract class LoginFormEvent extends Equatable {
  const LoginFormEvent();

  @override
  List<Object?> get props => [];
}

class EmailChanged extends LoginFormEvent {
  final String email;

  const EmailChanged({required this.email});

  @override
  List<Object?> get props => [email];
}

class PasswordChanged extends LoginFormEvent {
  final String password;

  const PasswordChanged({required this.password});

  @override
  List<Object?> get props => [password];
}

class Submitted extends LoginFormEvent {}

7. 创建 UI 层

使用 FlutterFormBloc 小部件来构建表单:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'login_form_bloc.dart';

void main() {
  runApp(
    BlocProvider<LoginFormBloc>(
      create: (context) => LoginFormBloc(),
      child: MaterialApp(
        home: LoginFormScreen(),
      ),
    ),
  );
}

class LoginFormScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: BlocListener<LoginFormBloc, LoginFormState>(
          listener: (context, state) {
            if (state.status == Status.submissionInProgress) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Submitting...')),
              );
            } else if (state.status == Status.submissionSuccess) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Login Successful')),
              );
            } else if (state.status == Status.submissionFailure) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Login Failed')),
              );
            }
          },
          child: FlutterFormBloc<LoginFormBloc, LoginFormState, LoginFormFields>(
            onSubmit: (bloc) => bloc.add(Submitted()),
            child: Column(
              children: <Widget>[
                TextFormFieldBlocBuilder<LoginFormBloc, LoginFormState, String>(
                  formBloc: (context) => context.read<LoginFormBloc>(),
                  field: LoginFormFields.email,
                  builder: (context, field, state) {
                    return TextFormField(
                      decoration: InputDecoration(labelText: 'Email'),
                      onChanged: (value) {
                        context.read<LoginFormBloc>().add(EmailChanged(email: value));
                      },
                      validator: (value) {
                        return !field.isValid ? 'Invalid email' : null;
                      },
                    );
                  },
                ),
                TextFormFieldBlocBuilder<LoginFormBloc, LoginFormState, String>(
                  formBloc: (context) => context.read<LoginFormBloc>(),
                  field: LoginFormFields.password,
                  builder: (context, field, state) {
                    return TextFormField(
                      decoration: InputDecoration(labelText: 'Password'),
                      obscureText: true,
                      onChanged: (value) {
                        context.read<LoginFormBloc>().add(PasswordChanged(password: value));
                      },
                      validator: (value) {
                        return !field.isValid ? 'Password must be at least 6 characters' : null;
                      },
                    );
                  },
                ),
                SizedBox(height: 24),
                ElevatedButton(
                  onPressed: () {
                    context.read<LoginFormBloc>().add(Submitted());
                  },
                  child: Text('Login'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

总结

以上代码展示了如何使用 flutter_form_bloc 来管理一个简单的登录表单。通过分离逻辑和 UI 层,代码更加清晰和模块化,方便维护和扩展。希望这个示例对你有所帮助!

回到顶部