RxJava Zip Operator Error Handling with Iterator - android

Background
I have a process that uses RxJava to get data from different locations based on a list. Each item is got through a different method (all returning Observables.). Due to having N items to get the logic operator to use is zip with an iterator.
The Problem
The code below works as expected but it seems "wrong" that I need a try-catch block to catch the Exception that is thrown by getBigFoo() - that returns a FooNotFoundException. Do not the other error related operators cover this, such as onErrorResumeNext() and onErrorReturn()?
private Observable<Bar> processFoos(List<Foo> foos) {
List<Observable<? extends IBar>> observablesToZip = new ArrayList<>();
for(Foo foo : foos) {
switch (foo.getType()) {
case BIG_FOO :
try {
observablesToZip.add(getBigFoo(foo.getId()));
} catch (Exception exception) {
//do nothing - but this seems wrong
}
}
}
return Observable.zip(observablesToZip, results -> mergeFoosIntoBar(results));
}
Attempts Made
The attempt below doesn't seem to catch the Exception generated. I don't understand why as there are technically no upstream or downstream items in the sequence, so Observable.empty() should work?
private Observable<Bar> processFoos(List<Foo> foos) {
List<Observable<? extends IBar>> observablesToZip = new ArrayList<>();
for(Foo foo : foos) {
switch (foo.getType()) {
case BIG_FOO :
observablesToZip.add(getBigFoo(foo.getId().onErrorResumeNext(Observable.empty()));
}
}
return Observable.zip(observablesToZip, results -> mergeFoosIntoBar(results));
}

You may want to use defer. getBigFoo should not throw an exception but instead return an Observable in error. So defer may help you to fix it :
Observable<IBar> obs = Observable.defer(() -> {
try {
return getBigFoo(foo.getId());
} catch (Exception ex) {
return Observable.error(ex);
}
});
observablestoZip.add(obs);

#dwursteisen was on to the right answer, but wasn't quite there.
My issue was that I was throwing a new FooNotFoundException:
throw new FooNotFoundException()
But what I needed to do was:
return Observable.error(new FooNotFoundException());
Then in my Zip function:
observablesToZip.add(getBigFoo(foo.getId())).onExceptionResumeNext(Observable.just(null);
Using the above combination means that the overall sequence does not abort and return an error when the individual Observables are resolved and potentially throw errors.

Could you make getBigFoo(foo.getId()) throw RuntimeException instead Exception?. All Exceptions on Pipeline must be captured, but not runtimeExceptions.
Take a look to this silly example
/**
* Here is a silly example how runtimeExceptions are not needed
*/
#Test
public void observableRuntimeException() {
Integer[] numbers = {0, 1, 2, 3, 4, 5};
Observable.from(numbers)
.doOnNext(number -> throwRuntimeException())
.doOnError(t -> System.out.println("Expecting illegal argument exception:" + t.getMessage()))
.subscribe();
}
private void throwRuntimeException() {
throw new RuntimeException();
}
you can see more examples here https://github.com/politrons/reactive

Related

Catching all possible exceptions while using android videoview

I am using android videoview to display a loop of videos, as per our requirement the video loop should continue even if one of the videos gives an error.
To catch any exception, I have included the relevant code in a try-catch block as shown in the below code. However, while testing all scenarios, I gave the wrong path to the videoview.setVideopath() but the exception is not caught. I can see in the android studio console that it reports the data source not found error, but catch block does not catch the exception. I also tried implementing onerrorlistener, it is also not called when this happens.
Could you please help me, I am attaching the relevant code and exception log, many thanks for your help.
private void DisplayVideo_VideoView(){
try {
adplayer = (ResizableVideoView) findViewById(R.id.adplayer);
String MediaStorePath = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/Videos";
// String videoPath = MediaStorePath + "/" + Root2Util.Videopathlist.get(CurrentMediaIndex).getFileName();
String videoPath = MediaStorePath + "/1" + Root2Util.Videopathlist.get(CurrentMediaIndex).getFileName();
//adplayer.setVideoPath(videopath[CurrentMediaIndex]);
adplayer.setVideoPath(videoPath);
adplayer.changeVideoSize(Root2Util.SCREEN_WIDTH, Root2Util.SCREEN_HEIGHT);
adplayer.setVisibility(View.VISIBLE);
adplayer.start();
adplayer.setKeepScreenOn(true);
adplayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
CurrentMediaIndex++;
//mp.reset();
if (CurrentMediaIndex == Root2Util.Videopathlist.size()) {
CurrentMediaIndex = 0;
}
playMedia();
// ErrorHandlerAsyncTask ErrorTask=new ErrorHandlerAsyncTask();
// ErrorTask.execute((Object)getApplicationContext(),(Object)String.valueOf(what));
return false;
}
});
} catch(Exception e) {
ErrorHandlerAsyncTask ErrorTask=new ErrorHandlerAsyncTask();
ErrorTask.execute((Object)getApplicationContext(),(Object)e.getMessage());
}
Exception log from the console :
W/VideoView: Unable to open content: /storage/emulated/0/Download/Videos/1f0a9106d-d7d5-470c-
b287-3e3cad7d13fb.mp4
java.io.IOException: setDataSource failed.
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1091)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1065)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1019)
at android.widget.VideoView.openVideo(VideoView.java:352)
at android.widget.VideoView.access$2100(VideoView.java:72)
at android.widget.VideoView$7.surfaceCreated(VideoView.java:628)
at android.view.SurfaceView.updateWindow(SurfaceView.java:580)
at android.view.SurfaceView.setVisibility(SurfaceView.java:256)
at root2tech.cloudplayer.HomepageActivity.DisplayVideo_VideoView(HomepageActivity.java:728)
at root2tech.cloudplayer.HomepageActivity.playMedia(HomepageActivity.java:958)
at root2tech.cloudplayer.HomepageActivity.access$200(HomepageActivity.java:78)
at root2tech.cloudplayer.HomepageActivity$5.run(HomepageActivity.java:571)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:935)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:730)
So, I just faced a similar issue, I needed to use a VideoView to play the video from a URL without any knowledge of if the URL was a valid video or not. I understand why you're asking about catching every exception as well. My VideoView would print out an IOException and another which I can't quite remember at the moment of this answer. To fix this, I used a very similar code to yours but in a different order. The setOnErrorListener is what solved my issue for me but I placed my OnErrorListener directly after my VideoPlayer initialization and before the setVideoPath.
This works because the setVideoPath is where the errors are handled, unfortunately, VideoView will print these errors to the log but it will not throw anything to crash the app (which I don't agree with or like one bit). Because of this, your setOnErrorListener should at least go before you set the path or there will be nothing for it to catch as the error would have been thrown already (The errors aren't thrown on start oddly enough.
To apply my solution to your code, I would change it to this:
private void DisplayVideo_VideoView(){
//initialize adplayer
adplayer = (ResizableVideoView) findViewById(R.id.adplayer);
//begin listening for errors
adplayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
CurrentMediaIndex++;
if (CurrentMediaIndex == Root2Util.Videopathlist.size()) {
CurrentMediaIndex = 0;
}
playMedia();
return false;
}
});
//build variables for readability
String MediaStorePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/Videos";
String videoPath = MediaStorePath + "/1" + Root2Util.Videopathlist.get(CurrentMediaIndex).getFileName();
//set path - If there is an issue with videoPath, the error should be thrown here
adplayer.setVideoPath(videoPath);
//final adplayer customizations
adplayer.changeVideoSize(Root2Util.SCREEN_WIDTH, Root2Util.SCREEN_HEIGHT);
adplayer.setVisibility(View.VISIBLE);
//begin the adplayer
adplayer.start();
adplayer.setKeepScreenOn(true);
}
It is not a good style of code to catch a raw Exception, it is too broad to handle different error cases. However, for most of the IO operations IOException is the base exception, it is recommended to catch this.
As per java docs, we also need to consider IO operations can throw IOError. Error is not under the hierarchy of Exception, but both IOException and Error share Throwable as the base class. With this consideration, you can write your try-catch block as:
try {
// Your code
} catch (Throwable throwable) {
if (throwable instanceof IOException) {
// handle IOException here
} else if (throwable instanceof Error) {
if (t instanceof IOError) {
// handle IOError here
}
} else {
//This else will be reached only if you have any custom exceptions
}
}
For more readability of the code, you can use multiple catch blocks:
try {
// Your code
} catch (IOException ioException) {
// handle IOException here
} catch (IOError ioError) {
// handle IOError here
}
Also, you can add throws clause to your method if you want to handle these exceptions later:
public void displayVideo() throws Throwable {
// Your code here
}
Note: This is not the perfect solution to capture all exceptions for your scenario. But, since your code also includes IO operations on the file, hence have used the above examples to explain how you can possibly implement your exception handling.

RxJava MissingBackpressureException not thrown

We are using a lot of RxJava1 code in our Android app. Recently, we started getting a lot of MissingBackpressureException thrown around. So I tried understanding the mechanism around backpressure better.
I'm able to get a backpressure exception to be thrown
BehaviorSubject<Integer> subject = BehaviorSubject.create();
subject
.observeOn(Schedulers.computation())
.subscribe(x -> {
try {
logger.info("got " + x);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
for (int i = 0; true; ++i) {
logger.info("sending " + i);
subject.onNext(i);
}
That's great, I get a MissingBackpressureException, but when I cause the subscribe action to never return, I no longer get MissingBackpressureException, so this code:
subject
.observeOn(Schedulers.computation())
.subscribe(x -> {
while(true) {
try {
logger.info("got " + x);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
So I got a few questions here:
Why am I not getting MissingBackpressureException thrown in the 2nd subscriber?
What happens to all the objects? I don't see the memory footprint getting larger, so I assume that they are thrown away? why?
When I try to do it with RxJava2 by adding toFlowable(BackpressureStrategy.ERROR) to the subscriber, I'm not getting an exception in either of the cases, what's happening here?
subject
.observeOn(Schedulers.computation())
.toFlowable(BackpressureStrategy.ERROR)
.subscribe(x -> {

Chunked streaming not behaving as expected Retrofit+RxJava

#GET("poll/session/{sessionId}/details")
Observable getSessionDetails(#Path("sessionId") String sessionId);
#GET("poll/session/{sessionId}/details")
#Streaming
Observable getSessionDetails(#Path("sessionId") String sessionId);
#Override
public Observable getSessionDetails(String sessionId) {
return sessionAPI.getSessionDetails(sessionId)
.flatMap(responseBody -> events(responseBody.source()));
}
public static Observable<String> events(BufferedSource source) {
return Observable.create(subscriber -> {
try {
while (!source.exhausted()) {
subscriber.onNext(source.readUtf8Line());
}
} catch (IOException e) {
e.printStackTrace();
subscriber.onError(e);
}
subscriber.onCompleted();
});
}
The events() method does not get called unless all the chunks are done.
But the chunked streams are expected to be delivered chunk by chunk, which does not seem to be happening.
I have tried with and without the #Streaming annotation to the API, yet the behaviour is same.
I had used Android Retrofit 2 + RxJava: listen to endless stream as a reference to do my implementation
Alright guys I found the answer. It was because I was logging with the Body Attribute
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
So, since the logger is waiting for the whole body to print it, it was behaving the way as mentioned in the problem.
reference : Square's Retrofit response parsing logic: streaming?

How to handle errors in BlockingObservable without subscribe()?

for various reasons I have to block on observable like this
Foo foo = fooBarNetworkRequestObservable()
.toBlocking()
.single();
return foo;
But how do I handle errors now? Is is possible without subscribe(new Subscriber .. onError()?. I tried wrapping the code in try-catch but compiler complains that IOException is never thrown in coresponding try-catch block. Any solutions? Thanks
You are in blocking world so you need try-catch. Since the API can't throw checked exceptions, we wrap them into RuntimeException for you:
try {
source.toBlocking.single();
} catch (RuntimeException ex) {
if (ex.getCause() instanceof IOException) {
// handle IOException
} else {
throw ex; // something other happened
}
}

android exception for validating file exist not working

I've been working with Eclipse ADT for about 2 months. In that time, I have a small utility that allows me to select an IP Address and Port, and then send a file to that combo. The utility works as intended, but when I type in the wrong file name, the application hangs.
#Override
public void run() {
if (data != null) {
this.send(data);
} else if (this.file != null) {
if (file.exists()) {
this.send(file);
} else {
transferError = new FileNotFoundException("The specified file could not be found");
}
}
}
I've even tried to do the following in hopes that one or the other would throw, but I am unsuccessful in both.
public void run() {
if (data != null) {
this.send(data);
} else if (this.file != null) {
if (file.exists()) {
this.send(file);
} else {
transferError = new FileNotFoundException("The specified file could not be found");
}
}try {
throw new Exception("blah blah blah");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
I've jockeyed around the exception, I've added the one above, I've tried placing it in different places, and all unsuccessful. Again, I'm exceptionally new to this, and got here from basically mincing various tcp client codes. Aside of creating a way to throw the exception correctly, please help me understand why the first one isn't working and why the one you suggest is.
in your else block you aren't throwin the transferError you create.
throw transferError;
However you probably won't be able to do that because FileNotFoundException is a checked exception and the run() method doesn't declare any thrown exceptions. You probably need to find a different way to present the error to the user, like with a Toast or something.
Your second block doesn't work because you are catching the exception you throw.

Categories

Resources