I really like the RxJava, it's a wonderful tool but somethimes it's very hard to understand how it works.
We use Retrofit with a RxJava in our Android project and there is a following use-case:
I need to poll the server, with some delay between retries, while server is doing some job. When server is done I have to deliver the result. So I've successfully done it with RxJava, here is the code snippet:
I used "skipWhile" with "repeatWhen"
Subscription checkJobSubscription = mDataManager.checkJob(prepareTweakJob)
.skipWhile(new Func1<CheckJobResponse, Boolean>() {
#Override
public Boolean call(CheckJobResponse checkJobResponse) {
boolean shouldSkip = false;
if (SHOW_LOGS) Logger.v(TAG, "checkJob, skipWhile, jobStatus " + checkJobResponse.getJobStatus());
switch (checkJobResponse.getJobStatus()){
case CheckJobResponse.PROCESSING:
shouldSkip = true;
break;
case CheckJobResponse.DONE:
case CheckJobResponse.ERROR:
shouldSkip = false;
break;
}
if (SHOW_LOGS) Logger.v(TAG, "checkJob, skipWhile, shouldSkip " + shouldSkip);
return shouldSkip;
}
})
.repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
#Override
public Observable<?> call(Observable<? extends Void> observable) {
if (SHOW_LOGS) Logger.v(TAG, "checkJob, repeatWhen " + observable);
return observable.delay(1, TimeUnit.SECONDS);
}
}).subscribe(new Subscriber<CheckJobResponse>(){
#Override
public void onNext(CheckJobResponse response) {
if (SHOW_LOGS) Logger.v(TAG, "checkJob, onSuccess, response " + response);
}
#Override
public void onError(BaseError error) {
if (SHOW_LOGS) Logger.v(TAG, "checkJob, onError, canEditTimeline, error " + error);
Toast.makeText(ChoseEditOptionActivity.this, R.string.NETWORK__no_internet_message, Toast.LENGTH_LONG).show();
}
#Override
public void onCompleted() {
if (SHOW_LOGS) Logger.v(TAG, "onCompleted");
}
});
The code works fine:
When server responded that job is processing I return "true" from "skipWhile" chain, the original Observable waits for 1 second and do the http request again.
This process repeats until I return "false" from "skipWhile" chain.
Here is a few things I don't understand:
I saw in the documentation of "skipWhile" that it will not emit anything (onError, onNext, onComplete) from original Observable until I return "false" from its "call" method. So If it doesn't emit anything why does the "repeatWhen" Observable doing it's job? It waits for one second and run the request again. Who launches it?
The second question is: Why Observable from "repeatWhen" is not running forever, I mean why it stops repeating when I return "false" from "skipWhile"? I get onNext successfully in my Subscriber if I return "false".
In documentation of "repeatWhile" it says that eventually I get a call to "onComplete" in my Subscriber but "onComplete" is never called.
It makes no difference if I change the order of chaining "skipWhile" and "repeatWhen". Why is that ?
I understand that RxJava is opensource and I could just read the code, but as I said - it's really hard to understand.
Thanks.
I've not worked with repeatWhen before, but this question made me curious, so I did some research.
skipWhile does emit onError and onCompleted, even if it never returns true before then. As such, repeatWhen is being called every time checkJob() emits onCompleted. That answers question #1.
The rest of the questions are predicated on false assumptions. Your subscription is running forever because your repeatWhen never terminates. That's because repeatWhen is a more complex beast than you realize. The Observable in it emits null whenever it gets onCompleted from the source. If you take that and return onCompleted then it ends, otherwise if you emit anything it retries. Since delay just takes an emission and delays it, it's always emitting the null again. As such, it constantly resubscribes.
The answer to #2, then, is that it is running forever; you're probably doing something else outside this code to cancel the subscription. For #3, you never get onCompleted because it never completes. For #4, the order doesn't matter because you're repeating indefinitely.
The question now is, how do you get the correct behavior? It's as simple as using takeUntil instead of skipWhile. That way, you keep repeating until you get the result you want, thus terminating the stream when you want it to end.
Here's a code sample:
Observable<Boolean> source = ...; // Something that eventually emits true
source
.repeatWhen(completed -> completed.delay(1, TimeUnit.SECONDS))
.takeUntil(result -> result)
.filter(result -> result)
.subscribe(
res -> System.out.println("onNext(" + res + ")"),
err -> System.out.println("onError()"),
() -> System.out.println("onCompleted()")
);
In this example, source is emitting booleans. I repeat every 1 second until the source emits true. I keep taking until result is true. And I filter out all notifications that are false, so the subscriber doesn't get them until it's true.
Related
I have this simple retrofit2 api interface which contains
interface Api {
#GET(BuildConfig.END_POINT) Observable<Response> fetchData();
}
So everything is fine when I'm doing a fresh request
but let say I fire a request and I un-subscribe immediately and then I try to fire new request it returns nothing.
So, in code it looks something like this:
in Activity::onPause I perform un-subscription and in Activity::onResume I fire the request again.
My request looks something like this::
api.fetchData()
.timeout(30,TimeUnit.SECONDS)
.doOnNext(new Action1<Response>() {
#Override public void call(Response response) {
list = response.getDataForList();
}
}).flatMap(new Func1<Response, Observable<List<Object>>>() {
#Override public Observable<Object>> call(Response response) {
return Observable.just(list);
}
});
When I tried debugging it, the call is made but doOnNext() is not called. None of the lifecycle methods are called.
And just for clarification from here I'm just returning the observable which I'm using it somewhere else where I'm observing on main thread and subscribing on IO.
Don't use doOnNext, just map. And try to get use to use lambdas, make your code much readable.
api.fetchData()
.timeout(30,TimeUnit.SECONDS)
.map(response -> response.getDataForList())
.flatMap(list -> Observable.just(list));
Now as concept, every time that some observer subscribe to this observable consume the items, then observable automatically unsubscribe the observer. So you don't have to worry about unsubscribe anything.
You can see some practical examples here. https://github.com/politrons/reactive
I have an Observable and subscribe to it. I need to not miss any emitted result, so I use onBackpressureBuffer like following:
Observable<Data> observable = observable.onBackpressureBuffer();
if (BuildConfig.DEBUG)
{
observable
.subscribeOn(HandlerScheduler.from(dataManager.getBackgroundHandler()))
.observeOn(HandlerScheduler.from(dataManager.getBackgroundHandler()))
.subscribe(new MeasuringSubscriber(...));
}
// Here is the real observer that I need in my app
observable
.subscribeOn(HandlerScheduler.from(dataManager.getBackgroundHandler()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Data>()
{
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Data data) {
}
});
The MeasuringSubscriber is a custom subscriber that just logs how long a task needs, that's all.
Problem
If I add the MeasuringSubscriber, the subscribers do not work anymore and never emit a result. Why? And how can I make that working?
EDIT - NEW PROBLEM
Currently it's working, but the MeasuringSubscriber is somehow blocking, meaning, first all items are emitted one by one to the MeasuringSubscriber and only afterwards all items are emitted one by one to the main subscriber... Any ideas what could cause that?
I have a solution for that - I can extend my main observalbe from the MeasuringObservable - but I rather would like to know why this happens and how to avoid this...
I tried using publish + connect, but still it does emit all items to the first subscriber before emitting them to the second one...
Rx way of doing things can be very complex for none and for many reasons...
but I feel there ARE simple ways to do simple things with RX...
How would I simply perform this statement on a background thread and receive the response on the ui thread?
All functions of this object need to run on a background thread. Get, put, clear, and delete.
String city = Paper.get("city");
The base object in Rx is Observable. That object usually wraps an OnSubscribe object, which is simply an extension of Action1 that takes a Subscriber as a parameter.
What all that means is that you just need to define a class that wraps your call and passes the result to the Subscriber:
public class RxPaperGet implements Observable.OnSubscribe<String> {
#Override
public void call(Subscriber<? super String> t1) {
try {
t1.onNext(Paper.get("city"));
} catch (Throwable t) {
t1.onError(t);
return;
}
t1.onCompleted();
}
}
That's a basic example. Now, you would want to wrap that so you can call any function, and not just Paper.get("city"). Something like https://github.com/ReactiveX/RxJavaAsyncUtil/blob/0.x/src/main/java/rx/util/async/operators/OperatorFromFunctionals.java#L44 does that, by allowing you to pass an arbitrary Callable.
Which in your case, would implement as:
Observable<String> res = OperatorFromFunctionals.fromCallable(() -> Paper.get("city"));
(In case you're wondering, this is java8 lambdas brought to android by retrolambda. quite nice to remove the verbosity of Rx)
Once you have your observable, you can subscribe on it, and get results. To execute on the background, and retrieve the results on the ui thread, you would do:
res.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
AndroidSchedulers is provided by rx-android.
Then you can simply be calledback with the result:
.subscribe(city -> Log.d(TAG, city));
That returns a subscription, which is useful if you need to cancel it.
Overall:
OperatorFromFunctionals.fromCallable(() -> Paper.get("city"))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(city -> Log.d(TAG, city));
EDIT: This is not correct. Will not delete the answer though to preserve the comments.
Very simple example:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getPaper()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
Log.d("xxx", s);
}
});
}
private Observable<String> getPaper() {
return Observable.just(Paper.get());
}
where Paper.get() is a long running operation that returns a String. Check the docs for Scheduler.
Don't forget to observe on the main thread if you want to change the UI after receiving the result of your operation, else you will get an exception for changing the UI from outside the UI thread.
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.
I am using RxJava to move network access to a separate thread in Android, but my UI still blocks.
I am not using the wrong observable as shown here: Android RxJava, Non Blocking?
The codepoints [A], [B] and [C] in below code are passed in the order [A] -> [C] -> [B] so the current thread is processed fine and RxJava calls [C] once it had a result. This is fine.
Also, blocking is much better compared to doing the network call on the UI thread, but I still have minor blocking. The UI stays fluent after the call is made, but if the server does not respond in a matter of milliseconds, it blocks.
private search; // search is an instance variable in the same class
// [A]
Observable.just(search.find("something")) // search.find calls the REST endpoint
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Search>() {
#Override public void call(Search search) {
// further processing // [B]
}
}, new Action1<Throwable>() {
#Override public void call(Throwable throwable) {
// error handler
}
});
// [C]
Could it be a problem that search is an instance variable in the same class where the Observable uses it, but the endpoint call is performed from a separate library? It shouldn't matter, right?
Am I doing anything bad that I shouldn't be doing?
--
Find looks like this (removed exception handling for brevity):
public Search find(String searchtext) {
setSearchtext(searchtext);
SearchEndpoint.find(Session.getUser().getId(), searchtext);
return this;
}
SearchEndpoint like this:
public static Search find(final Long userId, final String searchtext) throws IOException {
return ApiService.api().searches().find(userId).setFind(searchtext).execute();
}
and makes a call to the generated Google cloud endpoint library.
Try this:
Observable.create(new Observable.OnSubscribe<Search>() {
#Override
// method signature is from memory - I hope I am correct...
public void call(Subscriber<? super Search> subscriber) {
try {
Search search = search.find("something");
subscriber.onNext(search);
subscriber.onCompleted();
} catch (SomeException e) {
subscriber.onError(e);
}
}
})
// and then continue with your .subscribeOn(...)
To clarify, maybe this makes the problem with your code more obvious:
Observable.just(search.find("something"))
is clearly equivalent to
Search search = search.find("something");
Observable.just(search)
And this makes it obvious that search.find is executed before we ever hand the control over to rxjava and it is executed on whatever thread you are currently on - then the construction of an Observable from the pre-computed value and the delivery of the value happen on another thread but that does not help you much...
I know this is a few months old-- but instead of createing an entirely new Observable (which is relatively error-prone), you can use the map operator to run the search:
String search_input = "something"; // this is where you can specify other search terms
Observable.just(search_input)
.map(s -> search.find(s)) // search.find calls the REST endpoint
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( // your subscriber goes here
If not using lambdas, that map function should look like:
.map(new Func1<String, Search>() {
#Override
public Search call(String s) {
return search.find(s)
}
})