Instant Search Using Kotlin Flow Operators
- Authors
- Name
- Amit Shekhar
- Published on
I am Amit Shekhar, Co-Founder @ Outcome School, 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 Outcome School and get high paying tech job: Outcome School
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:
- Mastering Flow API in Kotlin
- Creating Flow Using Flow Builder in Kotlin
- Terminal Operators in Kotlin Flow
- Cold Flow vs Hot Flow
- StateFlow and SharedFlow
- Long-running tasks in parallel with Kotlin Flow
- Retry Operator in Kotlin Flow
- Retrofit with Kotlin Flow
- Room Database with Kotlin Flow
- Kotlin Flow Zip Operator for Parallel Multiple Network Calls
- Instant Search Using Kotlin Flow Operators - YOU ARE HERE
- Exception Handling in Kotlin Flow
- callbackFlow - Callback to Flow API in Kotlin
- Unit Testing ViewModel with Kotlin Flow and StateFlow
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. Thedebounce
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, thedebounce
operator comes to the rescue. Thedebounce
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, theflatMapLatest
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
Co-Founder @ Outcome School
You can connect with me on:
Follow Outcome School on: