Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android rxJava Error handling with retrofit

I'm using the newer RX java where instead of

Observable.create(new Observable.OnSubscribeFunc<T>() {...});

this is used: (due to deprecation)

Observable.create(new Observable.OnSubscribe<T>() {...});

(This can be important as most example, tutorial, explonation uses the old one...)

Well, lets see my problem. I have a Java class, relevant parts from it:

private interface ApiManagerService {

    @FormUrlEncoded
    @POST("/login")
    User getUser(@Field("username") String userName, @Field("password") String password);

}


private static RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(HOST)
    .setLogLevel(RestAdapter.LogLevel.FULL)
    .build();

private static ApiManagerService apiManager = restAdapter.create(ApiManagerService.class);

public static Subscription login(final String userName, final String password, Observer<User> observer) {
    return Observable.create(new Observable.OnSubscribe<User>() {
        @Override
        public void call(Subscriber<? super User> subscriber) {
            try {

                User user = apiManager.getUser(userName, password);

                subscriber.onNext(user);
                subscriber.onCompleted();

            } catch (RetrofitError e) {
                subscriber.onError(e);
            } catch (Throwable e) {
                subscriber.onError(e);
            }
        }
    }

    ).subscribeOn(Schedulers.io())
     .retry(3)
     .observeOn(AndroidSchedulers.mainThread())
     .subscribe(observer);
}

This code almost works perfectly, if everything is ok. But if I make an intentional error, like I turn off the WiFi.. than retrofit get the "UnKnownHostException"... as it should happen at the retrofit call (getUser) in the try catch block. But instead of handling the error to onError(Throwable t) --> where I could handle, it just crashes the app. So it is like if the error never gets to the catch block. What is strange that HTTP errors (like 404, 401 etc.) is catched, got by onError(...) and everything is just fine.

Everything goes for 3 times before crash, as of .retry(3) but none gets into catch clause.

EDIT 1

LogCat Output:

    01-08 16:19:31.576  15285-16162/asd.bdef.gh D/Retrofit﹕ ---- ERROR https://testapi.com/api/login
    01-08 16:19:31.606  15285-16162/asd.bdef.gh D/Retrofit﹕ java.net.UnknownHostException: Unable to resolve host "testapi.com": No address associated with hostname
                at java.net.InetAddress.lookupHostByName(InetAddress.java:394)
                at java.net.InetAddress.getAllByNameImpl(InetAddress.java:236)
                at java.net.InetAddress.getAllByName(InetAddress.java:214)
                at com.squareup.okhttp.internal.Network$1.resolveInetAddresses(Network.java:29)
                at com.squareup.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:259)
                at com.squareup.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:233)
                at com.squareup.okhttp.internal.http.RouteSelector.nextUnconnected(RouteSelector.java:159)
                at com.squareup.okhttp.internal.http.RouteSelector.next(RouteSelector.java:133)
                at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:314)
                at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:237)
                at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:423)
                at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:105)
                at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:239)
                at com.squareup.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
                at com.squareup.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:25)
                at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:68)
                at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:37)
                at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
                at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
                at retrofit.RestAdapter$RestHandler$1.invoke(RestAdapter.java:265)
                at retrofit.RxSupport$2.run(RxSupport.java:55)
                at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
                at java.util.concurrent.FutureTask.run(FutureTask.java:234)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
                at retrofit.Platform$Android$2$1.run(Platform.java:142)
                at java.lang.Thread.run(Thread.java:841)
    01-08 16:19:31.606  15285-16162/asd.bdef.gh D/Retrofit﹕ ---- END ERROR
    01-08 16:19:31.977  15285-15285/asd.bdef.gh D/AndroidRuntime﹕ Shutting down VM
    01-08 16:19:31.977  15285-15285/asd.bdef.gh W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x41c9d8b0)
    01-08 16:19:31.977  15285-15285/asd.bdef.gh E/AndroidRuntime﹕ FATAL EXCEPTION: main

the given api address is not the real one, but the real one is reachable. I just turned off the WiFi to test error handling.

And one more use-case: If I add to the observable .onExceptionResumeNext([2nd observable]) than it goes to the 2nd observable, and it not crashes. But this is not the solution of the problem.

EDIT 2

ApiManager.login(userName, pass, new Observer<User>() {
    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) {
        DialogManager.showBasicErrorDialog(getApplicationContext(), e.getLocalizedMessage());
        logger.showLog("Login Not ok");
        e.printStackTrace();
    }

    @Override
    public void onNext(User user) {
        logger.showLog("login ok, user: " + user.getName().toString());
        {...}
    }

}); 

EDIT 3

FATAL EXCEPTION: main
rx.exceptions.OnErrorFailedException: Error occurred when trying to propagate error to Observer.onError
        at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:175)
        at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:97)
        at rx.internal.operators.NotificationLite.accept(NotificationLite.java:144)

{...}

Caused by: java.lang.NullPointerException: Attempt to read from field 'rx.functions.Action0 rx.schedulers.TrampolineScheduler$TimedAction.action' on a null object reference
        at rx.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:85)

Thanks in advance for helping.

like image 245
Peter Szucs Avatar asked Jan 08 '15 08:01

Peter Szucs


1 Answers

You don't have to build your own Observable with Retrofit, as Retrofit can directly return Observable:

http://square.github.io/retrofit/

Retrofit also integrates RxJava to support methods with a return type of rx.Observable

@GET("/user/{id}/photo") Observable getUserPhoto(@Path("id") int id);

(You won't have to handle errors by yourself)

Can you post the stacktrace of your crash ? As I think like you, that your application shouldn't crash.

like image 91
dwursteisen Avatar answered Sep 27 '22 17:09

dwursteisen