Using debounceTime from RxDart and Bloc to wait for a user to end typing

Story disponible en Français: 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

Hey happy coders, here we are today to talk about Bloc (again!) and debounceTime from the amazing package RxDart (https://pub.dev/packages/rxdart). If you are wondering what the heck is debounceTime, let’s check the documentation for that(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.

All 3 paragraphs are very important to understand and we will get into it by following an example!

Ok, let’s dive in!

So, the example, let’s say you have an TextFormField and you want to query your database according to the text the user is entering in it. If you are using Firebase, you definitely don’t want to query your collection each time the user enter a new character, it will cost a lot in your 50K free readings daily quota. How can we fix it? First, we’ll see how to implement this use case without debounceTime so you can see where is the problem:

Ok, have you found the issue yet? No? give it another look then…

Yes? Indeed, in the onChanged Function of the TextFormField, we are calling our event CityWatcherEvent.watchTenNext(value) and in the CityWatcherBloc class, we can see that each time this event is received we are listening to the Stream returned by _cityFacade.watchAll(value), meaning that for each letter typed by the user we are querying the database… NOT GOOD!

Now that we got the problem, let’s take a step back and recall the 3 paragraphs of the doc i mentioned earlier and we are going the take the second one:

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

it means that each time there is a debounced event, the timer will be restarted, which is good for us because we want to wait for the user to end typing, not to do the call to our database after X seconds. And if we look at the first one:

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.

which means if there isn’t any new emitted item, in our case, the event will be delayed by the Duration(), which is perfect for us.

Time to fix it!

If you take a look at the Bloc repository, you will see this method:

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

right here: https://github.com/felangel/bloc/blob/master/packages/bloc/lib/src/bloc.dart at line 171. This method seems perfect to do what we are aiming for. Now can just override this method inside our city_watcher_bloc.dart which is extending Bloc and implement our debounceTime in it. There is one thing to take in account first, WE DON’T WANT to use debounceTime for every type of event, in our case _WatchTenNext and _CityReceived that’s why our transformEvents method has to return a merge of our streams with MergeStream (https://pub.dev/documentation/rxdart/latest/rx/MergeStream-class.html), the debounced ones and the non debounced ones (if it can help you understand how Merge is working, you can find an interactive diagram in here: https://rxmarbles.com/#merge):

And that’s it, now when a user is typing inside the TextFormField, the event _watchTenNext will be fired only 500ms after the user has ended typing. I am normally using 300ms or 500ms, not lower, otherwise if the user is a slow typer, then the event will be fired even if he has not ended entering the value in the input.

Obviously, Bloc is not the only way to achieve this, but in my honest opinion, i think it’s an elegant way to do it.

If you have another solution for this, feel free to share it in comment.

Happy coding to everyone!

Flutter Developer enthousiast since 2018

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store