android - qué - rxjava single example
¿Cómo manejar correctamente onError dentro de RxJava(Android)? (3)
Me aparece una lista de las aplicaciones instaladas en el dispositivo. Es una operación costosa, así que estoy usando Rx para eso:
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));
Sin embargo, mi problema es con el manejo de errores. Normalmente, el usuario inicia esta pantalla, espera a que se carguen las aplicaciones, selecciona lo que es mejor y pasa a la página siguiente. Sin embargo, cuando el usuario cambia rápidamente la interfaz de usuario, la aplicación se bloquea con NullPointer.
Bien, entonces implementé este onError
. Sin embargo, todavía no funciona, y con el uso de mayúsculas arriba me lanza esto:
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)
¿Cómo debo manejar adecuadamente este problema?
Aquí está la respuesta de novato (porque soy nuevo en javarx y finalmente soluciono este problema):
Aquí está su implementación:
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();
En esta implementación anterior, cuando me suscribo, desencadeno el flujo de errores ... y obtengo un bloqueo de la aplicación.
El problema es que TIENE QUE administrar el error de la llamada subscribe (). El "doOnError (...)" es solo un tipo de ayudante que clona el error y le da un nuevo lugar para realizar alguna acción después de un error. Pero no maneja el error.
Así que tienes que cambiar tu código con eso:
Observable.create(new Observable.OnSubscribe<RegionItem>() {
@Override
public void call(Subscriber<? super RegionItem> subscriber) {
subscriber.onError(new Exception("TADA !"));
}
})
.subscribe(actionNext, actionError, actionCompleted);
No estoy seguro de la explicación real, pero así es como lo arreglo. Espero que ayude.
Mi opinión es: probablemente estés usando Action1 en
.subscribe(s -> createListView(s, view));
Deberá reemplazarlo con el suscriptor u observador, que tiene un método abstracto en onError
. Este método se llamará desde subscriber.onError(new Throwable());
EDIT:
Así es como lo haría. Mirando de cerca, creo que el principal problema en su código es la primera parte en la que llama a subscriber.onError
incluso cuando no hay ningún error. Es probable que tampoco necesite el map
porque técnicamente está pasando datos como está sin manipulación. Pero lo dejé por si es necesario más tarde.
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);
}
});
.doOnError()
es un operador y no es parte del Subscriber
.
Por lo tanto, tener un .doOnError()
no cuenta como un onError()
implementado onError()
.
Sobre la pregunta en uno de los comentarios, por supuesto, es posible utilizar lambdas.
En este caso simplemente reemplace
.doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
.subscribe(s -> createListView(s, view))
con
.subscribe(s -> createListView(s, view),
throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))