web-dev-qa-db-fra.com

Comment gérer correctement onError dans RxJava (Android)?

Je reçois une liste des applications installées sur l'appareil. C'est une opération coûteuse, donc j'utilise Rx pour ça:

    Observable<List> observable = Observable.create(subscriber -> {
        List result = getUserApps();

        subscriber.onNext(result);
        subscriber.onError(new Throwable());
        subscriber.onCompleted();
    });

    observable
            .map(s -> {
                ArrayList<String> list = new ArrayList<>();
                ArrayList<Application> applist = new ArrayList<>();
                for (Application p : (ArrayList<Application>) s) {
                    list.add(p.getAppName());
                    applist.add(p);
                }
                return applist;
            })
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
            .subscribe(s -> createListView(s, view));

Cependant, mon problème est lié à la gestion des erreurs. Normalement, l'utilisateur ouvre cet écran, attend le chargement des applications, sélectionne ce qui est le mieux et passe à la page suivante. Cependant, lorsque l'utilisateur change rapidement l'interface utilisateur, l'application se bloque avec NullPointer.

D'accord, j'ai donc implémenté ce onError. Cependant, cela ne fonctionne toujours pas, et avec l'utilisation ci-dessus, il me lance ceci:

    04-15 18:12:42.530  22388-22388/pl.digitalvirgo.safemob E/AndroidRuntime﹕ FATAL EXCEPTION: main
        Java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
                at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.Java:52)
                at Android.os.Handler.handleCallback(Handler.Java:730)
                at Android.os.Handler.dispatchMessage(Handler.Java:92)
                at Android.os.Looper.loop(Looper.Java:176)
                at Android.app.ActivityThread.main(ActivityThread.Java:5419)
                at Java.lang.reflect.Method.invokeNative(Native Method)
                at Java.lang.reflect.Method.invoke(Method.Java:525)
                at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1046)
                at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:862)
                at dalvik.system.NativeStart.main(Native Method)
         Caused by: rx.exceptions.OnErrorNotImplementedException
                at rx.Observable$31.onError(Observable.Java:7134)
                at rx.observers.SafeSubscriber._onError(SafeSubscriber.Java:154)
                at rx.observers.SafeSubscriber.onError(SafeSubscriber.Java:111)
                at rx.internal.operators.OperatorDoOnEach$1.onError(OperatorDoOnEach.Java:70)
                at rx.internal.operators.NotificationLite.accept(NotificationLite.Java:147)
                at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.Java:177)
                at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.access$000(OperatorObserveOn.Java:65)
                at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.Java:153)
                at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.Java:47)
                at Android.os.Handler.handleCallback(Handler.Java:730)
                at Android.os.Handler.dispatchMessage(Handler.Java:92)
                at Android.os.Looper.loop(Looper.Java:176)
                at Android.app.ActivityThread.main(ActivityThread.Java:5419)
                at Java.lang.reflect.Method.invokeNative(Native Method)
                at Java.lang.reflect.Method.invoke(Method.Java:525)
                at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1046)
                at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:862)
                at dalvik.system.NativeStart.main(Native Method)
         Caused by: Java.lang.Throwable
                at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment.lambda$getAppList$25(ApplicationsFragment.Java:267)
                at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment.access$lambda$2(ApplicationsFragment.Java)
                at pl.digitalvirgo.safemob.fragments.wizard.ApplicationsFragment$$Lambda$3.call(Unknown Source)
                at rx.Observable$1.call(Observable.Java:145)
                at rx.Observable$1.call(Observable.Java:137)
                at rx.Observable.unsafeSubscribe(Observable.Java:7304)
                at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.Java:62)
                at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.Java:47)
                at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:390)
                at Java.util.concurrent.FutureTask.run(FutureTask.Java:234)
                at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.Java:153)
                at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.Java:267)
                at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1080)
                at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:573)
                at Java.lang.Thread.run(Thread.Java:841)

Comment dois-je gérer correctement ce problème?

25
a_dzik

.doOnError() est un opérateur et ne fait pas en tant que tel partie du Subscriber.

Par conséquent, avoir une .doOnError() ne compte pas comme une onError() implémentée.

À propos de la question dans l'un des commentaires, il est bien sûr possible d'utiliser des lambdas.

Dans ce cas, remplacez simplement

.doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
.subscribe(s -> createListView(s, view))

avec

.subscribe(s -> createListView(s, view),
    throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
19
LukeJanyga

Ma position est la suivante: vous utilisez probablement Action1 dans

.subscribe(s -> createListView(s, view));

Vous devrez le remplacer par Subscriber ou Observer qui a la méthode abstraite onError. Cette méthode sera appelée depuis subscriber.onError(new Throwable());

EDIT: Voici comment je procéderais. En y regardant de plus près, je pense que le principal problème dans votre code est la première partie où vous appelez subscriber.onError Même lorsqu'il n'y a pas d'erreur. Vous n'avez probablement pas besoin non plus de map car vous passez techniquement des données telles quelles sans manipulation. Mais je l'ai laissé au cas où il serait nécessaire plus tard.

     Observable.create(new Observable.OnSubscribe<Application>() {
        @Override
        public void call(Subscriber<? super Application> subscriber) {
            List result = getUserApps();
            if (result != null){
                for (Application app : result){
                     subscriber.onNext(app);
                }
                subscriber.onComplete();
            }else{
                subscriber.onError(new IOException("no permission / no internet / etc"));
               //or if this is a try catch event you can pass the exception
            }     
        }
     })
    .subscribeOn(Schedulers.io())//the thread *observer* runs in
    .observeOn(AndroidSchedulers.mainThread())//the thread *subscriber* runs in
    .map(new Func1<Application, String>() {

        // Mapping methods are where data are manipulated. 
        // You can simply skip this and 
        //do the same thing in Subscriber implementation
        @Override
        public String call(Application application) {
            return application.getName();
        }
    }).subscribe(new Subscriber<String>() {
        @Override
        public void onCompleted() {
           Toast.makeText(context, "completed", Toast.LENGTH_SHORT).show();
           //because subscriber runs in main UI thread it's ok to do UI stuff
           //raise Toast, play sound, etc
        }

        @Override
        public void onError(Throwable e) {
           Log.e("getAppsError", e.getMessage());
           //raise Toast, play sound, etc
        }

        @Override
        public void onNext(String s) {
            listAdapter.add(s);
        }
    });
12
inmyth

Voici la réponse du débutant (parce que je suis nouveau dans javarx et que j'ai finalement résolu ce problème):

Voici votre implémentation:

    Observable.create(new Observable.OnSubscribe<RegionItem>() {
                @Override
                public void call(Subscriber<? super RegionItem> subscriber) {
                    subscriber.onError(new Exception("TADA !"));
                }
            })
            .doOnNext(actionNext)
            .doOnError(actionError)
            .doOnCompleted(actionCompleted)
            .subscribe();

Dans cette implémentation précédente, lorsque je m'abonne, je déclenche le flux d'erreurs ... et j'obtiens un plantage de l'application.

Le problème est que vous DEVEZ gérer l'erreur à partir de l'appel subscribe (). Le "doOnError (...)" est juste une sorte d'aide qui clone l'erreur et vous donne un nouvel endroit pour effectuer une action après une erreur. Mais cela ne résout pas l'erreur.

Vous devez donc changer votre code avec ça:

    Observable.create(new Observable.OnSubscribe<RegionItem>() {
                @Override
                public void call(Subscriber<? super RegionItem> subscriber) {
                    subscriber.onError(new Exception("TADA !"));
                }
            })
            .subscribe(actionNext, actionError, actionCompleted);

Je ne suis pas sûr de la vraie explication, mais c'est comme ça que je la corrige. J'espère que cela vous aidera.

6
Tobliug