【问题标题】:Best practice for BLoC pattern in Dart/FlutterDart/Flutter 中 BLoC 模式的最佳实践
【发布时间】:2021-02-04 07:20:29
【问题描述】:

在 Flutter 中使用 BLoC 模式时,在构建应用程序代码时,什么是良好的编程习惯?

这是一个松散的问题,所以我将尝试举一个例子。假设您定义了一个 BLoC 类来处理特定 Widget 的验证,并且您希望通过在填充表单时发出各种事件来更新验证消息。

据我了解,您的 BLoC 可能如下所示:

import 'dart:async';
import 'package:myproject/bloc/base_bloc.dart';
import 'package:rxdart/rxdart.dart';

class SignUpBloc extends Bloc {
    BehaviorSubject<String> _emailSubject;
    BehaviorSubject<String> _nameSubject;
    BehaviorSubject<String> _phoneSubject;
    BehaviorSubject<String> _signUpSubject;

    SignUpBloc() {
        _emailSubject = new BehaviorSubject<String>.seeded('');
        _nameSubject = new BehaviorSubject<String>.seeded('');
        _phoneSubject = new BehaviorSubject<String>.seeded('');
        _signUpSubject = new BehaviorSubject<String>.seeded('');
    }

    void nameChanged(String content) {
        if (content?.isEmpty ?? true) {
          _nameSubject.emit('Name is required for the sign-up process');
        } else {
           _nameSubject.emit('');
        }
    }

    void emailChanged(String content) {
        if (!_validEmail(content)) {
          _emailSubject.emit(
              'Please enter a valid email address (e.g. example@mydomain.com');
        } else {
          _emailSubject.emit('');
          _email = content;
        }
    }

    .
    .
    // Other functions are used to emit various messages here
    .
    .

    Stream<String> get emailStream => _emailSubject.stream;
    Stream<String> get nameStream => _nameSubject.stream;
    Stream<String> get phoneStream => _phoneSubject.stream;
    Stream<String> get signUpStream => _signUpSubject.stream;
}

...然后在您的 ui/view 中,您可能会执行以下操作:

StreamBuilder<String>(
      stream: _bloc.emailStream,
      builder: (context, snapshot) {
        return Padding(
          padding: EdgeInsets.only(top: 5.0),
          child: Text(
            snapshot?.data ?? '',
          ),
        );
      },

现在,对我来说,这段代码散发出一股难闻的代码气味……尤其是,继续定义 BehaviorSubject 然后编写方法以启用对其相应流的访问似乎有点混乱。我在网上找到的所有示例都定义了一种与此非常相似的方法,虽然它确实允许将业务逻辑与 UI 分开,但感觉就像有很多重复的工作来继续定义和公开主题。

【问题讨论】:

    标签: flutter dart bloc


    【解决方案1】:

    虽然在某些情况下你的方法是合理的,但像这样使用 Bloc 感觉是多余的。

    Bloc 本身带有 2 个流(内置,抽象出来),一个用于事件,一个用于状态。

    按照您的示例,使用 Bloc 的更惯用方法如下所示:

    状态:

    @immutable
    abstract class FormState {
      final String email;
      final String name;
      final String phone;
      final String signUp;
    
      final String emailError;
      final String nameError;
      final String phoneError;
      final String signUpError;
    
      FormState({
        this.email = "",
        this.name = "",
        this.phone = "",
        this.signUp = "",
        this.emailError = "",
        this.nameError = "",
        this.phoneError = "",
        this.signUpError = "",
      });
    }
    

    事件:

    @immutable
    abstract class FormChangedEvent {
      final String email;
      final String name;
      final String phone;
      final String signUp;
    
      FormChangedEvent({
        this.email = "",
        this.name = "",
        this.phone = "",
        this.signUp = "",
      });
    }
    

    集团:

    class FormBloc extends Bloc<FormChangedEvent, FormState> {
      FormBloc() : super(FormState());
    
      @override
      Stream<FormState> mapEventToState(FormChangedEvent event) async* {
        yield FormState(
          email: event.email,
          name: event.name,
          phone: event.phone,
          signUp: event.signUp,
          emailError: (event.email == null || event.email.isEmpty)
              ? "This field should not be empty!"
              : "",
          nameError: (event.name == null || event.name.isEmpty)
              ? "This field should not be empty!"
              : "",
          phoneError: (event.phone == null || event.phone.isEmpty)
              ? "This field should not be empty!"
              : "",
          signUpError: (event.signUp == null || event.signUp.isEmpty)
              ? "This field should not be empty!"
              : "",
        );
      }
    }
    

    通过该设置,您可以使用BlocBuilder 来监听 Bloc 的状态变化。在此示例中,您将收到 FormState 的实例,其中包含整个表单的状态。

    进一步,您可以创建事件和状态子类,并使用isstate is Loadingstate is FormReady)根据您的需要呈现页面的不同状态。

    我希望这会有所帮助。 :]

    Bloc 8.x.x 更新

    Bloc 从 8.0.0 开始有了更清晰的事件/状态映射逻辑:

    class FormBloc extends Bloc<FormChangedEvent, FormState> {
      FormBloc() : super(FormState()) {
        on<FormChangedEvent>(
          (event, emit) async {
            emit(
              FormState(
                email: event.email,
                name: event.name,
                phone: event.phone,
                signUp: event.signUp,
                emailError: (event.email == null || event.email.isEmpty)
                    ? "This field should not be empty!"
                    : "",
                nameError: (event.name == null || event.name.isEmpty)
                    ? "This field should not be empty!"
                    : "",
                phoneError: (event.phone == null || event.phone.isEmpty)
                    ? "This field should not be empty!"
                    : "",
                signUpError: (event.signUp == null || event.signUp.isEmpty)
                    ? "This field should not be empty!"
                    : "",
              ),
            );
          },
        );
      }
    }
    

    【讨论】:

    • 这正是我一直在寻找的东西,谢谢。在您的回答后面,您会建议将 RxDart 库与 BlocBuilder 结合使用,还是 BlocBuilder 提供足够的功能?
    • 另外(为重复发布道歉),在您的示例中,事件数据实际上是如何传递给集团的?你能在 UI 代码中展示它的样子吗?
    • 使用 Rx 很大程度上取决于您的用例。例如,我肯定会使用 Rx inside a Bloc 来合并来自数据层(网络 + 数据库)的流。在使用 Bloc 时,我想不出 UI 端的 Rx 用例,但这可能是因为我缺乏想象力。
    • 在 UI 方面,我会使用 BlocProvider 在 Widget 树中设置一个 Bloc,然后通过 BlocProvider.of&lt;YourBlocType&gt;(context) 访问它。您可以在结果上调用add(event)(当然是请求的 Bloc)。
    猜你喜欢
    • 2021-11-26
    • 1970-01-01
    • 2021-06-24
    • 2021-08-17
    • 1970-01-01
    • 1970-01-01
    • 2020-01-09
    • 2020-05-11
    • 1970-01-01
    相关资源
    最近更新 更多