[更新]:我在这里创建了一个复制存储库:https://gitlab.com/dantec204/flutter-app-issue-reproduction
我正在学习 Flutter/Dart,到目前为止我真的很喜欢使用它,但今天我遇到了一个问题,我似乎无法理解它。
我开始使用 Bloc 进行用户身份验证。每当用户输入他的电子邮件地址时,都应该发出 EmailChanged 事件。到目前为止一切顺利,一切仍然正常。
但是对于电子邮件地址,我有这个值对象类:
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 事件被发出时,这个工厂构造函数都会被执行两次。 第一次它收到正确的输入值,但第二次输入值只是一个空白字符串。因此我的状态是不正确的。
这就是该 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))
);
});
}
}
Bloc 国家:
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 });
}
我真的希望有人能帮助我,因为我几乎一整天都在互联网上查找并思考这个问题......
最佳答案
感谢您提供项目,我可以使用它来查看您的问题。
我已经发现问题所在。您正在日志中查找文本 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)));
});
...
然后你就可以继续了!
关于Flutter 值对象工厂构造函数在 Bloc 发出时执行两次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71146711/