synchronous calls with rxjava Android - android

My app has a SearchView. When the user types in the SearchView the onQueryTextChange passes the query to the presenter and then it calls the API. I am using Retrofit and RxJava for the calls. The calls return a json file with the words containing what the user typed so far. The problem is that, if the user is fast to type letters and the network is slow sometimes the SearchView doesn't show the results based on all the typed letters but maybe up to the second last because the last call was quicker to get the results compared to the second last.
Example:
the user start typing:
"cou" -> make a call to the API (first call after 3 letters) -> start returnin values
"n" -> make a call -> start returning values
"t" -> make a call -> start returning values
"r" -> make a call (the connection is slow)
"y" -> make a call -> start returning values
-> "r" get the results finally and the returns them
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
}
The call is very simple and whenever I get an error I don't want to display anything.
Is there a way to solve that? Or maybe this is not the case to use reactive programming?
EDIT:
Just to make more clear, the flow is the following:
Activity that uses a custom search view (https://github.com/Mauker1/MaterialSearchView)
the custom searchview has a listener when the user starts typing. Once the user starts typing the activity calls the Presenter.
the presenter will subscribe an observable returned by the interactor:
presenter:
addSubscription(mInteractor.getValues(query)
.observeOn(mMainScheduler)
.subscribeOn(mIoScheduler)
.subscribe(data -> {
getMvpView().showValues(data);
}, e -> {
Log.e(TAG, e.getMessage());
}));
interactor:
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(2, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
So now either I change the custom search view in a 'normal' searchview and then use RxBinding or maybe I should use an handler or something like that (but still struggling how to fit it in my architecture)

Firstly make your Searchview as Observable so that you can apply Rx operators.
To convert searchview into Observable
public static Observable<String> fromview(SearchView searchView) {
final PublishSubject<String> subject = PublishSubject.create();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String s) {
subject.onComplete();
searchView.clearFocus(); //if you want to close keyboard
return false;
}
#Override
public boolean onQueryTextChange(String text) {
subject.onNext(text);
return false;
}
});
return subject;
}
private void observeSearchView() {
disposable = RxSearchObservable.fromview(binding.svTweet)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(text -> !text.isEmpty() && text.length() >= 3)
.map(text -> text.toLowerCase().trim())
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
You can apply filter, condition
RxJava debounce() operator to delay taking any action until the user pauses briefly.
Use of distinctUntilChanged() ensures that the user can search for the same thing twice, but not immediately back to back
The filter operator is used to filter the unwanted string like the empty string in this case to avoid the unnecessary network call.
Handling searchview withRXJava

You're in luck there's an operator for that called debounce
Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(3, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
What debounce does is wait N time units for more results prior to continuing. Say for example the network takes 2 seconds to return and you flood it with request after request, debounce will wait for 3 seconds of no results and then return the last result. Think of it as dropping everything but the one before N time of inactivity.
This solve your problem but will still flood the network, ideally you would use the excellent RxBinding library do the defer prior to making the request something like:
RxTextView.textChanges(searchView)
.debounce(3, TimeUnit.SECONDS)
.map(input->mNetworkService.getAPI().getValues(input.queryText().toString()))
.retry(2)
.onErrorReturn(e -> new ArrayList<>()))
With the current setup it will wait 3 seconds after a user types something and only then make the network call. If instead they start typing something new, the first pending search request gets dropped.
Edit: changed to RxTextView.textChanges(textview) based on OP not using an android SearchView widget

Extending on what #MikeN said, if you want to only use the results of the LAST input, you should use switchMap() (which is flatMapLatest() in some other Rx implementations).

I solved the flooding issue without using RxBinding and I want to post my solution just in case someone else needs it.
So whenever the onTextChanged is called I check, first of all, if the size is > 2 and if it is connected to the network (boolean updated by a BroadcastReceiver). Then I create message to be sent has delayed and I delete all the other messages in the queue. This means that I will execute only the queries that are not within the specified delay:
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.getTrimmedLength(s) > 2 && isConnected) {
mHandler.removeMessages(QUERY_MESSAGE);
Message message = Message.obtain(mHandler, QUERY_MESSAGE, s.toString().trim());
mHandler.sendMessageDelayed(message, MESSAGE_DELAY_MILLIS);
}
}
Then the Handler:
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (msg.what == QUERY_MESSAGE) {
String query = (String)msg.obj;
mPresenter.getValues(query);
}
}
};

Add rxbinding dependency to gradle implementation "com.jakewharton.rxbinding2:rxbinding-kotlin:2.1.1"
Use debounce and distinct for ignoring frequent key input and duplicate input
Dispose previous API call for getting only latest search result
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.toolbar_menu, menu)
// Associate searchable configuration with the SearchView
val searchManager = requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.setSearchableInfo(
searchManager.getSearchableInfo(requireActivity().componentName)
)
searchView.maxWidth = Integer.MAX_VALUE
// listening to search query text change
disposable = RxSearchView.queryTextChangeEvents(searchView)
.debounce(750, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
callApi(it.queryText().toString())
}, {
Timber.e(it)
})
super.onCreateOptionsMenu(menu, inflater)
}
private fun callApi(query: String){
if(!apiDisposable.isDisposed){
apiDisposable.dispose()
}
apiDisposable = mNetworkService.getAPI(query)
}

Related

How to implement Periodic processing of user input?

My current Android application allows users to search for content remotely.
e.g. The user is presented with an EditText which accepts their search strings and triggers a remote API call that returns results that match the entered text.
Worse case is that I simply add a TextWatcher and trigger an API call each time onTextChanged is called. This could be improved by forcing the user to enter at least N characters to search for before making the first API call.
The "Perfect" solution would have the following features:-
Once the user starts entering search string(s)
Periodically (every M milliseconds) consume the entire string(s) entered. Trigger an API call each time the period expires and the current user input is different to the previous user input.
[Is it possible to have a dynamic timeout related to the entered texts length? e.g while the text is "short" the API response size will be large and take longer to return and parse; As the search text gets longer the API response size will reduce along with "inflight" and parsing time]
When the user restarts typing into the EditText field restart the Periodic consumption of text.
Whenever the user presses the ENTER key trigger "final" API call, and stop monitoring user input into the EditText field.
Set a minimum length of text the user has to enter before an API call is triggered but combine this minimum length with an overriding Timeout value so that when the user wishes to search for a "short" text string they can.
I am sure that RxJava and or RxBindings can support the above requirements however so far I have failed to realise a workable solution.
My attempts include
private PublishSubject<String> publishSubject;
publishSubject = PublishSubject.create();
publishSubject.filter(text -> text.length() > 2)
.debounce(300, TimeUnit.MILLISECONDS)
.toFlowable(BackpressureStrategy.LATEST)
.subscribe(new Consumer<String>() {
#Override
public void accept(final String s) throws Exception {
Log.d(TAG, "accept() called with: s = [" + s + "]");
}
});
mEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
#Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
publishSubject.onNext(s.toString());
}
#Override
public void afterTextChanged(final Editable s) {
}
});
And this with RxBinding
RxTextView.textChanges(mEditText)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe(new Consumer<CharSequence>(){
#Override
public void accept(final CharSequence charSequence) throws Exception {
Log.d(TAG, "accept() called with: charSequence = [" + charSequence + "]");
}
});
Neither of which give me a conditional filter that combines entered text length and a Timeout value.
I've also replaced debounce with throttleLast and sample neither of which furnished the required solution.
Is it possible to achieve my required functionality?
DYNAMIC TIMEOUT
An acceptable solution would cope with the following three scenarios
i). The user wishes to search for the any word beginning with "P"
ii). The user wishes to search for any word beginning with "Pneumo"
iii). The user wishes to search for the word "Pneumonoultramicroscopicsilicovolcanoconiosis"
In all three scenarios as soon as the user types the letter "P" I will display a progress spinner (however no API call will be executed at this point). I would like to balance the need to give the user search feedback within a responsive UI against making "wasted" API calls over the network.
If I could rely on the user entering their search text then clicking the "Done" (or "Enter") key I could initiate the final API call immediately.
Scenario One
As the text entered by the user is short in length (e.g. 1 character long) My timeout value will be at its maximum value, This gives the user the opportunity to enter additional characters and saves "wasted API calls".
As the user wishes to search for the letter "P" alone, once the Max Timeout expires I will execute the API call and display the results.
This scenario gives the user the worst user experience as they have to wait for my Dynamic Timeout to expire and then wait for a Large API response to be returned and displayed. They will not see any intermediary search results.
Scenario Two
This scenario combines scenario one as I have no idea what the user is going to search for (or the search strings final length) if they type all 6 characters "quickly" I can execute one API call, however the slower they are entering the 6 characters will increase the chance of executing wasted API calls.
This scenario gives the user an improved user experience as they have to wait for my Dynamic Timeout to expire however they do have a chance of seeing intermediary search results. The API responses will be smaller than scenario one.
Scenario Three
This scenario combines scenario one and two as I have no idea what the user is going to search for (or the search strings final length) if they type all 45 characters "quickly" I can execute one API call (maybe!), however the slower they type the 45 characters will increase the chance of executing wasted API calls.
I'am not tied to any technology that delivers my desired solution. I believe Rx is the best approach I've identified so far.
Something like this should work (didn't really try it)
Single<String> firstTypeOnlyStream = RxTextView.textChanges(mEditText)
.skipInitialValue()
.map(CharSequence::toString)
.firstOrError();
Observable<CharSequence> restartTypingStream = RxTextView.textChanges(mEditText)
.filter(charSequence -> charSequence.length() == 0);
Single<String> latestTextStream = RxTextView.textChanges(mEditText)
.map(CharSequence::toString)
.firstOrError();
Observable<TextViewEditorActionEvent> enterStream =
RxTextView.editorActionEvents(mEditText, actionEvent -> actionEvent.actionId() == EditorInfo.IME_ACTION_DONE);
firstTypeOnlyStream
.flatMapObservable(__ ->
latestTextStream
.toObservable()
.doOnNext(text -> nextDelay = delayByLength(text.length()))
.repeatWhen(objectObservable -> objectObservable
.flatMap(o -> Observable.timer(nextDelay, TimeUnit.MILLISECONDS)))
.distinctUntilChanged()
.flatMap(text -> {
if (text.length() > MINIMUM_TEXT_LENGTH) {
return apiRequest(text);
} else {
return Observable.empty();
}
})
)
.takeUntil(restartTypingStream)
.repeat()
.takeUntil(enterStream)
.mergeWith(enterStream.flatMap(__ ->
latestTextStream.flatMapObservable(this::apiRequest)
))
.subscribe(requestResult -> {
//do your thing with each request result
});
The idea is to construct the stream based on sampling rather then the text changed events itself, based on your requirement to sample each X time.
The way I did it here, is to construct one stream (firstTypeOnlyStream for the initial triggering of the events (the first time user input text), this stream will start the entire processing stream with the first typing of the user, next, when this first trigger arrives, we will basically sample the edit text periodically using the latestTextStream. latestTextStream is not really a stream over time, but rather a sampling of the current state of the EditText using the InitialValueObservable property of RxBinding (it simply emits on subscription the current text on the EditText) in other words it's a fancy way to get current text on subscription, and it's equivalent to:
Observable.fromCallable(() -> mEditText.getText().toString());
next, for dynamic timeout/delay, we update the nextDelay based on the text length and using repeatWhen with timer to wait for the desired time. together with distinctUntilChanged, it should give the desired sampling based on text length. further on, we'll fire the request based on the text (if long enough).
Stop by Enter - use takeUntil with enterStream which will be triggered on Enter and it also will trigger the final query.
Restarting - when the user 'restarts' typing - i.e. text is empty, .takeUntil(restartTypingStream) + repeat() will stop the stream when empty string enter, and restarts it (resubscribe).
Well, you could use something like this:
RxSearch.fromSearchView(searchView)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(item -> item.length() > 1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(query -> {
adapter.setNamesList(namesAPI.searchForName(query));
adapter.notifyDataSetChanged();
apiCallsTextView.setText("API CALLS: " + apiCalls++);
});
public class RxSearch {
public static Observable<String> fromSearchView(#NonNull final SearchView searchView) {
final BehaviorSubject<String> subject = BehaviorSubject.create("");
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
subject.onCompleted();
return true;
}
#Override
public boolean onQueryTextChange(String newText) {
if (!newText.isEmpty()) {
subject.onNext(newText);
}
return true;
}
});
return subject;
}
}
blog referencia
your query can be easily solved by using RxJava2 methods, before i post code i will add the steps of what i am doing.
add an PublishSubject that will take your inputs and add a filter to it which will check if the input is greater than two or not.
add debounce method so that all input events that are fired before 300ms are ignored and the final query which is fired after 300ms is taken into consideration.
now add a switchmap and add your network request event into it,
Subscribe you event.
The code is as follows :
subject = PublishSubject.create(); //add this inside your oncreate
getCompositeDisposable().add(subject
.doOnEach(stringNotification -> {
if(stringNotification.getValue().length() < 3) {
getMvpView().hideEditLoading();
getMvpView().onFieldError("minimum 3 characters required");
}
})
.debounce(300,
TimeUnit.MILLISECONDS)
.filter(s -> s.length() >= 3)
.switchMap(s -> getDataManager().getHosts(
getDataManager().getDeviceToken(),
s).subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(hostResponses -> {
getMvpView().hideEditLoading();
if (hostResponses.size() != 0) {
if (this.hostResponses != null)
this.hostResponses.clear();
this.hostResponses = hostResponses;
getMvpView().setHostView(getHosts(hostResponses));
} else {
getMvpView().onFieldError("No host found");
}
}, throwable -> {
getMvpView().hideEditLoading();
if (throwable instanceof HttpException) {
HttpException exception = (HttpException) throwable;
if (exception.code() == 401) {
getMvpView().onError(R.string.code_expired,
BaseUtils.TOKEN_EXPIRY_TAG);
}
}
})
);
this will be your textwatcher:
searchView.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
subject.onNext(charSequence.toString());
}
#Override
public void afterTextChanged(Editable editable) {
}
});
P.S. This is working for me!!
You might find what you need in the as operator. It takes an ObservableConverter which allows you to convert your source Observable into an arbitrary object. That object can be another Observable with arbitrarily complex behavior.
public class MyConverter implements ObservableConverter<Foo, Observable<Bar>> {
Observable<Bar> apply(Observable<Foo> upstream) {
final PublishSubject<Bar> downstream = PublishSubject.create();
// subscribe to upstream
// subscriber publishes to downstream according to your rules
return downstream;
}
}
Then use it like this:
someObservableOfFoo.as(new MyConverter())... // more operators
Edit: I think compose may be more paradigmatic. It's a less powerful version of as specifically for producing an Observable instead of any object. Usage is essentially the same. See this tutorial.

RxJava2 materialize() doesn't emit next item after an error

I've been trying to convert my onErrors into notifications in order to keep the stream emitting items. As far as I understood the materialize() operator does just that. So basically:
materialize() / dematerialize() are available to turn terminal events
into Notification
So I made a test for this based on this question (How to continue streaming items after error in RxJava?). I tried the following:
#Test
public void materializeTest() {
final Observable<String> stringObservable = Observable.fromArray("1", "2", "3")
.flatMap(x -> {
if (x.equals("2")) {
return Observable.error(new NullPointerException());
}
return Observable.just(x);
})
.materialize()
.map(n -> n.getValue());
final TestObserver<String> testObs = stringObservable.test();
Java6Assertions.assertThat(testObs.values().size()).isEqualTo(2);
testObs.assertValueAt(0, "1");
testObs.assertValueAt(1, "3");
}
The result is that no more items are emitted after "2" gives the error. I've also tried to warp on my own Notification object (MyNotification<T>) and do something like:
stringObs
.map(string -> MyNotification.success(string)
.onErrorReturn(error -> MyNotification.error())
But the end result is always the same: after "2" no more items are emitted. I'm 100% doing something wrong but can't really understand what is.
With flatMap, if one of the inner Observables fails, the sequence is terminated an no further items are transformed from the upstream. That happens before materialize() even gets involved.
So instead of trying to materialize the merged flow, materialize the inner sources individually:
Observable.fromArray("1", "2", "3")
.flatMap(x -> {
if (x.equals("2")) {
return Observable.<String>error(new NullPointerException())
.materialize();
}
return Observable.just(x)
.materialize();
})
.filter(n -> n.isOnNext())
.map(n -> n.getValue());

RxJava operator Debounce is not working

I want to implement place autocomplete in Android application, and for this I'm using Retrofit and RxJava. I want to make response every 2 seconds after user type something. I'm trying to use debounce operator for this, but it's not working. It's giving me the result immediately without any pause.
mAutocompleteSearchApi.get(input, "(cities)", API_KEY)
.debounce(2, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(prediction -> Observable.fromIterable(prediction.getPredictions()))
.subscribe(prediction -> {
Log.e(TAG, "rxAutocomplete : " + prediction.getStructuredFormatting().getMainText());
});
As #BenP says in the comment, you appear to be applying debounce to the Place Autocomplete service. This call will return an Observable that emits a single result (or error) before completing, at which point the debounce operator will emit that one and only item.
What you probably want to be doing is debouncing the user input with something like:
// Subject holding the most recent user input
BehaviorSubject<String> userInputSubject = BehaviorSubject.create();
// Handler that is notified when the user changes input
public void onTextChanged(String text) {
userInputSubject.onNext(text);
}
// Subscription to monitor changes to user input, calling API at most every
// two seconds. (Remember to unsubscribe this subscription!)
userInputSubject
.debounce(2, TimeUnit.SECONDS)
.flatMap(input -> mAutocompleteSearchApi.get(input, "(cities)", API_KEY))
.flatMap(prediction -> Observable.fromIterable(prediction.getPredictions()))
.subscribe(prediction -> {
Log.e(TAG, "rxAutocomplete : " + prediction.getStructuredFormatting().getMainText());
});

Android RxJava Cancel the old request

I am new to Rxjava and exploring the possibilities and need help in below described scenario.
Step 1 : Swipe the View
Step 2 : Make an API Call to fetch Data
The above steps are repeated for the number of Views.
Problem :
API_CALL_1 fetching the View_1 Results
User swipes a View_2
API_CALL_2 fetching the View_2 Results
View_1 results are returned and populated in View_2 along with View_2 results
I need to cancel the API_CALL_1 request when the API_CALL_2 request.
Thanks.
Class member:
Subscription mSubscription;
Your API call:
if(subscription != null && !subscription.isUnsubscribed()){
subscription.unsubscribe();
subscription = null;
}
subscription = doApiCall("some_id")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
}
});
So, the main idea of it, that you have to unsubscribe from previous call:
if(subscription != null && !subscription.isUnsubscribed()){
subscription.unsubscribe();
subscription = null;
}
Before starting new call
a better way is to use switchMap to cancel previous request
You are able to provide 'cancel' logic by using 'switchMap' operator. For example, you want to start/stop loading (Code written in Kotlin).
val loadingObservable = Observable.timer(10, TimeUnit.SECONDS)
val load = PublishSubject.create<Boolean>()
val resultObservable = load.switchMap { if(it) loadingObservable else Observable.never() }
Explanation:
'loadingObservable' is a timer that simulate long running operation
'load' is a command from user to start or stop loading. If you want
to start loading - send True else False
'switchMap' operator is
designed to dispose previous observable. So it you send True it will
start loading. If you send False it will dispose (cancel) previous
observable.

Pause and resume an observable based on a boolean gate in RxJava 2.X?

Let's say I have a Processor that emits a boolean value when ever a button is pressed, think of this as a toggle.
boolean gateValue = true;
PublishProcessor<Boolean> gate = PublishProcessor.create();
view.onButtonClicked()
.subscribe(new Action1<Void>() {
#Override
public void call(final Void aVoid) {
gate.onNext(gateValue = !gateValue);
}
}));
What I would like to do is use the value of the gate to pause and resume an observable sequence, buffering the emitted values whilst paused.
I've read into this a lot and while it seems possible in reactive extensions for other languages, RxJava doesn't seem to support it.
Here's an example of what I'd like to achieve, it simply outputs an incremental value every second. When I press the button I want the output to stop until I press it again which should output every item emitted between the two button presses:
Flowable.interval(1, TimeUnit.SECONDS)
.bufferWhile(gate)
.flatMapIterable(longs -> longs)
.subscribe(new Consumer<Long>() {
#Override
public void accept(final Long aLong) throws Exception {
view.displayTime(aLong);
}
});
Does anyone know of a way to achieve something like this?
Edit I've written a blog post on how to achieve this https://medium.com/#scottalancooper/pausing-and-resuming-a-stream-in-rxjava-988a0977b771#.gj7fsi1xk
There is now an operator valve() in the RxJava2Extensions library that does the requested behavior.
You can achieve this using default RxJava operators:
final Random random = new Random();
final Observable<Boolean> gates = Observable.interval(10, TimeUnit.SECONDS)
.map(it -> random.nextBoolean())
.startWith(false)
.doOnNext(it -> System.out.println("Gate " + (it ? "opened" : "closed")))
.take(100);
final Observable<Long> data = Observable.interval(3, TimeUnit.SECONDS)
.doOnNext(it -> System.out.println("New value " + it))
.take(100);
gates.publish(innerGates -> data.publish(innerData -> Observable.merge(
innerData.window(
innerGates.distinctUntilChanged().filter(it -> it),
(ignore -> innerGates.distinctUntilChanged().filter(it -> !it))
).flatMap(it -> it),
innerData.buffer(
innerGates.distinctUntilChanged().filter(it -> !it),
(ignore -> innerGates.distinctUntilChanged().filter(it -> it))
)
.flatMap(Observable::from)
)))
.subscribe(it -> System.out.println("On next " + it));
Just use the ready-made Observable.window operator that takes one Observable<T> parameter.

Categories

Resources