RxAndroid button click Observer? - android

Hi fellow programmers,
I am using RxAndroid to make an interval API call every 3 seconds when a button is pressed.
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);
}
});
My question is if there is a way to make the button (or it's onClick listener) an Observable too and chain it with the others.
Something like buttonFetchIntervalWeather.subscribe(fetchWeatherInterval);

Use RxBinding
Subscription s = RxView.clicks(button)
.throttleFirst(5, TimeUnit.SECONDS) // maybe you want to ignore multiple clicks
.flatMap(foo -> fetchWeatherInterval)
.subscribe(displayWeatherInterval);
throttleFirst just stops further events for next 5 seconds so if user clicks the button multiple times, the same fetchWeatherInterval won't be triggered again, for the next 5 seconds, of course.
flatMap converts output of one observable into another observable, in this case from click event to fetchWeatherInterval. Read the docs if you need more info.
Also, RxJava2 works as well, I just answered this for RxJava1. Just change Subscription to Disposable.
Using Observable.create():
Observable.create(new Action1<Emitter<View>>() {
#Override
public void call(Emitter<View> emitter) {
emitter.setCancellation(new Cancellable() {
#Override
public void cancel() throws Exception {
button.setOnClickListener(null);
emitter.onCompleted();
}
});
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
emitter.onNext(v);
}
});
}
}, Emitter.BackpressureMode.DROP);
Or with lambda:
Observable.create(emitter -> {
emitter.setCancellation(() -> {
button.setOnClickListener(null);
emitter.onCompleted();
});
button.setOnClickListener(emitter::onNext);
}, Emitter.BackpressureMode.DROP);

Related

How to delay loading items by using RxJava?

While inflating Android view I load a bunch of stuff from the background thread and inflate some views based on network responses. So I am trying to defer some of that tasks using RxJava like this
Single.fromCallable(() -> savedInstanceState)
.delay(50,TimeUnit.MICROSECONDS,AndroidSchedulers.mainThread())
.flatMapCompletable(this::loadVideos)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
Timber.d("on Subscribe");
}
#Override
public void onComplete() {
Timber.d("on onComplete");
}
#Override
public void onError(Throwable e) {
Timber.d("on onError");
}
});
And the loadVideos method is like this:
private Completable loadVideos(Bundle savedInstanceState) {
return Completable.fromAction(() -> {
videoPresenter.loadVideos(savedInstance);
});
}
What I am finding is onSubscribe() certainly gets called, but method videoPresenter.loadVideos never gets called. Would appreciate if anyone can point out what I am doing wrong.
For my testing, I implemented following test that seems to work...
public class DelayTest {
public static void main(String[] args) throws InterruptedException {
Single.fromCallable(() -> "hello")
.delay(50, TimeUnit.MICROSECONDS)
.flatMapCompletable(new Function<String, CompletableSource>() {
#Override
public CompletableSource apply(String s) throws Exception {
return getFlatMapCompletable();
}
})
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
System.out.println("In onSubscribe");
}
#Override
public void onComplete() {
System.out.println("In onComplete");
}
#Override
public void onError(Throwable e) {
System.out.println("In onError");
}
});
Thread.sleep(200L);
}
private static Completable getFlatMapCompletable() {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
System.out.println("In flatmapCompletable");
}
});
}
}
Delay operator in RxJava is executed in another thread. So the rest of the execution does not wait for this one to be finished.
Take a look to some examples https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/utils/ObservableDelay.java

Stop Observable from loop

Recently I have been working on RxJava 2 and I have tested the Observable.interval()
subscription = Observable.interval(1, TimeUnit.MILLISECONDS, Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
subscription.subscribe(new Observer<Long>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Long aLong) {
//binding.appBar.mainContent.msg.setText(aLong+"");
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Observable is started after activity's onCreate method. And I am logging the output through onNext() method. And I have a Stop Button. When it is triggered I want to stop subscription flow.
Even after the stop button is clicked the log keeps on going.
stop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (subscription != null) {
subscription.unsubscribeOn(Schedulers.io());
}
}
});
You have subscribed with an Observer, which means you have to keep a reference to the actual Disposable from onSubscribe(Disposable) callback, and later perform Disposable#dispose() on that object.
private Disposable disposable;
...
Observable.interval(1, TimeUnit.MILLISECONDS, Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Observer() {
#Override public void onSubscribe(Disposable d) {
disposable = d;
}
// other callbacks here
});
disposable.dispose();
Instead you can change your subscription to following:
Disposable disposable = Observable.interval(1, TimeUnit.MILLISECONDS, Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
#Override public void accept(Long aLong) throws Exception {
// onNext
}
}, new Consumer() {
#Override public void accept(Throwable throwable) throws Exception {
// onError
}
}, new Action() {
#Override public void run() throws Exception {
// onComplete
}
});
disposable.dispose();

RxSearchView observing textChanges and Events at the same time

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

onNext() onError() callbacks not executing on second subscribtion android

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.

Continue next Observable onError

I have sequence of tasks to be completed, if any of them throws exception would like to continue with next task.
But with this implementation, if first REST calls fail it throws onError in subscriber.
Wondering what is best operator to use or I need to call some other function to make it resume on exception.
private void logout() {
// Observable from Retrofit to make logout service call
requestLogout()
.doOnNext(o -> {
clearNotifications();
})
.doOnNext(o -> {
unregisterGcm();
})
.doOnNext(o -> {
clearLocalData();
})
.doOnNext(o -> {
// clear all jobs
mJobManager.clear();
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Object>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
mView.navigateToLogin();
}
#Override
public void onNext(Object o) {
mView.navigateToLogin();
}
});
}
If you just want to re-subscribe use Observable.retry():
.observeOn(AndroidSchedulers.mainThread())
.retry().subscribe(new Subscriber<Object>() {
// rest of code
So I found way to execute all the Observables even if one of them have error. But this does not preserve order.
I am still looking for way where order is preserved and on error it should continue to next observable.
Observable.mergeDelayError(requestLogout(),
clearNotifications(),
unregisterGcm(),
clearLocalData(),
clearJobs())
.first()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Object>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
mView.navigateToLogin();
}
#Override
public void onNext(Object o) {
mView.navigateToLogin();
}
}
);

Categories

Resources