I have a textfield to validate, I want to disable a button whenever user is typing. After user stops typing (debounce by 1 second), the validation is carried out and the button is conditionally enabled based on the result. Notice the corner case when user typed only one character, validation should still happen.
--"a"-"ab"-"abc"------------------"ab"--"a"------------------"ab"-----------------
--false---------validate("abc")---false----validate("a")-----false--validate("ab")
This SO (Deliver the first item immediately, 'debounce' following items) proposes the following solution in RxJava. But it seems only returns the very first element, not when user starts typing again after debounce? Correct me if I am wrong
Observable.from(items).publish(publishedItems ->
publishedItems.limit(1).concatWith(
publishedItems.skip(1).debounce(1, TimeUnit.SECONDS)
)
)
After some thinking, I was able to fully solve it in the following way, everything works as I intended
let input = textField.rx.text.distinctUntilChanged()
let keystroke = input.map { _ in Observable.just(false) }
let validate = input
.flatMap { Observable.from(optional: $0) } // 1
.filter { $0.count >= minimumTextLength }
.debounce(1, scheduler: MainScheduler.instance)
.map { self.networkManager.validate($0).asObservable() } // 2
return Observable.merge(keystroke, validate).switchLatest().distinctUntilChanged() // 3
validate
returns Single<Bool>
in my case, so I turn it into Observable
. Notice I intentionally used map
not flatMap
in order to use the functionality switchLatest
provides in step 3Observable<Observable<Bool>>
, switchLatest
allows me to ignore the validate
result if user starts typing again. distinctUntilChanged
discards repeated false
s