Utiliser debounceTime de RxDart et Bloc pour attendre que l’utilisateur ait finit de taper

Arnaudelub
3 min readMar 14, 2021

Story available in english: https://arnaudelub.medium.com/using-debouncetime-from-rxdart-and-bloc-to-wait-for-a-user-to-end-typing-7a389c506a34

Story disponible en español: https://arnaudelub.medium.com/usar-debouncetime-de-rxdart-y-bloc-para-esperar-a-que-un-usuario-termine-de-escribir-147c91243329

Salut les Happy Coders, nous sommes ici aujourd’hui pour parler de Bloc (encore!) et de debounceTime de l’excellent package RxDart (https://pub.dev/packages/rxdart). Si vous vous demandez ce qu’est debounceTime, regardons la documentation: (https://pub.dev/documentation/rxdart/latest/rx/DebounceExtensions/debounceTime.html):

Transforms a Stream so that will only emit items from the source sequence whenever the time span defined by duration passes, without the source sequence emitting another item.

This time span start after the last debounced event was emitted.

debounceTime filters out items emitted by the source Stream that are rapidly followed by another emitted item.

Ces 3 paragraphes sont très importants à comprendre et nous y reviendrons en suivant un exemple!

Ok, C’est parti!

Comme exemple, disons que vous avez un TextFormField et que vous souhaitez interroger votre base de données en fonction du texte que l’utilisateur y entre. Si vous utilisez Firebase, vous ne voulez certainement pas interroger votre collection chaque fois que l’utilisateur saisit un nouveau caractère, cela coûtera cher dans votre quota quotidien de 50K lectures gratuites. Comment pouvons-nous y remédier? Tout d’abord, nous verrons comment implémenter ce cas d’utilisation sans debounceTime afin que vous puissiez voir où est le problème:

Ok, avez-vous déjà trouvé le problème? Non? Regardez bien ce code, vous devriez le voir…

Ca y est? En effet, dans la fonction onChanged du TextFormField, nous appelons notre événement CityWatcherEvent.watchTenNext(value) et dans la classe CityWatcherBloc, nous pouvons voir que chaque fois que cet événement est reçu nous écoutons le Stream retourné par _cityFacade.watchAll(value), ce qui signifie que pour chaque lettre tapée par l’utilisateur, nous interrogeons la base de données… ET CA, CE N’EST PAS BON!

Maintenant que nous avons le problème, prenons un peu de recul et revoyons les 3 paragraphes de la doc que j’ai mentionnés plus tôt et nous allons commencer par le second:

This time span start after the last debounced event was emitted.

cela signifie qu’à chaque fois qu’il y a un événement “debounced”, le Timer sera redémarré, ce qui est bon pour nous car nous voulons attendre que l’utilisateur finisse de taper, nous ne voulons pas faire l’appel à notre base de données après X secondes. Ensuite, le premier paragraphe:

Transforms a Stream so that will only emit items from the source sequence whenever the time span defined by duration passes, without the source sequence emitting another item.

ce qui signifie que s’il n’y a pas de nouvel élément émis, dans notre cas, l’événement sera retardé par Duration(), ce qui est parfait pour nous.

Réglons ce problème!

Si vous jetez un œil au repo Bloc, vous verrez cette méthode:

Stream<Transition<Event, State>> transformEvents(Stream<Event> events,TransitionFunction<Event, State> transitionFn,  ) {
return events.asyncExpand(transitionFn);
}

juste là: https://github.com/felangel/bloc/blob/master/packages/bloc/lib/src/bloc.dart ligne 171. Cette méthode semble parfaite pour faire ce que nous voulons. Maintenant, vous pouvez simplement remplacer cette méthode dans notre city_watcher_bloc.dart qui extends Bloc et y implémenter notre debounceTime. Il y a une chose à prendre en compte d’abord, NOUS NE VOULONS PAS utiliser debounceTime pour chaque type d’événement, dans notre cas _WatchTenNext et _CityReceived c’est pourquoi notre méthode transformEvents doit renvoyer un merge de nos streams avec MergeStream (https://pub.dev/documentation/rxdart/latest/rx/MergeStream-class.html), les stream “debounced” et les non “debounced” (si cela peut vous aider à comprendre le fonctionnement de Merge, vous pouvez trouver un diagramme interactif ici: https://rxmarbles.com/#merge):

Et c’est tout, maintenant, lorsqu’un utilisateur tape dans TextFormField, l’événement _watchTenNext ne sera déclenché que 500 ms après que l’utilisateur ait fini de taper. J’utilise normalement 300 ms ou 500 ms, pas moins, sinon si l’utilisateur est un “slow typer”, l’événement sera déclenché même s’il n’a pas fini d’entrer la valeur dans l’input.

Évidemment, Bloc n’est pas le seul moyen d’y parvenir, mais à mon humble avis, je pense que c’est une façon élégante de le faire.

Si vous avez une autre solution, n’hésitez pas à la partager en commentaire.

Happy coding à tous!

--

--