flutter_form_bloc

在Flutter中创建精美的表单。预填充、异步验证、更新表单字段以及根据表单状态显示进度、失败、成功或导航的最简单方法。

使用form_bloc将表单状态和业务逻辑与用户界面分离。

在使用此包之前,您需要了解bloc包的核心概念和flutter_bloc的基础知识。

小部件

笔记

FormBlocInputFieldBlocTextFieldBlocBooleanFieldBlocSelectFieldBlocMultiSelectFieldBloc都是blocs,因此您可以使用BlocBuilderBlocListener(来自flutter_bloc)来使任何您想要的widget与任何FieldBlocFormBloc兼容。

如果您希望我添加其他小部件,请告诉我,或提交一个pr。

示例

  • 具有异步验证的FieldBlocs:BLoC - UI
  • 手动设置FieldBloc错误:BLoC - UI
  • 带提交进度的FormBloc:BLoC - UI
  • 无自动验证的FormBloc:BLoC - UI
  • 复杂的异步预填充FormBloc:BLoC - UI
  • 还有更多示例.

基本示例

dependencies:
  form_bloc: ^0.5.1
  flutter_form_bloc: ^0.4.2
  flutter_bloc: ^0.21.0
import 'package:form_bloc/form_bloc.dart';

class LoginFormBloc extends FormBloc<String, String> {
  final emailField = TextFieldBloc(validators: [Validators.email]);
  final passwordField = TextFieldBloc();

  final UserRepository _userRepository;

  LoginFormBloc(this._userRepository);

  @override
  List<FieldBloc> get fieldBlocs => [emailField, passwordField];

  @override
  Stream<FormBlocState<String, String>> onSubmitting() async* {
    try {
      _userRepository.login(
        email: emailField.value,
        password: passwordField.value,
      );
      yield currentState.toSuccess();
    } catch (e) {
      yield currentState.toFailure();
    }
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:flutter_form_bloc_example/forms/simple_login_form_bloc.dart';
import 'package:flutter_form_bloc_example/widgets/widgets.dart';

class LoginForm extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<LoginFormBloc>(
      builder: (context) =>
          LoginFormBloc(RepositoryProvider.of<UserRepository>(context)),
      child: Builder(
        builder: (context) {
          final formBloc = BlocProvider.of<LoginFormBloc>(context);

          return Scaffold(
            appBar: AppBar(title: Text('Simple login')),
            body: FormBlocListener<LoginFormBloc, String, String>(
              onSubmitting: (context, state) => LoadingDialog.show(context),
              onSuccess: (context, state) {
                LoadingDialog.hide(context);
                Navigator.of(context).pushReplacementNamed('success');
              },
              onFailure: (context, state) {
                LoadingDialog.hide(context);
                Notifications.showSnackBarWithError(
                    context, state.failureResponse);
              },
              child: ListView(
                children: <Widget>[
                  TextFieldBlocBuilder(
                    textFieldBloc: formBloc.emailField,
                    keyboardType: TextInputType.emailAddress,
                    decoration: InputDecoration(
                      labelText: 'Email',
                      prefixIcon: Icon(Icons.email),
                    ),
                  ),
                  TextFieldBlocBuilder(
                    textFieldBloc: formBloc.passwordField,
                    suffixButton: SuffixButton.obscureText,
                    decoration: InputDecoration(
                      labelText: 'Password',
                      prefixIcon: Icon(Icons.lock),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: RaisedButton(
                      onPressed: formBloc.submit,
                      child: Center(child: Text('LOGIN')),
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

基本用法

1. 导入它

import 'package:form_bloc/form_bloc.dart';

2. 创建一个继承自 FormBloc<SuccessResponse, FailureResponse> 的类

FormBloc<SuccessResponse, FailureResponse>

SuccessResponse 成功响应的类型。

FailureResponse 失败响应的类型。

例如,LoginFormBlocSuccessResponse 类型和 FailureResponse 类型将是 String

import 'package:form_bloc/form_bloc.dart';

class LoginFormBloc extends FormBloc<String, String> {}

2. 创建 Field Blocs

您需要创建字段bloc,并且这些字段必须是final的。

您可以创建

例如,LoginFormBloc 将有两个 TextFieldBloc

import 'package:form_bloc/form_bloc.dart';

class LoginFormBloc extends FormBloc<String, String> {
  final emailField = TextFieldBloc(validators: [Validators.email]);
  final passwordField = TextFieldBloc();
}

3. 添加服务/存储库

在此示例中,我们需要一个UserRepository来进行登录。

import 'package:form_bloc/form_bloc.dart';

class LoginFormBloc extends FormBloc<String, String> {
  final emailField = TextFieldBloc(validators: [Validators.email]);
  final passwordField = TextFieldBloc();

  final UserRepository _userRepository;

  LoginFormBloc(this._userRepository);
}

4. 实现 get method fieldBlocs

您需要覆盖 fieldBlocs 方法,并返回一个包含所有FieldBlocs的列表。

例如,LoginFormBloc 需要返回一个包含emailFieldpasswordField的列表。

import 'package:form_bloc/form_bloc.dart';

class LoginFormBloc extends FormBloc<String, String> {
  final emailField = TextFieldBloc(validators: [Validators.email]);
  final passwordField = TextFieldBloc();

  final UserRepository _userRepository;

  LoginFormBloc(this._userRepository);

  @override
  List<FieldBloc> get fieldBlocs => [emailField, passwordField];
}

5. 实现 onSubmitting 方法

onSubmitting 返回一个Stream<FormBlocState<SuccessResponse, FailureResponse>>

当您调用loginFormBloc.submit()FormBlocState.isValidtrue(即每个字段bloc都有有效值)时,将调用此方法。

您可以通过调用emailField.valuepasswordField.value来获取每个字段bloc的当前value

您应该在此处调用此表单的所有业务逻辑,并yield相应的状态。

您可以使用以下方式yield一个新状态:

在此处查看其他状态:here

例如,如果_userRepository.login方法未抛出任何异常,则LoginFormBloconSubmitting将返回一个Stream<FormBlocState<String, String>>yield currentState.toSuccess();如果抛出异常,则yield currentState.toFailure()

import 'package:form_bloc/form_bloc.dart';

class LoginFormBloc extends FormBloc<String, String> {
  final emailField = TextFieldBloc(validators: [Validators.email]);
  final passwordField = TextFieldBloc();

  final UserRepository _userRepository;

  LoginFormBloc(this._userRepository);

  @override
  List<FieldBloc> get fieldBlocs => [emailField, passwordField];

  @override
  Stream<FormBlocState<String, String>> onSubmitting() async* {
    try {
      _userRepository.login(
        email: emailField.value,
        password: passwordField.value,
      );
      yield currentState.toSuccess();
    } catch (e) {
      yield currentState.toFailure();
    }
  }
}

6. 创建一个表单Widget

您需要创建一个具有FormBloc访问权限的widget。

在这种情况下,我将使用BlocProvider来实现。

class LoginForm extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<LoginFormBloc>(
      builder: (context) =>
          LoginFormBloc(RepositoryProvider.of<UserRepository>(context)),
      child: Builder(
        builder: (context) {
          final formBloc = BlocProvider.of<LoginFormBloc>(context);

          return Scaffold();
        },
      ),
    );
  }
}

6. 添加FormBlocListener以管理表单状态更改

您需要添加一个FormBlocListener

在这个例子中

  • 当状态为loading时,我将显示一个加载对话框。
  • 当状态为success时,我将隐藏对话框并导航到success屏幕。
  • 当状态为failure时,我将隐藏对话框并显示一个包含错误的snackbar。
...
          return Scaffold(
            appBar: AppBar(title: Text('Simple login')),
            body: FormBlocListener<LoginFormBloc, String, String>(
              onSubmitting: (context, state) => LoadingDialog.show(context),
              onSuccess: (context, state) {
                LoadingDialog.hide(context);
                Navigator.of(context).pushReplacementNamed('success');
              },
              onFailure: (context, state) {
                LoadingDialog.hide(context);
                Notifications.showSnackBarWithError(
                    context, state.failureResponse);
              },
              child: 
              ),
            ),
          );
...          

6. 将 Field Blocs 与 Field Blocs Builder 连接

在这个例子中,我将使用TextFieldBlocBuilder连接LoginFormBlocemailFieldpasswordField

...
              child: ListView(
                children: <Widget>[
                  TextFieldBlocBuilder(
                    textFieldBloc: formBloc.emailField,
                    keyboardType: TextInputType.emailAddress,
                    decoration: InputDecoration(
                      labelText: 'Email',
                      prefixIcon: Icon(Icons.email),
                    ),
                  ),
                  TextFieldBlocBuilder(
                    textFieldBloc: formBloc.passwordField,
                    suffixButton: SuffixButton.obscureText,
                    decoration: InputDecoration(
                      labelText: 'Password',
                      prefixIcon: Icon(Icons.lock),
                    ),
                  ),
                ],
              ),
...          

7. 添加一个用于提交FormBloc的Widget

在这个例子中,我将添加一个RaisedButton并将FormBlocsubmit方法传递给它来提交表单。

...
              child: ListView(
                children: <Widget>[
                  ...,
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: RaisedButton(
                      onPressed: formBloc.submit,
                      child: Center(child: Text('LOGIN')),
                    ),
                  ),
                ],
              ),
...          

GitHub

https://github.com/GiancarloCode/form_bloc