Take a break from your busy schedule to write a small article. I will write the final chapter of the sigo coroutine network request, which has been delayed for a year, for the Chinese New Year
The introduction
In order to avoid meaningless search requests when users search, search data flow is usually limited. Familiar with RxJava students, will know how to do, all kinds of operators dazzling you.
If you use kotlin’s companion, you don’t have to use RxJava because kotlin’s own Flow will do the job. Cut to the chase.
The reason that inspired me to write this article was the search optimization of Kotlin Flow development application Practice. This article mistakenly used Flow. Now the author has corrected the errors in the article, but I still want to take the opportunity to explain it. Then give the correct explanation and correct code.
The wrong in which
Let me post the wrong code from that article:
// Error code 🙅
binding.etSearch.doOnTextChanged { text, _, _, _ ->
searchFilter(text.toString())
}
private fun searchFilter(str:String){
flow { emit(str) }
.debounce(400)
.filter {
it.isNotEmpty()
}
.catch { LogUtils.d(it.message) }
.flowOn(Dispatchers.Default)
.onEach {
LogUtils.d("Output:$it")
binding.tvShow.text = it.toString()
}.flowOn(Dispatchers.Main)
.launchIn(lifecycleScope)
}
Copy the code
Take a look at the code above and ask yourself what went wrong?
The text input box etSearch returns the searchFilter() method every time the text changes, and each time the method instantiates a flow and then uses the debounce() stream limiting. So what’s the point of this debounce() stream limiting? It doesn’t make any sense, does it? Because it’s a new flow every time.
If you don’t understand, look at the simplified code below:
// Simplified article error code 🙅
for (i in 0.100.) {
// The simulation generates data
flow<Int> {
emit(i)
}.debounce(500) // This is not valid because the emit of flow was executed only once...
.collect {
println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > > >$it")}}Copy the code
So our ideal correct code logic looks like this:
flow<Int> {
for (i in 0.100.) {
// The simulation generates data
emit(i)
}
}.debounce(500) // This is a valid current limit
.collect {
println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > > >$it")}Copy the code
Okay, so if you compare these two pieces of code,for
The loop is the input data that we simulate, thisfor
Loop on the inside and on the outside are two completely different logic ~ no more explanation
Correct usage
What you think is the right way to write it
For business logic like input boxes, flow alone won’t do the job because you can’t write it out. Some children to stand up and shout, “how can not write out, you say nonsense, I will write”, a operation to write the following code:
// Error code written by children 🙅
flow<Editable> {
editText.doAfterTextChanged { text ->
emit(text) // This is an error. Emit cannot be written to an inner class
}
}.debounce(500)
.collect {
println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > > >$it")}Copy the code
I wrote a comment on the error. The emit is a suspend function. It cannot be written inside the inner class.
The real correct way to write it
I want to start with a point that I want you to remember. Flow in RxJava is divided into cold Observable and hot Observable. What do you mean, hot and cold? Dear, it is recommended that you stir-fry twice-cooked pork.
inRxJava
The use of not paying attention to distinguish between hot and cold flow is causedRxJava
One of the reasons for misuse and abuse!
Here I will simply explain hot and cold flow in two sentences, without expanding on RxJava.
-
Cold flow: Upstream initiates transmit data only when the observer subscribers
-
Heat flow: Upstream data is emitted with or without an observer
The flow is the flow of cold
Directly annotate:
flow {
// Transmit data
}.collect {
/* Only when the stream is subscribed to collect or collectLast is executed will the code block in the upstream flow be executed! * /
}
Copy the code
Now, calm down and think about the business scenario of the input field. That is: The data is sent whenever the EditText text changes, regardless of the subscriber. Then we should use heat flow to solve the problem.
StateFlow heat flow
Let’s go straight to the code:
// Define a global StateFlow
private val _etState = MutableStateFlow("")
override fun onCreate(savedInstanceState: Bundle?). {
et.doAfterTextChanged { text ->
// Write data to the stream_etState.value = (text ? :"").toString()
}
lifecycleScope.launch {
_etState
.sample(500) // Limit the current to 500 milliseconds
.filter {
// Filter out the empty text
it.isNotBlank()
}.collect {
// Subscribe to data
println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > > >$it")}}}Copy the code
All right, that’s it. Google explains StateFlow this way: “Unlike the cold data flow built using the Flow builder, StateFlow is a hot data flow: collecting data from the data flow does not trigger any provider code. StateFlow is always active and exists in memory, and is eligible for garbage collection only if no other reference to it is involved in the garbage collection root.”
Document links: StateFlow and SharedFlow
Homework after class
In the final code, I used the sample() method to limit streams, instead of the debounce() method. So what’s the difference between these two methods of limiting flow? If you’re interested, try it out in your own code, and you’ll come to your own conclusions.
For this scenario, I prefer the sample() method to limit streams.