Flutter 值对象工厂构造函数在 Bloc emit 上执行两次

Flutter value object factory constructor executes twice on Bloc emit

[更新]:我在这里创建了一个复制仓库:https://gitlab.com/dantec204/flutter-app-issue-reproduction

我正在学习 Flutter/Dart,到目前为止我真的很喜欢使用它,但是今天我 运行 遇到了一个问题,我似乎无法解决这个问题。

我开始使用 Bloc 进行用户身份验证。每当用户输入他的电子邮件地址时,应发出 EmailChanged 事件。目前一切顺利,一切正常。

但是对于电子邮件地址,我有这个值对象 class:

class EmailAddress extends ValueObject<String> {
  @override
  final Either<Failure<String>, String> value;

  factory EmailAddress(String input) {
    return EmailAddress._(
      validateEmailAddress(input),
    );
  }

  const EmailAddress._(this.value);
}

Either<Failure<String>, String> validateEmailAddress(String input) {
  const emailRegex = r"""^[a-zA-Z0-9.!#$%&'*\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$""";

  if (RegExp(emailRegex).hasMatch(input)) {
    return right(input);
  } else {
    return left(Failure(input));
  }
}

问题 不知何故,每当发出 bloc 事件时,这个工厂 构造函数就会执行两次 第一次它接收到正确的输入值,但第二次输入值只是一个空字符串。因此我的状态不正确。

这就是集团的样子:

@injectable
class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final IAuthFacade _authFacade;

  LoginBloc(this._authFacade): super(LoginState()) {
    on<LoginEmailChanged>((event, emit) async {
      emit(
        state.copyWith(emailAddress: EmailAddress(event.emailAddress))
      );
    });
  }
}

集团国家:

class LoginState {
  final EmailAddress emailAddress;
  final Password password;
  final bool isSubmitting;

  LoginState({
    emailAddress,
    password,
    this.isSubmitting = false
  }) : emailAddress = EmailAddress(""),
       password = Password("");

  LoginState copyWith({
    EmailAddress? emailAddress,
    Password? password,
    bool? isSubmitting
  }) {
    return LoginState(
      emailAddress: emailAddress ?? this.emailAddress,
      password: password ?? this.password,
      isSubmitting: isSubmitting ?? this.isSubmitting,
    );
  }
}

这是来自 TextFormField 的 onChanged 事件:

onChanged: (value) =>
     context.read<LoginBloc>().add(LoginEmailChanged(emailAddress: value)),
abstract class LoginEvent {}

class LoginEmailChanged extends LoginEvent {
  final String emailAddress;

  LoginEmailChanged({ required this.emailAddress });
}

我真的希望有人能帮我解决这个问题,因为我已经在互联网上搜索并思考了几乎一整天...

感谢您提供我可以看到您的问题的项目。

我已经检测到问题所在。您正在日志中查找文本 factory constructor of EmailAddress, input:,当用户通过在文本字段中键入新字符等方式更改其电子邮件地址时,您会看到它在日志中打印了两次。

您看到该消息在屏幕上打印两次的原因是:

首先你发出这个事件:

LoginBloc(this._authFacade): super(LoginState()) {
  on<LoginEmailChanged>((event, emit) async {
    emit(
      state.copyWith(emailAddress: EmailAddress(event.emailAddress))
    );
  });

依次调用您所在州的 copyWith() 函数,如下所示:

LoginState copyWith({
  EmailAddress? emailAddress,
  Password? password,
  bool? isSubmitting,
  Option<Either<AuthFailure, Unit>>? authFailureOrSuccess
}) {
  return LoginState(
    emailAddress: emailAddress ?? this.emailAddress,
    password: password ?? this.password,
    isSubmitting: isSubmitting ?? this.isSubmitting,
    authFailureOrSuccess: authFailureOrSuccess ?? this.authFailureOrSuccess
  );
}

这是调用 LoginState() 构造函数,它本身会创建 EmailAddress 的另一个副本,如下所示:

LoginState({
  emailAddress,
  password,
  this.isSubmitting = false,
  authFailureOrSuccess
}) : emailAddress = EmailAddress(""),
      password = Password(""),
      authFailureOrSuccess = none();

所以即使你认为你正在调用 LoginState 并且应该只使用你传入的 EmailAddress? 值,默认构造函数确实在使用之前创建了 EmailAddress 的另一个实例你提供给它的那个。

解决方案是实际修复您的 LoginState 构造函数,正如我在此处向您展示的那样:

class LoginState {
  final EmailAddress emailAddress;
  final Password password;
  final bool isSubmitting;
  final Option<Either<AuthFailure, Unit>> authFailureOrSuccess;

  LoginState({
    required this.emailAddress,
    required this.password,
    required this.isSubmitting,
    required this.authFailureOrSuccess,
  });
  ...

那么您必须使用我们在您的代码库中的这行代码来解决问题:

LoginBloc(this._authFacade): super(LoginState())

由于 LoginState 不再有默认状态,您需要像这样定义一个:

@injectable
class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final IAuthFacade _authFacade;

  LoginBloc(this._authFacade)
      : super(LoginState(
          emailAddress: EmailAddress(''),
          password: Password(''),
          isSubmitting: false,
          authFailureOrSuccess: none(),
        )) {
    on<LoginEmailChanged>((event, emit) async {
      emit(state.copyWith(emailAddress: EmailAddress(event.emailAddress)));
    });
   ...

然后你就可以继续了!