Use RxJava to optimize EditText#onTextChanged callback

Insert picture description here

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 eby 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#onTextChangedby 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}")
})

PublishSubjectProvides a Stream pipeline, onTextChangedthe original callback received is
distinctUntilChangedused 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 debounceadd anti-shake. It should be noted that throttleLastalthough 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.
    Insert picture description here

  • 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.
    Insert picture description here

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

switchMapThe operator is disposemore 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.

switchMapAnd flatMapsimilar, 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
    Insert picture description here

  • switchMap
    Insert picture description here

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}")
            })
}

Guess you like

Origin blog.csdn.net/vitaviva/article/details/107879099