RxJava emit only when emitter stops - android

In an Android app, I'd like to refresh the list only once the user has stopped selecting a list of items in a List. So in effect, I'd like to the observer to be informed only once the producer has stopped emitting for at least 500ms.
Right now I have something like the following:
Subject<Object> _bus = PublishSubject.create().toSerialized();
...
_bus.onNext(new Event());
...
_bus.delay(500, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(Schedulers.computation())
.subscribe(event -> {
// Do something with event
}));
This is fine, except it emits at 500 ms intervals even if the source is still emitting. I'd like to wait for 500ms to see if the source has stopped calling onNext() and only then emit.
Is this possible?

So basically you need debouncing with buffer. There is article which should helper you.
And kick off sample from that article:
Observable<Object> tapEventEmitter = _rxBus.toObserverable().share();
Observable<Object> debouncedEventEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS);
Observable<List<Object>> debouncedBufferEmitter = tapEventEmitter.buffer(debouncedEventEmitter);
debouncedBufferEmitter.buffer(debouncedEventEmitter)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Object>>() {
#Override
public void call(List<Object> taps) {
_showTapCount(taps.size());
}
});

I think you have to used debounce operator instead of delay eg.
_bus.debounce(500, TimeUnit.MILLISECONDS
.distinctUntilChanged()
.observeOn(Schedulers.computation())
.subscribe(event -> {
// Do something with event
}));

Related

RxJava - How to stop a running task when new item emitted

I am building searching feature for my Android app with RxJava. When users type/change query characters in an EditText, an emitter will emit new query text and I search around my database to get results that match the query.
getEditTextObservable()
.subscribeOn(Schedulers.computation())
.debounce(500, TimeUnit.MILLISECONDS)
.filter {
!it.isNullOrEmpty()
}
.map {
//start searching
getResultsInDatabase(it) //this function takes a long time to complete
}.observeOn(AndroidSchedulers.mainThread())
.subscribe{
//render results on screen
}
The method getResultsInDatabase(string:String) take a long time to complete, when the query text changed, I want to stop method getResultsInDatabase(string:String) (in case it is running with previously emitted query) to run it again with new query. How can I do to achieve that? I would appreciate your helps. Thank you for reading my question.
You need a switchMap.
getEditTextObservable()
.subscribeOn(Schedulers.computation())
.debounce(500, TimeUnit.MILLISECONDS)
.filter { !it.isNullOrEmpty() } //Be careful with this. Should clearing text clear results too?
.switchMap {
Observable.fromCallable { getResultsInDatabase(it) }.subscribeOn(Schedulers.io())
}.observeOn(AndroidSchedulers.mainThread())
.subscribe{
//render results on screen
}
You can use valve to pause a stream in RxJava. Based on the value of it, the stream continues to emit or will be kept in a paused state.

Wait for an Observable to finish before executing another Observable?

The Problem
I have an activity which fetches data from an API periodically and displays the data received. The API uses OAuth so I receive a temporary access token which expires after a certain period of time (1 hr). If the app tries to get data with an expired token, obviously the request will fail. In an earlier iteration of my app, I was using AsyncTasks for the network requests and essentially just executed a new AsyncTask that would get a new access token before calling the main AsyncTask that fetches the data from the server. This worked great because the main AsyncTask would wait until the other one was finished before executing.
I recently switched to RxJava and basically just replaced the AsyncTasks with Observables. The problem is that the main Observable that fetches the data doesn't wait for the Observable that refreshes the access token to finish. Here's my code, thanks for your help.
Code
LiveThreadActivity.java
private Subscription subscription;
private Observable<List<CustomComment>> fetchData;
#Override
protected void onResume() {
super.onResume();
if (tokenExpired()) {
auth.refreshToken();
}
subscription = fetchData
.compose(bindToLifecycle())
.retryWhen(new RetryWithDelay(5, 2000))
.subscribe(list -> addNewComments(list), e -> handleFetchDataError(e));
}
// This method gets called in onCreate()
private void dataCollection() {
fetchData = Observable.interval(0, REFRESH_RATE, TimeUnit.MILLISECONDS)
.map(tick -> fetchNewComments()) // Run function every time a tick is emitted
.retryWhen( new RetryWithDelay(2, 2000) ) // Retry twice with 2 second delay
.subscribeOn(Schedulers.io()) // Network stuff in background thread
.observeOn(AndroidSchedulers.mainThread()); // Other stuff on the main thread
}
Auth.java
public class Auth {
...
public void refreshToken() {
Observable.just(1)
.map(y -> refreshAccessToken())
.retryWhen( new RetryWithDelay(3, 2000) )
.subscribeOn(Schedulers.io())
.subscribe();
}
}
Using reactive libraries a new way of thinking is needed. You have to write the code as it is synchronious, but be aware that it evecutes asynchroniously.
Your code just executes synchoniously. It executes two Observable's at the same time.
The function refreshToken() should look like:
public Observable<?> refreshToken() {
return Observable.just(1)
.map(y -> refreshAccessToken())
.retryWhen( new RetryWithDelay(3, 2000) )
.subscribeOn(Schedulers.io());
}
And onResume():
#Override
protected void onResume() {
super.onResume();
Observable obs = fetchData
.compose(bindToLifecycle())
.retryWhen(new RetryWithDelay(5, 2000));
if (tokenExpired()) {
obs = obs.startWith(auth.refreshToken());
}
subscription = obs
.subscribe(list -> addNewComments(list), e -> handleFetchDataError(e));
}
Notice startWith() operator. It allows to executes one Observable (fetching list) after another (refreshing token).
.flatMap() will probably be sufficient, i.e. tokenObservable.flatMap(/* return dataObservable */)

RXSearchView delay after typing

I have implemented SearchView widget and ContentProvider integration.
My concern is not to have the user spam the web service for every letter being typed or removed in the EditText of the SearchView. So the query of the ContentProvider should be triggered after user has stopped typing, say with a threshold of like 2 seconds.
I have not had luck doing this and I thought RXSearchView is the solution to that using debounce. However, even setting a 10 second delay does not to work. Am I on the right track? Or I need to use a different method/property.
int time = 10000;
RxSearchView.queryTextChanges(searchView)
.delay(time, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.debounce(time, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.throttleWithTimeout(time, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread());
This is the method I used and works fine:
1- First create a Listener for setOnQueryTextChangedListener:
searchView.setOnQueryTextListener(new OnQueryTextListener()...);
2- In your onQueryTextChange method call onNext of your subscriber:
#Override
public void onQueryTextChange(String s) {
subscriber.onNext(s);
}
3- Use debounce method to emit:
.filter(new Func1<String, Boolean>() {
#Override
public Boolean call(String s) { //make sure query is not empty
return !TextUtils.isEmpty(s);
}
})
.debounce(500, TimeUnit.MILLISECONDS) //wait 500ms to
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<TESTTTT>())
For complete code see this commit in iosched; Main part of the code is in onCreateOptionsMenu.

RxJava/Android monitor progress of multiple subscribers fired at different times

I am looking for a way, hopefully using RxJava for consistency, to monitor the progress of multiple subscribers that may be fired at different times. I am aware of how to merge or flatMap subscribers together when they are all fired from one method but I am unaware of a way to do it when they are fired at different times from different methods.
For example, if I have 2 long running tasks attached to button presses. I push button 1 and fire off the observable/subscriber, half way through running I push button 2 to fire off the second observable/subscriber.
I want to enable a button when no tasks are running and disable it when one or more tasks are running.
Is this possible? I am trying to avoid setting instance variable flags as well.
I would use a separate BehaviorSubject and scan to monitor execution status. This is quite similar to an instance variable, but probably it can inspire you to a better solution. Something like this:
private final BehaviorSubject<Integer> mProgressSubject = BehaviorSubject.create(0);
public Observable<String> firstLongRunningOperations() {
return Observable.just("First")
.doOnSubscribe(() -> mProgressSubject.onNext(1))
.finallyDo(() -> mProgressSubject.onNext(-1)));
}
public Observable<String> secondLongRunningOperations() {
return Observable.just("Second")
.doOnSubscribe(() -> mProgressSubject.onNext(1))
.finallyDo(() -> mProgressSubject.onNext(-1));
}
public Observable<Boolean> isOperationInProgress() {
return mProgressSubject.asObservable()
.scan((sum, item) -> sum + item)
.map(sum -> sum > 0);
}
Usage will be like this:
isOperationInProgress()
.subscribe(inProgress -> {
if (inProgress) {
//disable controls
} else {
//enable controls
}
});
With this approach you can have any number of long running operation and you do not have to fire them all. Just don't forget to call doOnSubscribe and finallyDo.
PS. Sorry, I didn't test it, but it should work.
To make this possible, let both long running operations emit an onNext event on a PublishSubject. Combine both Subjects with a zip or combineLatest function and subscribe to this. Once the combine function receives an event, this means that both Subjects have emitted an onNext event, thus both long running operations have finished and you can enable the 3rd button.
private PublishSubject<Boolean> firstSubject = PublishSubject.create();
private PublishSubject<Boolean> secondSubject = PublishSubject.create();
#Override
public void onStart() {
super.onStart();
subscribeToResult();
}
private Observable<Integer> firstOperation() {
return Observable.just(100)
.delay(1000) // takes a while
.subscribe(tick -> firstSubject.onNext(true));
}
private Observable<Integer> firstOperation() {
return Observable.just(200)
.delay(1000) // takes a while
.subscribe(tick -> secondSubject.onNext(true));
}
private void subscribeToResult() {
Observable.zip(
firstSubject,
secondSubject,
(firstResult, secondResult) -> return true
).subscribe(
tick -> thirdButton.setEnabled(true)
)
}
Definitely take a look at the RxJava combine functions.

Debouncing button clicks using Rx

I'm trying to make a simple "button debouncer" which will count filtered clicks and display it thru a TextView. I want to filter rapid/spam clicks in a way that clicks with less than 300ms time-gap in-between are ignored.
I did my research and stumbled upon Rx's awesome debounce() which in theory should do the exact thing I wanted..
..or so I thought. As the app seemed to only register the first click; the counter won't increment no matter how long I tried to wait.
Here's a piece of my code:
...
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS)
.subscribe(new Subscriber<Object>() {
public int mCount;
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object o) {
mText.setText(String.valueOf(++mCount));
}
});
...
What am I doing wrong? I've tried to run the thing without debounce() and it worked flawlessly (the counter will increment everytime the button got clicked).
Thanks in advance!
Note the following in the documentation on the debounce operator:
This variant operates by default on the computation Scheduler (...)
Or, code-wise, this currently happens:
public final Observable<T> debounce(long timeout, TimeUnit unit) {
return debounce(timeout, unit, Schedulers.computation());
}
As a result, the subscriber's callbacks are invoked on that same computation scheduler, since nothing is explicitly instructing otherwise.
Now, attempting to update a view (that's what's happening in onNext()) from any other thread than the main/ui thread, is a mistake and it will lead to undetermined results.
Fortunately, the remainder of the quote above provides the solution too:
(...) but you can optionally pass in a Scheduler of your choosing as a third parameter.
This would lead to:
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(...);
Alternatively, you can still let the debounce happen on the computation scheduler, but receive the notifications on the main/ui thread:
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
Either way will ensure that the notifications are received on the main/ui thread and thus that the view is updated from the correct thread.

Categories

Resources