I am new to using RxAndroid and RxJava. I am using RxJava + Retrofit2 to make GET requests. I am doing approximately 1500 GET requests using the following code and getting Out of memory error. However the same code this time with only retrofit, NO RxAndroid and it works. So my conclusion was I am doing something wrong in RxAndroid. Can you please help with what I am missing?
Code Sample:
Subject<Story> mStoryEmitter = PublishSubject.create();
private void getStory(int storyID) {
HNApi.Factory.getInstance().getStory(storyID).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getStoryObserver());
}
mStoryListEmitter.subscribe(new Observer<List<Integer>>() {
#Override
public void onSubscribe(Disposable d) {}
#Override
public void onNext(List<Integer> value) {
if(mRecyclerView != null) {
mRecyclerView.setAdapter(null);
if(mAdapter != null) {
mAdapter.clear();
mAdapter = null;
}
}
mAdapter = new SimpleRecyclerViewAdapter();
mRecyclerView.setAdapter(mAdapter);
for(Integer storyID : value) {
getStory(storyID);
}
}
#Override
public void onError(Throwable e) {}
#Override
public void onComplete() {}
});
private DisposableObserver<Story> getStoryObserver() {
DisposableObserver<Story> observer = new DisposableObserver<Story>() {
#Override
public void onNext(Story value) {
mStoryEmitter.onNext(value);
dispose();
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
return observer;
}
Error:
Throwing OutOfMemoryError "Could not allocate JNI Env"
java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.OutOfMemoryError: Could not allocate JNI Env
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:1063)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:921)
at java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1556)
at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:310)
at java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:543)
at java.util.concurrent.ScheduledThreadPoolExecutor.submit(ScheduledThreadPoolExecutor.java:642)
at io.reactivex.internal.schedulers.NewThreadWorker.scheduleActual(NewThreadWorker.java:120)
at io.reactivex.internal.schedulers.IoScheduler$EventLoopWorker.schedule(IoScheduler.java:221)
at io.reactivex.Scheduler.scheduleDirect(Scheduler.java:130)
at io.reactivex.Scheduler.scheduleDirect(Scheduler.java:109)
AppData::create pipe(2) failed: Too many open files
at io.reactivex.internal.operators.observable.ObservableSubscribeOn.subscribeActual(ObservableSubscribeOn.java:36)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at io.reactivex.internal.operators.observable.ObservableObserveOn.subscribeActual(ObservableObserveOn.java:44)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at com.example.MainActivity.getStory(MainActivity.java:100)
at com.example.MainActivity.access$300(MainActivity.java:25)
at com.example.MainActivity$2.onNext(MainActivity.java:67)
at com.example.MainActivity$2.onNext(MainActivity.java:49)
at io.reactivex.subjects.PublishSubject$PublishDisposable.onNext(PublishSubject.java:263)
at io.reactivex.subjects.PublishSubject.onNext(PublishSubject.java:182)
at com.example.MainActivity$5.onNext(MainActivity.java:147)
at com.example.MainActivity$5.onNext(MainActivity.java:138)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:198)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:250)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
... 7 more
AppData::create pipe(2) failed: Too many open files
FATAL EXCEPTION: main
Process: com.example, PID: 15857
java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.OutOfMemoryError: Could not allocate JNI Env
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:1063)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:921)
at java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1556)
at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:310)
at java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:543)
at java.util.concurrent.ScheduledThreadPoolExecutor.submit(ScheduledThreadPoolExecutor.java:642)
at io.reactivex.internal.schedulers.NewThreadWorker.scheduleActual(NewThreadWorker.java:120)
at io.reactivex.internal.schedulers.IoScheduler$EventLoopWorker.schedule(IoScheduler.java:221)
at io.reactivex.Scheduler.scheduleDirect(Scheduler.java:130)
at io.reactivex.Scheduler.scheduleDirect(Scheduler.java:109)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn.subscribeActual(ObservableSubscribeOn.java:36)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at io.reactivex.internal.operators.observable.ObservableObserveOn.subscribeActual(ObservableObserveOn.java:44)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at com.example.MainActivity.getStory(MainActivity.java:100)
at com.example.MainActivity.access$300(MainActivity.java:25)
at com.example.MainActivity$2.onNext(MainActivity.java:67)
at com.example.MainActivity$2.onNext(MainActivity.java:49)
at io.reactivex.subjects.PublishSubject$PublishDisposable.onNext(PublishSubject.java:263)
at io.reactivex.subjects.PublishSubject.onNext(PublishSubject.java:182)
at com.example.MainActivity$5.onNext(MainActivity.java:147)
at com.example.MainActivity$5.onNext(MainActivity.java:138)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:198)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:250)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
... 7 more
Posted the same question at RxAndroid github.
And JakeWharton reply acually heped
The problem is that Schedulers.io() uses a cached thread pool without a limit and thus is trying to create 1500 threads. You should consider using a Scheduler that has a fixed limit of threads, or using RxJava 2.x's parallel() operator to parallelize the operation to a fixed number of workers.
If you're using raw Retrofit by default it uses OkHttp's dispatcher which limits the threads to something like 64 (with a max of 5 per host). That's why you aren't seeing it fail.
If you use createAsync() when creating the RxJava2CallAdapterFactory it will create fully-async Observable instances that don't require a subscribeOn and which use OkHttp's Dispatcher just like Retrofit would otherwise. Then you only need observeOn to move back to the main thread, and you avoid all additional thread creation.
Add all your Disposables to CompositeDisposable and dispose it for every cycle
CompositeDisposable disposable = new CompositeDisposable();
mStoryListEmitter.subscribe(new Observer<List<Integer>>() {
#Override
public void onSubscribe(Disposable d) {
disposable.add(d); // adding disposable
}
#Override
public void onNext(List<Integer> value) {
if(mRecyclerView != null) {
mRecyclerView.setAdapter(null);
if(mAdapter != null) {
mAdapter.clear();
mAdapter = null;
}
}
mAdapter = new SimpleRecyclerViewAdapter();
mRecyclerView.setAdapter(mAdapter);
for(Integer storyID : value) {
getStory(storyID);
}
}
#Override
public void onError(Throwable e) {}
#Override
public void onComplete() {
diposable.dispose(); // <--- Disposing on complete
}
})
;
Related
I'm working on a large codebase with a lot of network calls. It currently works with Retrofit 1.9 and RxJava 1.x. But, we're trying to go to RxJava 2.x.
I currently keep getting a NetworkOnMainThreadException on our RxJava 2 code. I'm using RxJava 2.x and Retrofit 1.9. The plan is to go to Retrofit 2.x later. But, for now, we need to make this work with RxJava 2 + Retrofit 1.9.
The code in question is:
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<Void>() {
#Override
public void onNext(Void response) {
if (listener != null) {
listener.onSuccess(response);
}
}
#Override
public void onError(Throwable e) {
if (listener != null) {
listener.onError(e);
}
}
#Override
public void onComplete() {
//do nothing for now
}
});
Any ideas how the network operation is ending up on the UI thread when I told it to subscribeOn(Schedulers.io())?
Edit: by request here's the stacktrace. I genericized filenames and such because I'm not allowed to divulge those details...
retrofit.RetrofitError
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:400)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at java.lang.reflect.Proxy.invoke(Proxy.java:913)
at <package-name>.network.api.$Proxy40.someApiMethod(Unknown Source)
at <package-name>.SomeApi.someApiMethod(SomeApi.java:30)
Caused by: android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1425)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:102)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:90)
at java.net.InetAddress.getAllByName(InetAddress.java:787)
at com.squareup.okhttp.internal.Network$1.resolveInetAddresses(Network.java:29)
at com.squareup.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:224)
at com.squareup.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:193)
at com.squareup.okhttp.internal.http.RouteSelector.next(RouteSelector.java:113)
at com.squareup.okhttp.internal.http.HttpEngine.createNextConnection(HttpEngine.java:344)
at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:329)
at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:319)
at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
at com.squareup.okhttp.Call.getResponse(Call.java:271)
at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:228)
at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:199)
at com.squareup.okhttp.Call.execute(Call.java:79)
at retrofit.client.OkClient.execute(OkClient.java:53)
I'm having tons of ConcurrentModificationException when doing some update operations with my database using ORMlite. I'm wrapping the code that does the actual update into rx.Observables to make it asynchronously. Looks like this:
#Override
public void setFavoriteTeams(List<Team> teams) {
final Iterator<Team> it = teams.iterator();
Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(final Subscriber<? super Object> subscriber) {
try {
List<String> teamIds = new ArrayList<>();
while (it.hasNext()) {
Team team = it.next();
teamIds.add(team.getmSipId());
}
setFavoriteTeamsSql(teamIds);
} catch (Exception e) {
handleException(e);
} finally {
subscriber.onNext(null);
subscriber.onCompleted();
}
}
}).subscribeOn(Schedulers.computation()).subscribe();
}
Ironically, a while ago I started using Interator instead of a for-loop to avoid any ConcurrenceModificationException that may happen, but instead they increased a lot. The setFavoriteTeamsSql(List<String>) method simply uses ORMlite's UpdateBuilder class to update the table. The exception is being thrown at the Team team = it.next(); line.
Any idea about how to fix this? Right now I'm rolling back the changes and going back to the for-loop. But I'd like doing this the right way.
UPDATE:
This is how the Stacktrace looks like:
Non-fatal Exception: java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
at com.siplay.android_siplay.data.cache.db.DBTeamCache$4.call(DBTeamCache.java:164)
at com.siplay.android_siplay.data.cache.db.DBTeamCache$4.call(DBTeamCache.java:157)
at rx.Observable.unsafeSubscribe(Observable.java:10150)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.EventLoopsScheduler$EventLoopWorker$1.call(EventLoopsScheduler.java:172)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:154)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
UPDATE 2:
...
mNetworkTeamRepository
.subscribeOn(Schedulers.io())
.obseveOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultSubscriber<List<Teams>>() {
#Override
public void onSuccess(List<Team> teams) {
mTeamsCache.setFavoriteTeams(teams);
}
public void onResult(List<Team> teams) {
callback.showTeams(teams);
}
});
...
The TeamRepository internally uses a Retrofit service to get the server-side response.
Your code is equivalent to
public void setFavoriteTeams(List<Team> teams) {
Observable
.fromIterable(teams)
.map(Team::getmSipId)
.toList()
.doOnNext(this::setFavoriteTeamsSql)
.subscribeOn(Schedulers.computation())
.subscribe();
}
But I still can't see how your code emits ConcurrentModificationException - maybe a stack trace will help?
Im performing a write to a BLE characteristic, where with these specific devices certain characteristics are not writeable if the peripheral is still locked.
I'd like to simply provide a visual notification if this is called while the device is still locked, instead of the exception being thrown.
Here is the first part of the code which is from a custom library I've written for this application:
public Observable<byte[]> setInterval(int interval) {
if (!(interval > 0)) {
return Observable.error(new IllegalArgumentException("Interval must be greater than 0."));
}
byte[] bytes = Utils.formatIntToBytes(interval);
return mConnection.writeCharacteristic(Constants.INTERVAL, bytes)
.doOnError(throwable -> Log.d(TAG, "call: Error writing to the interval characteristic"))
.onErrorReturn(throwable -> {
return new byte[0];
});
}
And here is the method that uses the previous:
private void setInterval(int interval) {
mDevice.setInterval(interval)
.onErrorResumeNext(throwable -> {
return Observable.empty();
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<byte[]>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(byte[] bytes) {
if (bytes != null) {
Toast.makeText(DeviceDetailActivity.this, Utils.formatResponse(bytes), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(DeviceDetailActivity.this, "Seems to of been an error. Ensure the device is unlocked.", Toast.LENGTH_SHORT).show();
}
}
});
}
This was previously just an Action1, but I changed to a Subscriber to clearly show onError() has been implemented.
I've added a bunch of extra operators (onErrorReturn(), onErrorResumeNext()) in an attempt to prevent the exception.
Why is the exception still coming through?
EDIT: The stack trace:
java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
at rx.android.schedulers.LooperScheduler$ScheduledAction.run(LooperScheduler.java:112)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: rx.exceptions.OnErrorNotImplementedException
at rx.internal.util.InternalObservableUtils$ErrorNotImplementedAction.call(InternalObservableUtils.java:386)
at rx.internal.util.InternalObservableUtils$ErrorNotImplementedAction.call(InternalObservableUtils.java:383)
at rx.internal.util.ActionSubscriber.onError(ActionSubscriber.java:44)
at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:152)
at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:115)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.checkTerminated(OperatorObserveOn.java:276)
at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.call(OperatorObserveOn.java:219)
at rx.android.schedulers.LooperScheduler$ScheduledAction.run(LooperScheduler.java:107)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: BleGattException{status=8, bleGattOperation=BleGattOperation{description='CHARACTERISTIC_WRITE'}}
at com.polidea.rxandroidble.internal.connection.RxBleGattCallback.propagateStatusErrorIfGattErrorOccurred(RxBleGattCallback.java:245)
at com.polidea.rxandroidble.internal.connection.RxBleGattCallback.access$100(RxBleGattCallback.java:26)
at com.polidea.rxandroidble.internal.connection.RxBleGattCallback$1.onCharacteristicWrite(RxBleGattCallback.java:110)
at android.bluetooth.BluetoothGatt$1.onCharacteristicWrite(BluetoothGatt.java:407)
at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:279)
at android.os.Binder.execTransact(Binder.java:453)
Many RxJava tutorials with RxTextView.textChanges examples and debounce, use 'live search'. For example: Improving UX with RxJava. So I've implemented this example and I tried to play around:
RxTextView.textChanges(searchView)
.observeOn(Schedulers.io())
.skip(1)
.debounce(DELAY_BEFORE_REQUEST_MS, TimeUnit.MILLISECONDS)
.map(new Func1<CharSequence, String>() {
#Override public String call(CharSequence charSequence) {
return charSequence.toString();
}
})
.switchMap(new Func1<String, Observable<Response>>() {
#Override public Observable<Response> call(String query) {
return retrofitService.search(query);
}
})
.subscribe();
Everything looked good, until I decided to simulate GPRS network type on Android emulator.
First api call was triggered, and when I added next letter to 'searchView', app crashed with InterruptedIOException:
java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:60)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: rx.exceptions.OnErrorNotImplementedException: thread interrupted
at rx.Observable$27.onError(Observable.java:7923)
at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:159)
at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:120)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.onError(OperatorSubscribeOn.java:71)
at rx.observers.SerializedObserver.onError(SerializedObserver.java:159)
at rx.observers.SerializedSubscriber.onError(SerializedSubscriber.java:79)
at rx.internal.operators.OperatorSwitch$SwitchSubscriber.error(OperatorSwitch.java:223)
at rx.internal.operators.OperatorSwitch$InnerSubscriber.onError(OperatorSwitch.java:282)
at rx.internal.operators.OperatorMerge$MergeSubscriber.reportError(OperatorMerge.java:240)
at rx.internal.operators.OperatorMerge$MergeSubscriber.checkTerminate(OperatorMerge.java:776)
at rx.internal.operators.OperatorMerge$MergeSubscriber.emitLoop(OperatorMerge.java:537)
at rx.internal.operators.OperatorMerge$MergeSubscriber.emit(OperatorMerge.java:526)
at rx.internal.operators.OperatorMerge$MergeSubscriber.onError(OperatorMerge.java:250)
at rx.internal.operators.OperatorMap$1.onError(OperatorMap.java:48)
at retrofit2.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:114)
at retrofit2.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:88)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable.unsafeSubscribe(Observable.java:8098)
at rx.internal.operators.OperatorSwitch$SwitchSubscriber.onNext(OperatorSwitch.java:105)
at rx.internal.operators.OperatorSwitch$SwitchSubscriber.onNext(OperatorSwitch.java:60)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54)
at rx.internal.operators.OperatorDoOnEach$1.onNext(OperatorDoOnEach.java:85)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54)
at rx.observers.SerializedObserver.onNext(SerializedObserver.java:95)
at rx.observers.SerializedSubscriber.onNext(SerializedSubscriber.java:95)
at rx.internal.operators.OperatorDebounceWithTime$DebounceState.emit(OperatorDebounceWithTime.java:132)
at rx.internal.operators.OperatorDebounceWithTime$1$1.call(OperatorDebounceWithTime.java:79)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.io.InterruptedIOException: thread interrupted
at okio.Timeout.throwIfReached(Timeout.java:145)
at okio.Okio$2.read(Okio.java:136)
at okio.AsyncTimeout$2.read(AsyncTimeout.java:211)
at okio.RealBufferedSource.indexOf(RealBufferedSource.java:306)
at okio.RealBufferedSource.indexOf(RealBufferedSource.java:300)
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:196)
at okhttp3.internal.http.Http1xStream.readResponse(Http1xStream.java:184)
at okhttp3.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:125)
at okhttp3.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:723)
at okhttp3.internal.http.HttpEngine.access$200(HttpEngine.java:81)
at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:708)
at okhttp3.internal.http.HttpEngine.readResponse(HttpEngine.java:563)
at okhttp3.RealCall.ge
I have searched a little, and it looks like I'm not alone: first and second.
Author of that first question solved this problem this by wrapping retrofit request with try-catch block.
For me it is attempt of covering bad architecture. And I'm looking for cleaner solution.
Is there a way of ignoring first API call result, and starting new one using RxJava? Or I should try to switch over new Retrofit Call API, and try to cancel previous request (and break a reactive approach)?
I have using Retrofit 2 beta 3, with newest Okio and OkHttp.
Well, the error is pretty explicit, you should add onError handling. It could look something like this:
.subscribe(new Observer<Response>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Response response) {
}
});
However, your subscription will be terminated once an error is emitted, but you can avoid this by handling the errors of the API call like this:
.switchMap(new Func1<String, Observable<Response>>() {
#Override public Observable<Response> call(String query) {
return retrofitService.search(query)
.onErrorResumeNext(Observable.<Response>empty());
}
})
I'm getting a list of installed apps on the device. It's a costly operation, so I'm using Rx for that:
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));
However, my problem is with handling errors.
Normally, user launches this screen, waits for apps to load, selects what is best and goes to next page. However, when user quickly changes the UI - app crashes with NullPointer.
Okay, so I implemented this onError. However it still doesn't work, and with above usecase it throws me this:
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)
How should I properly handle this problem?
.doOnError() is an operator, and is not as such a part of the Subscriber.
Therefore, having a .doOnError() does not count as an implemented onError().
About the question in one of the comments, of course it is possible to use lambdas.
In this case simply replace
.doOnError(throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
.subscribe(s -> createListView(s, view))
with
.subscribe(s -> createListView(s, view),
throwable -> L.e(TAG, "Throwable " + throwable.getMessage()))
My take is: you are probably using Action1 in
.subscribe(s -> createListView(s, view));
You will need to replace it with Subscriber or Observer which has abstract method onError. This method will be called from subscriber.onError(new Throwable());
EDIT: This is how I would do it. Upon closer look I think the main problem in your code is the early part where you call subscriber.onError even when there's no error. You probably don't need map either because you are technically passing data as-is without manipulation. But I left it in case it is needed later.
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);
}
});
Here is the newbie response (because I am new in javarx and finally fix this issue) :
Here is your implementation :
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();
In this previous implementation, when I subscribe I trigger the error flow ... and I get an application crash.
The problem is that you HAVE TO manage the error from the subscribe() call. The "doOnError(...)" is just a kind of helper that clone the error and give you a new place to do some action after an error. But it doesn't Handle the error.
So you have to change your code with that :
Observable.create(new Observable.OnSubscribe<RegionItem>() {
#Override
public void call(Subscriber<? super RegionItem> subscriber) {
subscriber.onError(new Exception("TADA !"));
}
})
.subscribe(actionNext, actionError, actionCompleted);
Not sure about the real explanation, but this is how I fix it. Hope it will help.