Comment obtenir une Validation correcte d’un Form en utilisant CombineLatest?

Question

Lorsque vous mettez en œuvre une validation de formulaire en utilisant des Streams (et donc, dans mon cas, un BLoC), il y a des cas où le résultat de la validation en utilisant Observable.combineLatest pourrait donner un résultat erroné.

Cela est dû au fait que les flux, référencés par CombineLatest, auraient déjà pu émettre une validation positive, plus tôt.

À titre de référence, considérez mon article initial qui explique les bases d’un tel processus de validation de formulaire.

Réponse

Avant de donner la solution au problème, analysons d’abord celui-ci et comprenons pourquoi cela pourrait arriver.

Si j’extrais la partie du code qui donne le résultat de la validation (et que je la limite aux notions de email et password pour rendre l’explication un peu plus facile), nous avons :

final BehaviorSubject<String> _emailController = BehaviorSubject<String>();
final BehaviorSubject<String> _passwordController = BehaviorSubject<String>();

//
//  Inputs
//
Function(String) get onEmailChanged => _emailController.sink.add;
Function(String) get onPasswordChanged => _passwordController.sink.add;

//
// Validators
//
Stream<String> get email => _emailController.stream.transform(validateEmail);
Stream<String> get password => _passwordController.stream.transform(validatePassword);

//
// Outcome of the validation
//
Stream<bool> get registerValid => Observable.combineLatest2(
                                      email, 
                                      password, 
                                      (e, p) => true
                                    );

Comment cela fonctionne-t-il ?

  • Lorsque l’utilisateur saisit les informations relatives au email, la saisie est envoyée au _emailController via son sink (onEmailChanged)
  • Le Stream émet l’entrée fournie au StreamTransformer (validateEmail) qui effectuera la validation.
  • Si la validation est positive, l’entrée est renvoyée au Stream (email)
  • Si la validation est négative, une erreur est envoyée au Stream (email)
  • Ce dernier est écouté par le Observable.combineLatest2, en même temps que l’autre flux de sortie mot de passe.
  • Le même principe s’applique au mot de passe.
  • Lorsque le email et le mot de passe auront tous deux émis quelque chose qui n’est pas une erreur, le Stream<bool> (registerValid) émettra un “true

Donc, en théorie, cela devrait fonctionner, mais si vous appliquez le flux d’actions suivant :

  • entrez un email correct => registerValid n'émet rien, mais
  • mettez à jour le email et introduisez un non validate
  • entrez un mot de passe correct => registerValid émet une réponse positive

Puisque le email n’est pas valide, cela ne devrait pas arriver, me direz-vous.

Alors pourquoi cela se produit-il ?

Cela arrive parce que le flux email a déjà émis quelque chose après une validation positive, avant .


Comment résoudre ce problème ?

Pour résoudre ce problème, j’ai écrit la classe suivante :

Voici le code qui utilise cette classe :

Comment cela fonctionne-t-il ?

  • Au moment de l’initialisation du RegistrationFormBloc (ligne 39), la classe ValidateAllStreamsHaveDataAndNoErrors est instanciée et nous lui demandons également d'écouter les flux ‘email', ‘password’ et ‘confirmPassword'.

  • Chaque fois qu’un de ces flux émet quelque chose (donnée ou erreur), il est intercepté par la classe ValidateAllStreamsHaveDataAndNoErrors (lignes 34-43)

  • Les errors[index] correspondants seront mis à false (cas d'émission de certaines données) ou à true (cas d'émission d’une erreur)

  • La méthode _validate() sera appelée, qui émettra (via son propre Stream) une réponse positive si aucune erreur ne reste enregistrée (au niveau du tableau errors[]), sinon elle émettra une réponse négative (= false).

  • Ce stream peut maintenant être utilisé en toute sécurité comme statut de validation (ligne #33 du RegistrationFormBloc)


Conclusions

Je suppose qu’il existe d’autres solutions et peut-être plus élégantes que celle-ci, mais au moins celle-ci fonctionne.

J’espère que cela pourra être utile dans certains développements.