Instant Search Using Kotlin Flow Operators

Authors
  • Amit Shekhar
    Name
    Amit Shekhar
    Published on
Instant Search Using Kotlin Flow Operators

I am Amit Shekhar, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.

Join my program and get high paying tech job: amitshekhar.me

Before we start, I would like to mention that, I have released a video playlist to help you crack the Android Interview: Check out Android Interview Questions and Answers.

In this blog, we will learn how to implement the instant search feature using Kotlin Flow operators in Android applications. We will also learn about all the operators used to implement this feature.

This blog is a part of the series I have written on Flow API in Kotlin:

Let's get started.

I will be using this project for the implementation part. You can find the complete code for the implementation mentioned in this blog in the project itself.

The following are the things of Kotlin Flow that we will be using to implement this search feature:

  • StateFlow:
  • Debounce Operator
  • Filter Operator
  • DistinctUntilChanged Operator
  • FlatMapLatest Operator

Earlier this instant search feature implementation in Android was not that easy with Kotlin Coroutines, but now with Kotlin Flow Operators, it has become easy and interesting.

Let's get started

First of all, we will create the extension function to return the StateFlow so that we can apply our required operators to that.

So here, on the SearchView, we will use the setOnQueryTextListener to observe for the changes in the text, change the state of the query, and finally return the StateFlow like below:

fun SearchView.getQueryTextChangeStateFlow(): StateFlow<String> {

    val query = MutableStateFlow("")

    setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            return true
        }

        override fun onQueryTextChange(newText: String): Boolean {
            query.value = newText
            return true
        }
    })

    return query

}

We have also simulated the data from the network with delay like below:

/**
 * Simulation of network data
 */
private fun dataFromNetwork(query: String): Flow<String> {
    return flow {
        delay(2000)
        emit(query)
    }
}

Now, on the QueryTextChangeStateFlow, we will apply the operators like below:

searchView.getQueryTextChangeStateFlow()
    .debounce(300)
    .filter { query ->
        if (query.isEmpty()) {
            textViewResult.text = ""
            return@filter false
        } else {
            return@filter true
        }
    }
    .distinctUntilChanged()
    .flatMapLatest { query ->
        dataFromNetwork(query)
            .catch {
                emitAll(flowOf(""))
            }
    }
    .flowOn(Dispatchers.Default)
    .collect { result ->
        textViewResult.text = result
    }

Now, it's time to learn why the above operators are used and how they work when they are put together.

Understanding Operators

  • Debounce: Here, the debounce operator is used with a time constant. The debounce operator handles the case when the user types “a”, “ab”, “abc”, in a very short time. So, there will be so many network calls. But the user is finally interested in the result of the search “abc”. So, we must discard the results of “a” and “ab”. Ideally, there should be no network calls for “a” and “ab” as the user typed those in a very short time. So, the debounce operator comes to the rescue. The debounce will wait for the provided time for doing anything, if any other search query comes in between that time, it will ignore the previous item and start waiting for that time again with the new search query. If nothing new comes in that given constant time, it will proceed with that search query for further processing. So, debounce only emit an item, if a particular timespan has passed without it emitting another item.
  • Filter: The filter operator is used to filter the unwanted string like an empty string in this case to avoid the unnecessary network call.
  • DistinctUntilChanged: The distinctUntilChanged operator is used to avoid duplicate network calls. Let say the last on-going search query was “abc” and the user deleted “c” and again typed “c”. So again it’s “abc”. So if the network call is already going on with the search query “abc”, it will not make the duplicate call again with the search query “abc”. So, distinctUntilChanged suppress duplicate consecutive items emitted by the source.
  • FlatMapLatest: Here, the flatMapLatest operator is used to avoid the network call results which are not needed more for displaying to the user. Let say the last search query was “ab” and there is an ongoing network call for “ab” and the user typed “abc”. Then, we are no more interested in the result of “ab”. We are only interested in the result of “abc”. So, the flatMapLatest comes to the rescue. It only provides the result for the last search query(most recent) and ignores the rest.

Note: If you notice inside the flatMapLatest, if we are getting any error, we are passing the empty result. We can change this based on our requirements.

This way, we are able to implement the instant search feature using Kotlin Flow Operators in an Android application.

You can find the end to end implementation in this project.

Prepare yourself for Android Interview: Android Interview Questions

That's it for now.

Thanks

Amit Shekhar

You can connect with me on:

Read all of my high-quality blogs here.