Dart/Flutter中BLoC模式的最佳实践

5
在Flutter中使用BLoC模式时,对于应用程序代码的结构化,什么被认为是良好的编程实践?
这是一个宽泛的问题,我将尝试举个例子。假设您定义了一个BLoC类来处理特定小部件的验证,并且您想通过发出各种事件来更新验证消息,以便填充表单。
据我所知,您的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/视图中,您可能会执行类似以下操作:

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

对我来说,这段代码有一种不好的代码气味......特别是,似乎很混乱地不断定义BehaviorSubjects,然后编写方法以启用对它们对应的流的访问。我在网上找到的所有示例都定义了一种非常类似的方法,虽然它确实可以将业务逻辑与UI分离,但感觉需要大量重复努力来定义和暴露subjects。

1个回答

4

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

本身带有两个流(内置,抽象),一个用于事件,另一个用于状态。

遵循你的示例,使用 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版以来,Bloc具有更清晰的事件/状态映射逻辑:

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!"
                : "",
          ),
        );
      },
    );
  }
}

这正是我在寻找的东西,非常感谢。基于你的回答,你会推荐在BlocBuilder中使用RxDart库吗?还是说BlocBuilder已经提供了足够的功能? - spuriousGeek
另外(对于重复发布表示歉意),在您的示例中,事件数据实际上是如何传递到 bloc 中的?您能展示一下这在 UI 代码中会是什么样子吗? - spuriousGeek
使用 Rx 取决于你的用例。例如,在一个 Bloc 中,我肯定会使用 Rx 来合并来自数据层(网络+数据库)的流。我想不出在使用 Bloc 时在 UI 端使用 Rx 的用例,但这可能只是因为我缺乏想象力。 - stewemetal
在 UI 方面,我会使用 BlocProvider 在小部件树中设置 Bloc,并通过 BlocProvider.of<YourBlocType>(context) 访问它。然后你可以在结果上调用 add(event)(当然是请求的 Bloc)。 - stewemetal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接