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)));
});
...
然后你就可以继续了!
[更新]:我在这里创建了一个复制仓库: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)));
});
...
然后你就可以继续了!