I have the following method that makes a request to get a pokemon from a endpoint.
I would like to prevent the user in making rapid requests by clicking quickly on the button that will invoke this method many times. I have used the throttle* methods and debounce.
Bascially, what I am looking for if the user rapidly clicks on the button within 300 milliseconds duration it should accept the last click in that duration. However, what i am experiencing is that all requests are being made. i.e. if the user rapidly clicks 3x within that duration I still get 3 requests.
fun getPokemonDetailByName(name: String) {
pokemonDetailInteractor.getPokemonDetailByName(name)
.subscribeOn(pokemonSchedulers.background())
.observeOn(pokemonSchedulers.ui())
.toObservable()
.throttleFirst(300, TimeUnit.MILLISECONDS)
.singleOrError()
.subscribeBy(
onSuccess = { pokemon ->
pokemonDetailLiveData.value = pokemon
},
onError = {
Timber.e(TAG, it.localizedMessage)
}
).addTo(compositeDisposable)
}
Bascially, what I am looking for if the user rapidly clicks on the button within 300 milliseconds duration it should accept the last click in that duration
to me sounds more like that debounce operator behaviour. From the documentation
Debounce — only emit an item from an Observable if a particular timespan has passed without it emitting another item
you can see the marble diagram here
private val subject = PublishSubject.create<String>()
init {
processClick()
}
fun onClick(name: String) {
subject.onNext(name)
}
private fun processClick() {
subject
.debounce(300, TimeUnit.MILLISECONDS)
.switchMap { getPokemonDetailByName(it) }
.subscribe(
{ pokemonDetailLiveData.value = it },
{ Timber.e(TAG, it.localizedMessage) }
)
}
private fun getPokemonDetailByName(name: String): Observable<Pokemon> =
pokemonDetailInteractor
.getPokemonDetailByName(name)
.subscribeOn(pokemonSchedulers.background())
.observeOn(pokemonSchedulers.ui())
.toObservable()
In your case, the getPokemonDetailByName
creates a new subscription every time. Instead, send the click events to a Subject
, create a single subscription to that stream and apply debounce
.
The getPokemonDetailByName()
subscribes to a new stream every time it is called.
Instead of subscribing to a new stream every time, just provide a subject to send the data to and map it directly to a LiveData
with LiveDataReactiveStreams.fromPublisher()
.
private val nameSubject = PublishSubject.create<String>()
val pokemonDetailLiveData = nameSubject.distinctUntilChanged()
.observeOn(pokemonSchedulers.background())
.switchMap(pokemonDetailInteractor::getPokemonDetailByName)
.doOnError { Timber.e(TAG, it.localizedMessage) }
.onErrorResumeNext(Observable.empty())
.toFlowable(BackpressureStrategy.LATEST)
.to(LiveDataReactiveStreams::fromPublisher)
fun getPokemonDetailByName(name: String) {
nameSubject.onNext(name)
}
The observeOn(pokemonSchedulers.background())
operator is needed as subjects handle subscriptions differently. The onErrorResumeNext(Observable.empty())
ensures that only valid objects end up in the LiveData
.
Like this only a single stream is subscribed to once the pokemonDetailLiveData
is being observed. The PublishSubject
ensures that only a user click triggers the update from the API and only a single API call is active at the same time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With