One Off the Slack: Where to Debounce?

Marco Romano asked:

I have a BasicTextField whose onValueChange does two things:

  1. Feeds onValueChange’s value back to the BasicTextField to display what the user has typed.
  2. Triggers an HTTP search call to fetch updated data to be displayed.

I’d like to debounce 2. but not 1. but: since the onValueChange is a single callback I see no “easy” way to do it.

Were I to debounce the whole onValueChange callback, the user will notice a lag with the on screen feedback too.

Zach Klippenstein worried about who was doing the debouncing:

It is smelly to be doing any sort of HTTP stuff from your composables anyway. I would fire text change events to a view model of some sort, and then that model can debounce the events it receives (e.g. using rx/flow operators or something else).

Marco further clarified:

Yeah that’s what I’m doing, the text change event is sent down to the business logic component which deals with state management and http calls (pure kotlin code, no android).

But I thought this kind of debouncing should happen at the earliest possible in the “chain” which in this case is in the UI layer (though this is debatable of course).

Zach countered that last part:

Why? You’ve described a very good reason why not to do this. Another reason is responsibility – if the same view model were used with different UI, the same debouncing logic would probably need to be applied. If that logic is in the UI layer like you propose, then you’d have to duplicate/rewrite it. It would also probably be a lot easier to unit test the debouncing logic if it’s in your view model.

As Marco then pointed out, this is a side-effect of legacy thinking:

this is just the way I used to do it with the legacy View system: since EditText manages its own internal state I could always debounce it on the UI side using afterTextCange without affecting the text fed back to the UI.

This highlights some subtle differences in how we deal with widget state with Compose. Some stuff is obvious: we need to manage that state ourselves and arrange to recompose the widget with the new state. But we also need to think through all those View listeners and callbacks that we are using and determine what the proper implementation is in the world of Compose. As this case illustrates, sometimes that will cause us to move implementation elsewhere in our architecture (e.g., UI layer to a viewmodel).

Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!