I get the weird behavior from the flutter app, trying to fix this for two days...
Here is the code: Form widgets:
@override
Widget build(BuildContext context) {
final FormBloc formBloc = FormProvider.of(context);
Widget _emailField(FormBloc formBloc) {
return StreamBuilder(
stream: formBloc.email,
builder: (context, AsyncSnapshot<String> snapshot) {
return TextFormField(
autofocus: false,
keyboardType: TextInputType.emailAddress,
onChanged: formBloc.changeEmail,
Widget _passwordField(FormBloc formBloc) {
return StreamBuilder(
stream: formBloc.password,
builder: (context, AsyncSnapshot<String> snapshot) {
return TextFormField(
autofocus: false,
obscureText: !_passwordVisible,
keyboardType: TextInputType.text,
onChanged: formBloc.changePassword,
Formbloc:
class FormBloc with LoginValidationMixin {
final _email = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
final _errorMessage = BehaviorSubject<String>();
// getters
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
Function(String) get addError => _errorMessage.sink.add;
// streams
Stream<String> get email => _email.stream.transform(validatorEmail);
Stream<String> get password => _password.stream.transform(validatorPassword);
Stream<String> get errorMessage => _errorMessage.stream;
Stream<bool> get submitValidForm => Rx.combineLatest3(
email,
password,
errorMessage,
(e, p, er) => true,
);
late AuthService authInfo;
// login
dynamic login(BuildContext context) async {
authInfo = AuthService();
final email = _email.valueOrNull;
final password = _password.valueOrNull;
if (email == null || password == null) {
addError("Password or Email cannot be empty");
return;
}
final response =
await authInfo.login(email, password, this);
if (response == null) {return;}
final data = jsonDecode(response) as Map<String, dynamic>;
if (data['status_code'] != 200) {
} else {
final tokens = data['tokens'];
AuthService.setToken(
tokens["access"], tokens["refresh"], data['client_id']);
Navigator.pushNamed(context, '/home');
return data;
}
}
Validation
class LoginValidationMixin {
static String get passwordPattern => "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}";
final validatorEmail = StreamTransformer<String, String>.fromHandlers(
handleData: (email, sink) {
sink.add(email);
if (!EmailValidator.validate(email)) {
sink.addError('Email should look like:'
'welcome@itsy.ext\n');
} else {
sink.add(email);
}
},
);
final validatorPassword = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
sink.add(password);
if (!RegExp(passwordPattern).hasMatch(password)) {
sink.addError('A hint, password should contain\n'
'at least 8 characters\n'
'at least 1 upper case character\n'
'at least 1 upper case character\n'
'at least 1 numerical character\n');
} else {
sink.add(password);
}
},
);
}
Login Button
Widget _loginButton(FormBloc formBloc) {
return StreamBuilder<bool>(
stream: formBloc.submitValidForm,
builder: (context, AsyncSnapshot<bool> snapshot) {
return Material(
child: MaterialButton(
onPressed: () {
if (snapshot.error != null) {
print(snapshot.error) <<<< in the second scenario, here is always an error, even if filed validations have passed. While in the first scenario, this does work as expected.
return;
} else {
formBloc.login(context);
}
},
So, in the second case, EventSink always holds an error and button is basically not clickable. However, in the first case EventSink works fine, holds errors if there are some and clears them if correct values are added. What should I do? Is there a way to clear EventSink errors?
So this is how I resolved the case, first I realized that streams will only yield value and not store.
pskink: EvenSink does not hold an error Next, I checked that
Stream<bool> get submitValidForm => Rx.combineLatest3(
email,
password,
errorMessage, <<< this one is null in the case two, therefore my solution was to give it an initial value while handling email or password.
(e, p, er) => true,
);