I am using RxJava and RxBindings for view in android. following is an example of what I am doing.
RxView.clicks(btMyButton).flatMap(btn -> {
// another observable which can throw onError.
return Observable.error(null);
}).subscribe(object -> {
Log.d("CLICK", "button clicked");
}, error -> {
Log.d("CLICK", "ERROR");
});
when I click on MyButton, I use flatMap to return another observable which is a network call and can return success or error. when it returns an error i handle it in error block. but I am not able to click the button again.
How can I handle the error and still be able to click on the button again?
GreyBeardedGeek is spot on. To be quite explicit about one of your options you can use .materialize():
RxView.clicks(btMyButton).flatMap(btn -> {
if (ok)
return someObservable.materialize();
else
return Observable.error(new MyException()).materialize();
}).subscribe(notification -> {
if (notification.hasValue())
Log.d("CLICK", "button clicked");
else if (notification.isOnError())
Log.d("CLICK", "ERROR");
});
By the way don't pass null to Observable.error().
I'm pretty new to RxJava, but just ran across this issue myself.
The problem is that by definition, an Observable will stop emitting values when it's error() method is called.
As far as I can tell, you have two options:
modify the Observable that makes the network call so that when an error occurs, an exception is not thrown, but rather you return a value that indicates that an error occurred. That way, the Observable's error() method will not be called when a network error occurs.
Look into using Observable.onErrorResumeNext to override the termination of the Observable when error() is called. See
Best practice for handling onError and continuing processing
I use approach similar to already described:
RxView.clicks(btMyButton)
.flatMap(btn -> {
// another observable which can throw onError.
return Observable.error(null)
.doOnError(error -> {
//handle error
})
.onErrorResumeNext(Observable.empty());//prevent observable from terminating
})
.subscribe(object -> {
Log.d("CLICK", "button clicked");//here no errors should occur
});
I have a short article which describes this approach.
Related
I have a Flux stream. For each element processed I want to have an action triggered which is an asynchronous/non-blocking one. For example, a method returning back a Mono from a db update.
I want this action to be done on the doOnNext block.
I don't want to affect the Flux, the processing and the back pressure implemented there.
Supposing Mono method to be called is
Mono<Integer> dbUpdate();
should my Flux be like this?
public Flux<Data> processData(PollRequest request)
{
return searchService.search(request)
.doOnNext(data -> dbUpdate(data));
}
Or should be as mentioned on a stack overflow example.
public Flux<Data> processData(PollRequest request)
{
return searchService.search(request)
.doOnNext(data -> dbUpdate(data).subscribe());
}
Won't the above cause blocking issues inside doOnNext?
Also which is the most appropriate scheduler to use for this type of action?
dbUpdate() will be ignored if you do not subscribe to it. The following snippet doesn't print anything because Mono.just("db update") doesn't get subscribed.
Mono<String> dbUpdate() {
return Mono.just("db update")
.doOnNext(System.out::println);
}
public Flux<String> processData() {
return Flux.just("item 1", "item 2")
.doOnNext(data -> dbUpdate());
}
Note that .subscribe() doesn't block your thread, it kicks off the work and returns immediately.
I'm adapting some sample code from what3words for accessing their API via their Java SDK. It uses RXJava.
The sample code is:
Observable.fromCallable(() -> wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
});
First of all. this gives a deprecation warning when building and a IDE warning (Result of 'Observable.subscribe()' is ignored).
To resolve this first issue I have added Disposable myDisposable = in front of the Observable. Is this correct? (See below for where it is added)
Next I need to add a timeout so that I can show a warning etc if the request times out. To do this I have added .timeout(5000, TimeUnit.MILLISECONDS) to the builder.
This works, but the way timeouts seem to work on Observables is that they throw an exception and I cannot figure out how to catch and handle that exception.
What I have right now is:
Disposable myDisposable = Observable.fromCallable(() -> wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(5000, TimeUnit.MILLISECONDS)
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
});
This builds and runs fine, and the API/deprecation warning is not shown, BUT when no network is available this correctly times out and throws the unhandled exception.
So, the code seems to be correct, but how on earth do add the exception handling to catch the timeout TimeoutException that is thrown?
I've tried numerous things, including: adding a try-catch clause around the whole Observable - this warns that TimeoutException is not thrown by the code in the `try; and adding an error handler.
Adding the error handler has got me closest, and so the code below is as far as I have got:
Disposable myDisposable = Observable.fromCallable(() -> wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(5000, TimeUnit.MILLISECONDS)
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
}, error -> {
runOnUiThread(new Runnable() {
#Override
public void run() {
myTextView.setText(R.string.network_not_available);
}
});
});
This catches the Timeout correctly and updates my UI without error, however when the network is restored it seems that the Observable might be trying to return and a null pointer exception is thrown.
(Update, this NPE might actually be being thrown sometimes after a short time whether the network is restored or not... but it is always thrown when the network restores.)
I get FATAL EXCEPTION: RxCachedThreadScheduler-1 and java.lang.NullPointerException: Callable returned a null value. Null values are generally not allowed in 3.x operators and sources.
Do I need to destroy the Observable or something to prevent the NPE?
You need to add an onError handler to your subscribe call:
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
Log.e("MainActivity", result.getError().getMessage());
}
},
error -> {
// handle error here
});
When a an exception makes it to a subscribe call that does not have an onError handler, it will throw a OnErrorNotImplementedException, like this:
io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | java.util.concurrent.TimeoutException: The source did not signal an event for 1 seconds and has been terminated.
Adding the onError handler will prevent that, and the onError handler will get called instead.
There's a few things going on here:
First of all. this gives a deprecation warning when building and a IDE warning (Result of 'Observable.subscribe()' is ignored).
subscribe() returns a Disposable. The idea is that when you're no longer interested in receiving the output of your observable, you call dispose() on the disposable and the work terminates. This can also prevent memory leaks.
As an example, imagine you have an Activity, and you start an Observable to run a long network query which finally posts something to the Activity UI. If the user navigates away before this task completes, or the Activity is otherwise destroyed, then you're no longer interested in its output because there is no longer a UI to post to. So you may call dispose() in onStop().
So, the code seems to be correct, but how on earth do add the exception handling to catch the timeout TimeoutException that is thrown?
Using the error block in subscribe is one option, but there are others. For example, if you wanted to keep using your Result class, you could use something like onErrorReturn(throwable -> Result.error(throwable)). Obviously I'm guessing what that class looks like:
.timeout(5000, TimeUnit.MILLISECONDS)
.onErrorReturn(throwable -> Result.errorWithMessage(R.string.network_not_available))
.subscribe(result -> {
if (result.isSuccessful()) {
Log.i("MainActivity", String.format("3 word address: %s", result.getWords()));
} else {
myTextView.setText(result.getErrorMessage());
}
});
java.lang.NullPointerException: Callable returned a null value. Null values are generally not allowed in 3.x operators and sources.
This:
wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute()
is returning null. You can do something like:
Observable.fromCallable(() -> {
Result<?> out = wrapper.convertTo3wa(new Coordinates(51.2423, -0.12423)).execute();
if(out == null)
out = Result.error(/*Returned null*/);
}
return out;
}
I have been tried to find out why the Runtime exception is not propagated back to the client. I have the next piece of code, so when I return a Mono.error this should be handled in the subscribe error section, to throw an exception to the client, but this is not happening. Any idea about I am doing wrong?
public void onmethod(EventDetails eventDetails, String eventType) {
messageConverter.convertAndSendMessage(eventType, eventDetails)
.flatMap(aBoolean -> {
if (aBoolean)
log.debug("Event published");
else {
log.debug("Problem publishing event.");
return Mono.error(new RuntimeException("Problem publishing event."));
}
return Mono.just(true);
})
.doOnError(throwable -> log.error("Failed to consume message", throwable))
.subscribe(
next -> { } ,
error -> {
throw Exceptions.propagate(error);
}
);
}
And this is the test I have to verify the method behaviour. This test fails as any exception is thrown. However, I can see in the logs that the exception happens.
Assertions.assertThrows(RuntimeException.class, () ->
consentsListener.onmethod(
eventDetails, "eventType")
);
19:44:32.463 [main] ERROR events.auth.ConsentsListenerImpl - Failed to consume message
java.lang.RuntimeException: Problem publishing event.
at events.auth.ConsentsListenerImpl.lambda$publishMessage$0(ConsentsListenerImpl.java:121)
at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:152)
at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
at reactor.core.publisher.Mono.subscribe(Mono.java:4084)
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4214)
at reactor.core.publisher.Mono.subscribe(Mono.java:4070)
at reactor.core.publisher.Mono.subscribe(Mono.java:4006)
at reactor.core.publisher.Mono.subscribe(Mono.java:3978)
at ...
org.opentest4j.AssertionFailedError: Expected java.lang.RuntimeException to be thrown, but nothing was thrown.
Thank you very much in advance.
Best regards.
I have been tried to find out why the Runtime exception is not propagated back to the client.
Because you're subscribing to it, which is almost certainly the wrong thing to do. The framework (Webflux in this case) should be what controls the subscription to your publisher.
If you remove your subscribe() call on that chain, change your method to return Mono<Boolean> and then return the entire chain in that method, it should work as expected.
I want to do something finally after stream terminates for any reason including cancellation, and I
found the doFinally method, but it dose not work when cancellation, because https://github.com/reactor/reactor-core/issues/1090#issuecomment-367633241 show :
Cancellation travels only upstream
So, how to capture the cancel signal?
There is my code:
public Mono<Void> myFunction() {
return Mono.just("hello")
.flatMap(s -> foo(s))
.doFinally(signalType -> {
// do something finally, but the doFinally won't be called
System.out.println(signalType);
});
}
// some other library's function that I cant not modify any way
public Mono<Void> foo(String s) {
// return a reactive stream, and will cancel it after it be subscribed, like:
return Mono.just(s)
.doOnSubscribe(subscription -> subscription.cancel())
.then();
}
You can't in that particular arrangement, because the foo() method/library seems to manage the subscription (the cancellation) itself, instead of leaving that responsibility to the consumer. Managing the subscription like that is thus not necessarily a good thing.
I've code like this in a repository:
return Completable.fromAction {
// Some code
loginService.login(id)
.subscribe(
{ response ->
if(response.isNotSuccessful()) {
throw Exception()
}
// Some code
},
{ e ->
throw e
}
)
}
I've code like this in a ViewModel:
fun onLoginAction(id) {
repository.login(id)
.subscribe(
{
showSuccess()
},
{
showFailure()
}
)
}
Basically, the ViewModel calls the login method in the repository which returns the Completable.
This results in an UndeliverableException when the response is not successful. I want the Completable's subscriber's onError() method to be called. How do I do this?
I don't have enough knowledge to actually say this with certainty, but I still think this has some value to you and it's too big for a comment.
Here's what I think it's happening. When onError fails rx won't run this through the same observable stream. Instead, it will propagate this to the RxPlugins error handler and eventually to the default exception handler in your system. You can find this here.
This is to say that when loginService.login(id) throws the exception in the onError, the Completable stream won't have a chance to catch it and forward it to the onError of the outer subscribe. In other words, the completable stream is independent of the login service one.
Usually, you'd want to create one single stream and let the view model subscribe to it. If you have more than one stream, rx has loads of operators to help you chain these. Try and make the repository return one stream from the service. Something like this:
fun login(id) = loginService.login(id)
And now on the view model, you can check if the call was or not successful using the same method - response.isNotSuccessful()