onTextChanged
EditText is a commonly used text input control, but its callback interface is not designed to be friendly. Three interfaces need to be implemented, and I only care about most scenariosonTextChanged
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
Log.d(TAG, "query: ${
s}")
}
})
And even the most concerned onTextChanged
, because its semantics are too simple (callback every time it changes), in some business scenarios can not achieve the expected results, such as common 实时搜索
scenarios, hope to be able to enter the keyword in the input box to perform real-time If you use Lenovo or Search at this time EditText#onTextChanged
, you will encounter many problems:
Multiple callbacks
For example, in the above code, input one a b c d e
by one, and get the following log
query: a
query: ab
query: ab
query: abc
query: abcd
query: abcd
Among them, ab and abcd appear twice. After investigation, it is related to the setting of EditText
android-TextWatcher events are being fired multiple times-Stack Overflow.
Of course, this problem does not appear in the real-time search scene. It also reflects how pitted this control is
Frequently triggered
While it is desirable to have the input feedback 实时
effect, but do not want too sharp (high maintenance ||| human true), when a combination of fast input, for example, "A" "AA" "AAA" "AAAA" ,说明我们的目的性很强,只对最后的
"AAAA" ` Just get the result
Asynchronous result is incorrect
Since an asynchronous call is “a” “aa”
initiated after input, it is possible that the callback timing of the results of multiple consecutive asynchronous requests does not meet expectations. For example, input triggers two asynchronous requests, but it is possible that the result of "a" is returned, and the input box has already Stayed in the "aa" state.
Use RxJava optimization
All of the above problems are caused EditText#onTextChanged
by the callback semantics that cannot meet the expected business scenarios. At this time, RxJava can be used to optimize them
Callback multiple times
val queryPublisher = PublishSubject.create<String>()
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
queryPublisher.onNext(s.toString())
}
})
queryPublisher.distinctUntilChanged().subscribe({
Log.d(TAG, "query: ${
it}")
})
PublishSubject
Provides a Stream pipeline, onTextChanged
the original callback received is
distinctUntilChanged
used to ensure that each value is only called back once
query: a
query: ab
query: abc
query: abcd
Frequent trigger
queryPublisher.distinctUntilChanged()
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
Log.d(TAG, "query: ${
it}")
})
Use to debounce
add anti-shake. It should be noted that throttleLast
although it is also a similar function operator, the effect does not meet the expectations of this requirement. Let's compare the differences between the two:
-
After debounce
sends the data for a period of time, if there is no new data, the data is actually sent out; if there is new data sent during this period of time, this data is used as the data item to be sent, and the timing is restarted.
-
throttleLast
sends the last data in each time period. The user may request a result in the middle of continuous input, which does not meet expectations.
Asynchronous result is incorrect
There are two solutions:
dipose
private var searchDisposable: Disposable? = null
override fun onCreate(savedInstanceState: Bundle?) {
// 略...
queryPublisher.distinctUntilChanged()
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
Log.d(TAG, "query: ${
it}")
searchDisposable?.dispose()
searchDisposable = search(query = it)
.subscribe({
// 搜索结果显示
})
})
}
/**
* 结果请求
*/
fun search(query: String?): Observable<ArrayList<String>> {
// 略...
return Observable.empty<ArrayList<String>>()
}
When it gets new input, manually stop the previous asynchronous request
switchMap
switchMap
The operator is dispose
more elegant relative to
RxJava also implements the switchMap operator. It behaves much like flatMap, except that whenever a new item is emitted by the source Observable, it will unsubscribe to and stop mirroring the Observable that was generated from the previously-emitted item, and begin only mirroring the current one.
switchMap
And flatMap
similar, it can also be used to switch Stream, but when the switch to a new switchMap Stream, the old stream stops automatically. Through the comparison with flatMap, understand its meaning:
-
flatMap
-
switchMap
The above example, after the transformation of switchMap, the code is much resolute:
override fun onCreate(savedInstanceState: Bundle?) {
// 略...
queryPublisher.distinctUntilChanged()
.debounce(500, TimeUnit.MILLISECONDS)
.switchMap {
search(it) }
.subscribe({
// 搜索结果显示
Log.d(TAG, "result: ${
it}")
})
}