One Off the Slack: Where to Debounce?
Marco Romano asked:
I have a
onValueChangedoes two things:
onValueChange’s value back to the
BasicTextFieldto display what the user has typed.
- Triggers an HTTP search call to fetch updated data to be displayed.
I’d like to debounce 2. but not 1. but: since the
onValueChangeis a single callback I see no “easy” way to do it.
Were I to debounce the whole
onValueChangecallback, 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
EditTextmanages its own internal state I could always debounce it on the UI side using
afterTextCangewithout 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
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).