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;
}
Related
I'm trying to get into CompletableFuture class for a project I'm running, and I got to some question here:
There is the following method: it tries to find a conversation by its ID or hash; and, if not found, it throws an exception. So far, so good.
public ConversationOutput getConversationByIdOrHash(String conversationIdOrHash)
throws ConversationNotFoundException {
Conversation conversation = this.conversationRepository.getByIdOrHash(conversationIdOrHash);
if (conversation == null) {
throw new ConversationNotFoundException(conversationIdOrHash);
}
return this.modelMapper.map(conversation, ConversationOutput.class);
}
Note that I am throwing ConversationNotFoundException from my method signature. My SpringBoot controller is reacting to this exception and it's all working fine since the beginning.
What I'm trying to do is to make this to a CompletableFuture return and actually throwing an exception, something similar to:
public CompletableFuture<ConversationOutput> getConversationByIdOrHashAsync(String conversationIdOrHash)
throws ConversationNotFoundException {
return CompletableFuture.supplyAsync(() -> this.getConversationByIdOrHash(conversationIdOrHash));
}
I've seen posts where people use exceptionally to handle exceptions, but what I really want to do is to throw it to my controller and let it handle it. Any suggestions of how can I make it?
Thank you all!
The question is do you care about the result of CompletableFuture.
CompletableFuture is like a special task and it is processed on other thread. If you don't invoke .join() you won't receive the results of CompletableFuture. This method also will propagate the exception if any occured. However it waits for CompletableFuture to finish and blocks the request.
However, there is no way to get exceptions from the inside of the CompletableFuture without waiting, you have to treat it like other task.
You can pass the completed future in case of a success, and failed future along with your custom exception.
public CompletableFuture<ConversationOutput> getConversationByIdOrHashAsync(String conversationIdOrHash) {
try {
return CompletableFuture.completedFuture(this.getConversationByIdOrHash(conversationIdOrHash));
} catch (ConversationNotFoundException e) {
return CompletableFuture.failedFuture(e);
}
}
and then at your controller level you can handle the exception.
final CompletableFuture<ConversationOutput> future = getConversationByIdOrHashAsync("idOrHash");
future.whenComplete((r, e) -> {
if (e != null) {
if (e instanceof ConversationNotFoundException) {
//handling
}
}
});
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'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()
I have the following code, which returns a lot of data in the response, so much that I get a NullPointerException when it's being loaded in the android Activity when I scroll down too fast (since not all the data has been initialized yet), no problems if I wait a second and then scroll.
I want a way to delay the subscribe part, so that the Response<GetFeedTopicsResponseBody> is entirely populated with data (none is not initialized) when I call setAdapter. I tried checking response.isSuccessful but that does not work because no problem with the response itself, just the data takes time to deserialize into Java objects from JSON. I also tried onComplete in subscribe but that does not work either.
So I want either a way in RxJava2 to have a boolean value switch to notify the following subscription once it is complete, it will subscribe.
mGetFeedTopicsDisposable = ApiClient.getInstance()
.doGetFeedTopicsQuery(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
.subscribe((Response<GetFeedTopicsResponseBody> response) -> {
if (response.body() != null) {
List<Topic> topics = response.body().getTopics();
if (topics != null) {
mBinding.fragmentTopicListRecyclerTopics.setAdapter(TopicListAdapter.getInstance(topics));
if (response.body().isPaginated()) {
mRequestBuilder.setCursor(response.body().getCursor());
}
}
}
}, (Throwable ex) -> {
Log.e(TAG, ex.getMessage());
});
The error message I specifically got was:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.models.User.getThumbnailImageUrl()' on a null object reference
where this User object is set as a field of the Topic object which is added into the list of topics retrieved with getTopics(). If I don't scroll, I don't get this NullPointerException and the thumbnail urls for the Users are loaded properly.
Question : How do I delay RxJava2 Subscription?
Example :
I have added repeat(...) for better understanding.
io.reactivex.Observable
.just(new Object())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.delay(10, TimeUnit.SECONDS)
.repeat(2)
.doOnSubscribe(disposable -> Log.d("Delay Example ","Observer subscribed at : "+ DateFormat.getDateTimeInstance().format(new Date()) + " and execute after 10 seconds"))
.subscribe(new DefaultObserver<Object>() {
#Override
public void onNext(Object o) {
Log.d("Delay Example ","on Next : "+ DateFormat.getDateTimeInstance().format(new Date()));
}
#Override
public void onError(Throwable e) {}
#Override
public void onComplete() {
Log.d("Delay Example ","on Complete : "+ DateFormat.getDateTimeInstance().format(new Date()));
}
});
Output:
You see, on Next is called twice with 10 second delay.
Here, you can do adapter related operations in onComplete. :)
Hope this answers the question that you've asked.
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.