RXSearchView delay after typing - android

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.

Related

RxJava emit only when emitter stops

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

Rx SearchView needs to cancel on going request with less priority

I am using
RxSearchView.queryTextChangeEvents
to detect the events of “Search As You Type” and also when you submit a search,

SAYT is to get suggestions and when you do a submit, it executes a full search.
There are 2 problems I am having at the moment.
When you are typing, and getting suggestions, but suddenly you click submit then it executes the full search but the problem is that if there is an on going suggestion request there might be the case that they appear on screen when they should not as the full search has priority.
So I would like to cancel (unsubscribe) the request from the suggestions in case there is a submit on the full search.
How can I achieve this with this code?
Another problem is that when I am deleting the search term in the search view and it gets empty, then it clears the adapter but there are cases when I clear the search text, there is still an on going suggestions request and it sets the results but it just clear, so I would like to guarantee that if the user clears the searchview, it cancels all the requests.
Here is the Code I am using.
RxSearchView.queryTextChangeEvents(searchView)
.skip(1)
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.filter(new Func1<SearchViewQueryTextEvent, Boolean>() {
#Override
public Boolean call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
final boolean empty = TextUtils.isEmpty(searchViewQueryTextEvent.queryText());
if (empty) {
//Dont show anything clear adapter
clearAdapter();
}
return !empty;
}
})
.concatMap(new Func1<SearchViewQueryTextEvent, Observable<Object>>() {
#Override
public Observable<Object> call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
String searchTerm = searchViewQueryTextEvent.queryText().toString();
boolean submitted = searchViewQueryTextEvent.isSubmitted();
//Hide RecyclerView
//Show loading indicator
showLoading();
Observable<Object> observable;
if (submitted) {
observable = getFullSearch(searchTerm);
} else {
observable = getSuggestionsSearch(searchTerm);
}
return observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(new Action0() {
#Override
public void call() {
//Show RecyclerView
//Hide loading indicator
showContent();
}
});
}
})
.subscribe(new Subscriber<Object>() {
#Override
public void onNext(Object object) {
//Set data on recyclerview and notify change.
setData(object);
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
});
}
You might want to try switchMap instead, it just uses the last emited value from the observable.
From the docs:
Returns a new Observable by applying a function that you supply to each item emitted by the source Observable that returns an Observable, and then emitting the items emitted by the most recently emitted of these Observables.
The resulting Observable completes if both the upstream Observable and the last inner Observable, if any, complete. If the upstream Observable signals an onError, the inner Observable is unsubscribed and the error delivered in-sequence.

Function not executing on background thread

I'm trying to apply RX to Android. I want when a button is clicked, to download something from web and display it.
My problem is that HttpClient.connect() executes on the mainThread instead of a background one.
The call to HttpClient.connect() executes as a function passed to Observable.map()
Observable<Integer> dayDeltas = Obs.obsToSequence(Obs.Observable(textView)); //transforms click events to observable
Observable<String> dates = dayDeltas.map(...).map(...)
dates.map(Obs.dateToWebPage()) // calls http.connect()
.map(Obs.parseEvents())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(updateTextView(textView));
public static Observable<Object> Observable(final TextView text) {
return Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(final Subscriber<? super Object> subscriber) {
final Object event = new Object();
text.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.e("click", "click");
subscriber.onNext(event);
}
});
}
});
Now my naive interpretation is that since i have .subscribeOn(Schedulers.newThread()) every function/operator on the observable should execute in a new thread, including .map(f). Clearly this is not what is happening, so what part of this chain does execute on new threads?
subscribeOn is there to trigger subscription side-effects. In your setup, it will register the callback to capture the button press on the new thread but when the press happens, the onNext emission is triggered by the main thread. The chain, including the network connect then executes on the main thread.
You have to put a new observeOn(Schedulers.io()) before the connecting method to make sure the reception of the button press event happens off the main thread.
Edit:
//transforms click events to observable
Observable<Integer> dayDeltas = Obs.obsToSequence(Obs.Observable(textView));
Observable<String> dates = dayDeltas.map(...).map(...)
dates
.observeOn(Schedulers.io()) // <------------------------------------------ add
.map(Obs.dateToWebPage()) // calls http.connect()
.map(Obs.parseEvents())
.observeOn(AndroidSchedulers.mainThread())
//.subscribeOn(Schedulers.newThread()) // <------------------------------- remove
.subscribe(updateTextView(textView));
After carefully reading the Scheduling and Threading RX doc, i have the solution:
dates
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.map(Obs.dateToWebPage())
In my original code, the schedulers passed to observeOn/subscribeOn were reversed and in the wrong place.

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.

RxJava -Do subscribers only run once

I have some confusion on subscribers and when they react to observers. Lets say i have the following simple observer with a subscriber that does an action:
Observable.just(preferences.getBoolean(C"vibrate", false))
.subscribeOn(Schedulers.io())//observe on new thread
.observeOn(AndroidSchedulers.mainThread()) //subscribe(listen) on main thread
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean shouldVibrate) {
if (shouldVibrate)
Toast.makeText(context,"i should vibrate now",Toast.SHORT).show();
}
});
I realize the observer gets called right away when this code is first seen. But what if the shared preference is changed again afterwards, will this code run again automatically or does it only run everytime i call subscribe ? What if i wanted it to run everytime the shared preference was altered (sort of like a watcher).
It really depends on the observable. I would suggest reading "Hot" and "Cold" Observables on the reactive Observable docs.
In your case, this is a Cold observable. It will resubscribe each time it is subscribed to. However, you only subscribe to it once. Your code snippet will actually block on the preferences fetch (probably not a huge problem), but it will only emit one preference.
I would suggest using the ContentObservable class in the RxAndroid extension lib for RxJava, which you are already using (because of AndroidSchedulers).
It would look something like this (This is back-of-napkin code, I have not compiled or ran this):
// Defer the observable so it gets a fresh preference value. Also, we'll
// be using it a few times.
final Observable<Boolean> vibratePreference = Observable.defer(
new Func0<Observable<Boolean>>() {
#Override
public Observable<Boolean> call() {
return Observable.just(preferences.getBoolean("vibrate", false));
}
});
vibratePreference
.concatWith(ContentObservable.fromSharedPreferencesChanges(preferences)
// Only consider changes to the vibrate preference.
.filter(new Func1<String, Boolean>() {
#Override
public Boolean call(final String key) {
return "vibrate".equals(key);
}
})
// Each time the preference changes, get the latest value.
.flatMap(new Func1<String, Observable<Boolean>>() {
#Override
public Observable<Boolean>(final String unusedKey) {
return vibratePreference;
}
}))
.scheduleOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( /* ...and so on. */ );
Also, if you are doing this on an activity or a fragment, I would strongly suggest looking into bindActivity and bindFragment in AppObservable in RxAndroid to make sure you are binding this observable to the lifecycle. You also may want to store a CompositeSubscription that you can empty in onPause and restore subscriptions in onResume. Those are slightly off-topic but will most likely be useful very soon.

Categories

Resources