useDebounce in Android's JetPack Compose?

Photo by Guido Coppa on Unsplash

useDebounce in Android's JetPack Compose?

Introduction

Searching is an essential functionality in most commercial mobile apps as it allows users to quickly retrieve their content of interest rather than going through all the provided content which results in a poor user experience.

Implementing a search functionality on the client side, which is in our case, a mobile application can be a bit tricky since we need to take into account the number of API hits, which affects the overall performance of the application.

Imagine a fast typer who can enter up to 3 words per second, the performance and the user experience would be terrible.
One final point is that the subsequent illustration invokes some advanced concepts of the Jetpack Compose android development toolkit, so I'm assuming the reader has quite good knowledge of Compose. But if not, don't worry you can keep reading and enjoying.

Note:
Here, we're not concerned with the implementation of the search functionality on the backend side. The one thing we know is that given a query, the backend returns a search result containing a subset of restaurants meeting the search criteria.

Now that we introduced the issue, how can we solve it?

Debouncing

Debouncing is a technique used in front-end development in general to improve the performance of event handlers that are triggered by user interaction, such as key presses or mouse clicks. The idea is to prevent a function from being called more often than necessary, especially in situations where the events fire rapidly.

Once debouncing is implemented, the function is only called after a specified amount of time has passed since the last event was triggered. This allows the client to "catch up" on the events and handle them more efficiently. Debouncing can be particularly useful in situations where a user is typing into an input field, as it can prevent unnecessary API requests or other expensive operations from being executed with every keystroke.

But how to implement the debouncing feature?

useDebounce

As a React developer, I manipulate hooks all the time which are functions that manage component side effects. useDebounce is a well-known React hook that can be used to debounce browser DOM events. But we're neither going to implement it nor use it in our case since we're dealing with the modern UI toolkit for native Android app development JetPack Compose.

Note:
The name `useDebounce` that I use is totally a personal choice since I like its name from React implementation.

Application layout

Before going further into the application structure, I want to point out that I'm not going to spin up a backend API to serve the restaurants, instead, I'll just use a local hardcoded list. But the procedure of developing the debouncing feature remains the same.

The following contains our MainActivity

Here, we are defining our MainActivity showing the FlowersScreen composable passing to it a modifier object so that it fills the entire screen.

RestaurantsScreen

The following is the code of the RestaurantScreen:

The screen contains a Scafold layout with a top search bar and a lazy column to display the list of available restaurants as RestaurantItem composable.

We see at the top that we're using a view model in order to manage the state of this simple app.

RestaurantViewModel

Here's the code for our state holder

In this code, I'm defining a mutable state object holding the list of restaurants, so that once it changes, the changes are reflected in the UI.

the toggleIsFavourite is not relevant in our case, so simply ignore it.

In order to perform a simple keyword search, I created a Kotlin extension function that takes a query string and returns the sublist of restaurants for which the query string appears at least either in the title or in the description.

this function will be called once we perform the search in the top bar component

Here, we're defining a custom text field composable to perform the search.

  1. First, we're defining the text state of our text field.

  2. Second, we see the magic useDebounce function that will be fired up once the composable recomposes, which means once the state gets changed.
    This function is responsible for debouncing the user's click event. And it'll be our focus next.

Here's the definition of the useDebounce function

We see that useDebounce is an extension function for a generic type but we'll be using it to debounce the text string typed by the user.

  1. We're passing a coroutine scope which will be needed to launch the asynchronous calls later.

  2. We're defining the state, which will be the same if the text does not change, or else the state changes.

  3. We're using a compose effect-handler, once this composable leaves composition or one of the keys (its parameters) changes, the onDispose cleanup function will get execution
    When called, it launches a new coroutine inside the coroutine scope defined earlier that delays the execution for some time which is in our case 300 milliseconds and launches the actual search function.
    This effect requires defining a clean-up function named onDispose in which we're cancelling the job of the search coroutine.

With this, we achieve the intended behaviour of debouncing typing events.

Conclusion

In conclusion, the useDebounce function in Jetpack Compose proves to be an invaluable tool for handling user input and optimizing the performance of your app. By effectively managing input events and reducing unnecessary computations, useDebounce helps create a seamless and responsive user experience.

With useDebounce, developers can easily implement a delay mechanism that postpones the execution of a callback until a specified period of inactivity occurs. This feature is particularly useful when dealing with fast and frequent user interactions, such as search bars or filtering options. By postponing the execution of expensive operations until the user has finished typing or interacting, unnecessary computations are avoided, leading to improved performance and reduced resource consumption.

Furthermore, useDebounce offers flexibility by allowing developers to customize the delay duration according to their specific use cases. Whether it's a short delay for immediate feedback or a longer delay to avoid excessive re-rendering, the debounce function can be tailored to suit the unique requirements of each scenario.