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?
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 trying to understand how the observer pattern works in Android.
I've created this method to load a sample list of object, pushing each items to the subscriber and loading it to into the recyclerview.
I don't understand why if i load 10 items everything is working fine, but if i load 100/1000 or in general more items, the recyclerView is empty and onNext, onComplete are not fired.
private Observable<AppInfo> getAppList() {
return Observable.create(new Observable.OnSubscribe<AppInfo>() {
#Override
public void call(Subscriber<? super AppInfo> subscriber) {
for (int i = 0; i<10; i++){
AppInfo appInfo = new AppInfo(
"Test item "+i,
ContextCompat.getDrawable(getApplicationContext(), R.mipmap.ic_launcher),
i
);
subscriber.onNext(appInfo);
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
});
}
And this is how i use the Observable:
Observable<AppInfo> appInfoObserver = getAppList();
appInfoObserver
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<AppInfo>() {
#Override
public void onCompleted() {
Toast.makeText(getApplicationContext(), "App List Load Completed!", Toast.LENGTH_LONG).show();
}
#Override
public void onError(Throwable e) {}
#Override
public void onNext(AppInfo appInfo) {
if(mAppInfoList != null){
mAppInfoList.add(appInfo);
adapter.notifyItemInserted(appInfo.getAppPosition());
}
}
});
Thanks for the help and advices.
You're not logging errors so if anything goes wrong you won't know (in this case you are probably forcing a MissingBackpressureException from the observeOn operator by sending it more than it requested). To be clear, in the subscriber:
public void onError(Throwable e) {
// log or display error here!!
}
Don't use Observable.create at all if you can help it because you need to honour backpressure or combine it with .onBackpressureBuffer.
The exception is that Observable.create(new SyncOnSubscribe<T>(...)) is a good way to create an Observable if you can imagine your source as an iterator/enumeration.
To avoid using Observable.create in your example you could do this:
Observable
.range(0, 10)
.map(i -> new AppInfo(...))
or without lambda:
Observable
.range(0, 10)
.map(new Func1<Integer, AppInfo>() {
#Override
public AppInfo call(Integer n) {
return new AppInfo(...);
}
});
Maybe your code is to heavy and its loading sync. Try to load your code inside a new thread, maybe you can use the observeOn() (i dont know exactally how rxjava works, but my guess is that this function defines the thread where the event occurs).
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());
}
})
Observable observable = Observable.from(backToArray(downloadWebPage("URL")))
.map(new Func1<String[], Pair<String[], String[]>>() {
#Override
public Pair<String[], String[]> call(String[] of) {
return new Pair<>(of,
backToArray(downloadWebPage("URL" + of[0])).get(0));
}
});
observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(
(new Observer<Pair>() {
#Override
public void onCompleted() {
// Update user interface if needed
}
#Override
public void onError(Throwable t) {
// Update user interface to handle error
}
#Override
public void onNext(Pair p) {
offices.add(new Office((String[]) p.first, (String[]) p.second));
}
}));
This runs and i get android.os.NetworkOnMainThreadException. I would expect it to run a new thread as set by the subscribeOn() method.
Assuming that the actual network request is happening in downloadWebPage(), the error is in the first line of your code:
Observable observable = Observable.from(backToArray(downloadWebPage("http://api.ataxcloudapp.com/v1/franchise/listing/?location=" + ZIPCode)))
This is equivalent to:
String[] response = downloadWebPage("http://api.ataxcloudapp.com/v1/franchise/listing/?location=" + ZIPCode)
Observable observable = Observable.from(backToArray(response))
This should make it clear that downloadWebPage is executed - on the main thread - before any Observable is even created, let alone subscribed to. RxJava cannot change the semantics of Java in this regard.
What you can do however is something like this (not tested, but should be about right):
Observable observable = Observable.create(new Observable.OnSubscribe<String[]>() {
#Override
public void call(final Subscriber<? super String[]> subscriber) {
final String[] response = downloadWebPage("http://api.ataxcloudapp.com/v1/franchise/listing/?location=" + ZIPCode);
if (! subscriber.isUnsubscribed()) {
subscriber.onNext(backToArray(response));
subscriber.onCompleted();
}
}
)
Now your network request will happen only after the Observable is subscribed to, and will be moved to a the thread you specify in subscribeOn().
You can use defer() to postpone the calling of downloadWebPage to the moment when you subscribe to the observable.
Example:
private Object slowBlockingMethod() { ... }
public Observable<Object> newMethod() {
return Observable.defer(() -> Observable.just(slowBlockingMethod()));
}
Source
You should change from
**observable.subscribeOn(Schedulers.newThread())**
to
**observable.subscribeOn(Schedulers.io())**
I have an API interface and I'm testing a View that involves network calls.
#Config(emulateSdk = 18)
public class SampleViewTest extends RobolectricTestBase {
ServiceApi apiMock;
#Inject
SampleView fixture;
#Override
public void setUp() {
super.setUp(); //injection is performed in super
apiMock = mock(ServiceApi.class);
fixture = new SampleView(activity);
fixture.setApi(apiMock);
}
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}
}
For some reason apiMock methods are never called and verification always fails.
In my view I'm calling my api like this
apiV2.requestA()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer());
What am I missing here?
Update #1:
After some investigation it appears that when in my implementation (sample above) I observeOn(AndroidSchedulers.mainThread()) subscriber is not called. Still do not know why.
Update #2:
When subscribing just like that apiV2.requestA().subscribe(new Observer()); everything works just fine - mock api is called and test passes.
Advancing ShadowLooper.idleMainLooper(5000) did nothing. Even grabbed looper from handler in HandlerThreadScheduler and advanced it. Same result.
Update #3:
Adding actual code where API is used.
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
I'm still wrapping my head around how to properly use rxjava myself but I would try to modify your code so that you only observeOn(mainThread) on the final zipped Observable instead of doing it on both of the original request response's Observable. I would then verify if this affect the fact that you have to advance both Loopers or not.
To simply your tests and remove the need for Looper idling I would take the threading out of the equation since you don't need background processing when running tests. You can do that by having your Schedulers injected instead of creating them statically. When running your production code you'd have the AndroidSchedulers.mainThread and Schedulers.io injected and when running tests code you would inject Schedulers.immediate where applicable.
#Inject
#UIScheduler /* Inject Schedulers.immediate for tests and AndroidSchedulers.mainThread for production code */
private Scheduler mainThreadSched;
#Inject
#IOScheduler /* Inject Scheduler.immediate for tests and Schedulers.io for production code */
private Scheduler ioSched;
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).observeOn(mainThreadSched)
.subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(ioSched);
}
what version of rxjava are you using? I know there was some changes in the 0.18.* version regarding the ExecutorScheduler. I had a similar issue as you when using 0.18.3 where I wouldn't get the onComplete message because my subscription would be unsubscribe ahead of time. The only reason I'm mentioning this to you is that a fix in 0.19.0 fixed my issue.
Unfortunately I can't really explain the details of what was fixed, it's beyond my understanding at this point but if it turns out to be the same cause maybe someone with more understand could explain. Here's the link of what I'm talking about https://github.com/Netflix/RxJava/issues/1219.
This isn't much of an answer but more a heads up in case it could help you.
As #champ016 stated there were issues with RxJava versions that are lower than 0.19.0.
When using 0.19.0 the following approach works. Although still don't quite get why I have to advance BOTH loopers.
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
ShadowLooper.idleMainLooper(5000);
Robolectric.shadowOf(
Reflection.field("handler")
.ofType(Handler.class)
.in(AndroidSchedulers.mainThread())
.get().getLooper())
.idle(5000);
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}