[UPDATE]: I created a reproduction repo here: https://gitlab.com/dantec204/flutter-app-issue-reproduction
I'm learning Flutter/Dart and so far I really love working with it, but today I ran into an issue and I can't seem to wrap my head around it.
I started using Bloc for my user authentication. Whenever the user enters his email address, an EmailChanged event should be emitted. So far so good, everything still works fine.
But for email addresses, I have this value object 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));
}
}
Problem here is that somehow, whenever the bloc event gets emitted, this factory constructor gets executed twice. First it receives the right input value, but the second time the input value is just a blank string. And thus my state is incorrect.
This is what the bloc looks like:
@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))
);
});
}
}
the bloc state:
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,
);
}
}
and this is the onChanged event from the TextFormField:
onChanged: (value) =>
context.read<LoginBloc>().add(LoginEmailChanged(emailAddress: value)),
abstract class LoginEvent {}
class LoginEmailChanged extends LoginEvent {
final String emailAddress;
LoginEmailChanged({ required this.emailAddress });
}
I really hope someone can help me out here because I've been looking on the internet and thinking about it for almost the entire day...