I am new to Rx world and try to implement my AutoCompleteTextView with RxJava, RxBinding and Retrofit 2.
Here's what I come up with which is troublesome: (Maybe I'm not doing it in the right way.)
I have an AutoCompleteTextView and here I created my subscribtion and observables:
subcription = RxTextView.textChangeEvents(clearableEditText)
.skip(1)
.debounce(400, TimeUnit.MILLISECONDS)
.map(new Func1<TextViewTextChangeEvent, String>() {
#Override
public String call(TextViewTextChangeEvent textViewTextChangeEvent) {
return textViewTextChangeEvent.text().toString();
}
})
.filter(new Func1<String, Boolean>() {
#Override
public Boolean call(String s) {
return s.length() > 2;
}
})
.flatMap(new Func1<String, Observable<List<String>>>() {
#Override
public Observable<List<String>> call(String text) {
return searchService.getAutoCompleteTermsObservable(text)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<String>>() {
#Override
public void onCompleted() {
Log.d("rx", "oncomplete");
}
#Override
public void onError(Throwable e) {
Log.e("rx", e.toString());
}
#Override
public void onNext(List<String> strings) {
Log.d("rx", strings.size()+"");
autoAdapter = new ArrayAdapter<>(MainActivity.this,
android.R.layout.simple_dropdown_item_1line, strings);
clearableEditText.setAdapter(autoAdapter);
clearableEditText.showDropDown();
}
});
My issue is when I set my EditText with setText() method, it triggers dropdown. For example it does that when I set the word from AutoCompleteTextView's dropdown and when I set it with voice input. Is there a way to avoid triggering onTextChanged when I set it manually? Or how can I fix that?
You could indeed use unsubscribe() but depending on how you set the value, you also use skipWhile. Here is an example:
public void handleTextChanges() {
final String textFromSource = "an";
Observable.fromArray("a", "an", "ancestor")
.skipWhile(new Predicate<String>() {
#Override
public boolean test(String value) throws Exception {
return textFromSource.contains(value);
}
})
.subscribe(new Consumer<String>() {
#Override
public void accept(String value) throws Exception {
Log.d("Rx", value);
}
});
}
This will only consume ancestor (example is RxJava2, but the same methods exist). Any subsequent values, even if they match an, will be consumed. You could use filter if you always want to do the check like this
Related
I want to send multiple requests over the network and this tutorial
helped but i'm stuck at the latter part .
seems i'm expected to return a value(OrderValues) from onSubscribe,onNext,....
since apply function returns a value. But ....,onNext returns void by default.
Any help?Here is my piece of code
Observable<Restaurant> orderRestaurant= IdentityClient.getAPIService()
.getRestaurantById(restaurantId)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<Menu> orderMenu= IdentityClient.getAPIService()
.getMenuById(menuId)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<User> orderUser= IdentityClient.getAPIService()
.getUserById(userId)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<OrderValues> combineValues=Observable.zip(orderRestaurant,
orderMenu, orderUser,
new Function3<Restaurant, Menu, User, OrderValues>() {
#Override
public OrderValues apply(Restaurant restaurant, Menu menu, User user)
throws Exception {
return new OrderValues(restaurant,menu,user);
}
I get an error here "cannot resolve method 'subscribe anonymous
org.reactivestreams.Subscriber(....OrderValues)
}).subscribe(new Subscriber<OrderValues>() {
#Override
public void onSubscribe(Subscription s) {
}
#Override
public void onNext(OrderValues orderValues) {
}
#Override
public void onError(Throwable t) {
}
#Override
public void onComplete() {
}
});
I'm assuming that you are using RxJava 2.
Use Observer instead of Subscriber. And also do not assign the result to a new Observable (you called it combineValues).
private void myMethod() {
Observable.zip(orderRestaurant, orderMenu, orderUser, new Function3<Restaurant, Menu, User, OrderValues>() {
#Override
public OrderValues apply(#NonNull Restaurant restaurant, #NonNull Menu menu, #NonNull User user) throws Exception {
return new OrderValues(restaurant, menu, user);
}
}).subscribe(new Observer<OrderValues>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(OrderValues orderValues) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
}
}
Am doing a weather API call every 3secs and append the result to a textView using showWeather("weather") but I am sure there is a better way to do it. Am not sure why I need create Class Func1 but did because map required it. Also is there a way to shorten observer? I don't use lamda unfortunately. Any suggestions?
Observer myObserver = new Observer<String>() {
#Override
public void onError(Throwable e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String value) {
showWeather(value);
}
};
class Func1<T, T1> implements io.reactivex.functions.Function<Long, String > {
#Override
public String apply(Long aLong) throws Exception {
return getJSON("http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b1b15e88fa797225412429c1c50c122a1",300);
}
}
Observable.interval(3, TimeUnit.SECONDS)
.map(new Func1<Long, Observable<String>>() {
}).observeOn(AndroidSchedulers.mainThread()).subscribe(myObserver);
I tried :
Observable
.interval(3, SECONDS)
.subscribeOn(Schedulers.io())
.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return getJSON("http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b1b15e88fa797225412429c1c50c122a1", 300);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(myObserver)
But I get :
03-07 21:47:25.982 21181-21181/com.alex.rxandroidexamples E/imerExampleFragment$1$1: null
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1273)
Also how do I unsubscribe onPause or onPause?
I found out the best way to do this is :
private final CompositeDisposable disposables = new CompositeDisposable();
Observable fetchWeatherInterval = Observable.interval(3, TimeUnit.SECONDS)
.map(new Function<Long, String>() {
#Override
public String apply(Long aLong) throws Exception {
return getWeather("http://samples.openweathermap.org/data/2.5/weather?", "London,uk", "b1b15e88fa797225412429c1c50c122a1");
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Observer displayWeatherInterval = new Observer<String>() {
#Override
public void onError(Throwable e) {
Log.e("Throwable ERROR", e.getMessage());
}
#Override
public void onComplete() {
}
#Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
#Override
public void onNext(String value) {
textViewWeatherInterval.append(value);
}
};
buttonFetchIntervalWeather.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fetchWeatherInterval.subscribe(displayWeatherInterval);
}
});
I am using RxSearchView to query the text changes in a form of "Search as you type"
RxSearchView.queryTextChanges(searchView)
but I would like to also catch when the user submits the search, so then I have to use
RxSearchView.queryTextChangeEvents(searchView) or searchView.setOnQueryTextListener
When I use any of these last 2, it looks like they are cancelling the first RxSearchView.queryTextChanges, looks like that there can only be 1 observable attached to SearchView.
How can I observe both events at the same time?
Here is the full code
private void setupSearch() {
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
}
return !empty;
}
}).subscribe(new Subscriber<SearchViewQueryTextEvent>() {
#Override
public void onNext(SearchViewQueryTextEvent searchViewQueryTextEvent) {
String searchTerm = searchViewQueryTextEvent.queryText().toString();
if (searchViewQueryTextEvent.isSubmitted()) {
submitFullSearch(searchTerm);
} else {
submitRecommendationsSearch(searchTerm);
}
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
});
}
There is only one observable since it overwrites the view's listener, but you can use RxSearchView.queryTextChangeEvents(searchView) to monitor both types of events. It gives a stream of SearchViewQueryTextEvent events. For each event, you can check isSubmitted() to determine if it is a submission or a change event and fetch the current text with queryText().
Here is how could use ConnectableObservable to get the events into two streams to filter separately --
private void setupSearch() {
ConnectableObservable<SearchViewQueryTextEvent> searchObs = RxSearchView.queryTextChangeEvents(searchView).publish();
searchObs.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
}
return !empty;
}
}).subscribe(new Subscriber<SearchViewQueryTextEvent>() {
#Override
public void onNext(SearchViewQueryTextEvent searchViewQueryTextEvent) {
String searchTerm = searchViewQueryTextEvent.queryText().toString();
if (!searchViewQueryTextEvent.isSubmitted()) {
submitRecommendationsSearch(searchTerm);
}
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
});
searchObs.subscribe(new Subscriber<SearchViewQueryTextEvent>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchViewQueryTextEvent searchViewQueryTextEvent) {
if (searchViewQueryTextEvent.isSubmitted()) {
submitFullSearch(searchTerm);
}
}
});
Subscription searchSub = searchObs.connect();
I've an Observable something like this:
#GET("endpoint")
Observable<Something> getSomething();
and Subscriber like this
Subscriber<Something> somethingSubscriber = new Subscriber<Something>() {
public void onCompleted() {
}
public void onError(Throwable e) {
//handle exceptions
}
public void onNext() {
//do something
}
In my OnClickListener associated with a button, i make a subscription
getSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(somethingSubscriber);
If i don't have an internet connection, onError is called and i do some exception handling. when I press the button again (assume i want to retry), the callback methods do not get called.
I want that onNext / onError callbacks get called everytime I press the button.
There is extention for RxJava. It has a lot of "cool tools", but for handling retrofit errors you can use ResponseOrError class.
So in you case it would looks like:
final PublishSubject<Object> clickSubject = PublishSubject.create();
final Observable<ResponseOrError<Something>> responseOrErrorObservable = clickSubject
.flatMap(new Func1<Object, Observable<ResponseOrError<Something>>>() {
#Override
public Observable<ResponseOrError<Something>> call(Object o) {
return getSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(ResponseOrError.<Something>toResponseOrErrorObservable());
}
})
.replay(1)
.refCount();
final Observable<Throwable> error = responseOrErrorObservable
.compose(ResponseOrError.<Something>onlyError())
.subscribe(new Action1<Segment>() {
#Override
public void call(Throwable throwable) {
// what to do on error, some toast or what ever yu need
}
});
final Observable<UserInfoResponse> success = responseOrErrorObservable
.compose(ResponseOrError.<Something>onlySuccess())
.subscribe(new Action1<Something>() {
#Override
public void call(Something some) {
// code what to do on success
}
});
And now, into onClick you just need to put clickSubject.onNext(null)
.replay(1).refCount(); needed because there are 2 Observables that uses responseOrErrorObservable, so without it retrofit request will "happens" two times.
You are reusing the same Subscriber. Once you get the onError or a result (so it completes) the subscriber is unsubscribed. Try to pass every time a new subscriber.
use this code
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Something>() {
#Override
public void call(Something something) {
//do something
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//handle exceptions
}
},
new Action0() {
#Override
public void call() {
}
});
}
});
Addition
or
replace this
Subscriber<Something> somethingSubscriber = new Subscriber<Something>() {
public void onCompleted() {
}
public void onError(Throwable e) {
//handle exceptions
}
public void onNext() {
//do something
}
};
to
Subscriber<String> somethingSubscriber = new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
}
};
In my Case onNext() and onError() methods are not getting called because of my model class wrong parsing, I was taking a double object as Integer so NumberFormatException was thrown and nothing was happening after getting the result from retrofit.
The user input a url from the ui,and then my presenter validate it,if it is valid,insert it into the db,else show error;
my solution looks like:
#Override
public void addFeed(final String uri) {
Observable.create(new Observable.OnSubscribe<Boolean>() {
#Override
public void call(Subscriber<? super Boolean> subscriber) {
//may contain some database query,if it has,how should i do ?
subscriber.onNext(uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME));
subscriber.onCompleted();
}
}).doOnNext(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
//insert into db
if (aBoolean)
mFeedModel.add(uri);
}
}).subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
if (aBoolean){
mFeedInputView.hideInputError();
}else{
mFeedInputView.showInputError(R.string.url_format_error);
}
}
});
}
**updated **
if (uri.startsWith(FeedFetcher.FEED_SCHEME) || uri.startsWith(FeedFetcher.HTTP_SCHEME)) {
mFeedInputView.hideInputError();
mChannelModel.isChannelExists(uri)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap(new Func1<Boolean, Observable<RssChannel>>() {
#Override
public Observable<RssChannel> call(Boolean aBoolean) {
if (aBoolean)
return mChannelModel.add(uri);
return Observable.empty();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<RssChannel>() {
#Override
public void call(RssChannel channel) {
mFeedInputView.addUrlSuccess();
}
});
} else {
mFeedInputView.showInputError(R.string.url_format_error);
}
but i think putting if(aBoolean) in the flapmap is not a pretty solution..
How about:
public void addFeed(String uri) {
Observable.just(uri)
.first(new Func1<String, Boolean>() {
#Override
public Boolean call(String uri) {
return uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME);
}
})
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
mFeedInputView.hideInputError();
}
#Override
public void onError(Throwable e) {
mFeedInputView.showInputError(R.string.url_format_error);
}
#Override
public void onNext(String s) {
mFeedModel.add(uri);
}
});
}
The first() operator will filter out invalid uris it receives based on your filter rule and throw a NoSuchElementException if no valid uri is found (in your case, if the one passed as parameter is invalid).
onCompleted() will be called only if the uri is valid so you can be safe to call hideInputError() there. onError() will be called instead of onCompleted() only if an error occurs so it is safe to do the error handling there. Finally, onNext() will be called when the uri is valid since the first() operator is ignoring invalid uris so it is safe to save it there.
Note that I removed the final attribute from your method declaration. There is no need for it here since you are passing the uri itself down the chain with just().
Also worth noting that you don't have to use RxJava for the sake of it. In your case, if you don't need to run this asynchronously, this is probably a much cleaner solution:
public void addFeed(String uri) {
if (uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME)) {
mFeedInputView.hideInputError();
mFeedModel.add(uri);
} else {
mFeedInputView.showInputError(R.string.url_format_error);
}
}
I much rather has this code in more functional way.
Observable.create(new Observable.OnSubscribe<Boolean>())
.map(b -> b = false)
.doOnNext(uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME))
.map(b -> {
mFeedModel.add(uri);
b=true;
return b;
})
.subscribe(b-> {
if(!b){
mFeedInputView.showInputError(R.string.url_format_error);
}
});