I'm having problems dealing with ConnectableFlowable and Threads:
My retrofit service returns a ConnectableFlowable:
service.searchBeers(config.getKey(), name)
.map(Mappers.SEARCH_MAPPER).publish();
In my Interactor Class I have subscribed to it to add data to a Firebase database and then I returned the ConnectableFlowable to presenter. I have called the methods subscribeOn, unsubscribeOn and observeOn passing IO thread, since the Firebase insertions don't need to execute in main thread.
#Override
public Flowable<LocalType<List<Beer>>> searchBeers(String query) {
ConnectableFlowable<LocalType<List<Beer>>> connectableFlowable = (ConnectableFlowable<LocalType<List<Beer>>>)
remote.search(query);
connectableFlowable.unsubscribeOn(schedulerProvider.io()).subscribeOn(schedulerProvider.io()).observeOn
(schedulerProvider.io()).subscribe(onNext,
onError);
return connectableFlowable;
}
In my Presenter I have subscribed to it to update the View, so I call observeOn and pass schedulerProvider.ui() (that is a wrapper to mainThread).
#Override
public void search(String query) {
ConnectableFlowable<LocalType<List<Beer>>> flowable = (ConnectableFlowable<LocalType<List<Beer>>>)
searchInteractor.searchBeers(query);
flowable.subscribeOn(schedulerProvider.io()).observeOn(schedulerProvider.ui()).unsubscribeOn
(schedulerProvider.io())
.subscribe(
beerListData -> searchView.showSearchResult(beerListData.getData()),
error -> searchView.showSearchError(error)
);
flowable.connect();
}
But when it calls flowable.connect it raises an NetworkOnMainThreadException:
E/SearchActivity: showSearchError: null
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1303)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:86)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:74)
at java.net.InetAddress.getAllByName(InetAddress.java:752)
at okhttp3.Dns$1.lookup(Dns.java:39)
at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:171)
at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.java:137)
at okhttp3.internal.connection.RouteSelector.next(RouteSelector.java:82)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:171)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
at okhttp3.RealCall.execute(RealCall.java:69)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:10910)
at io.reactivex.internal.operators.flowable.FlowableFromObservable.subscribeActual(FlowableFromObservable.java:29)
at io.reactivex.Flowable.subscribe(Flowable.java:12994)
at io.reactivex.internal.operators.flowable.FlowableOnBackpressureLatest.subscribeActual(FlowableOnBackpressureLatest.java:32)
at io.reactivex.Flowable.subscribe(Flowable.java:12994)
at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:38)
at io.reactivex.Flowable.subscribe(Flowable.java:12994)
at io.reactivex.internal.operators.flowable.FlowablePublish.connect(FlowablePublish.java:130)
at io.reactivex.flowables.ConnectableFlowable.connect(ConnectableFlowable.java:64)
at com.github.alexpfx.udacity.beercollection.beer.search.DefaultSearchPresenter.search(DefaultSearchPresenter.java:50)
at com.github.alexpfx.udacity.beercollection.SearchActivity.actionSearch(SearchActivity.java:63)
at com.github.alexpfx.udacity.beercollection.SearchActivity_ViewBinding$1.doClick(SearchActivity_ViewBinding.java:35)
at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
at android.view.View.performClick(View.java:6261)
at android.view.View$PerformClick.run(View.java:23748)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)
I don't realize what's going wrong, since the subscribe is not executed in main thread.
-- My SchedulerProvider class:
#Singleton
public class AndroidSchedulerProvider implements SchedulerProvider {
#Inject
public AndroidSchedulerProvider() {
}
#Override
public Scheduler computation() {
return Schedulers.computation();
}
#Override
public Scheduler io() {
return Schedulers.io();
}
#Override
public Scheduler ui() {
return AndroidSchedulers.mainThread();
}
}
You should put the .subscribeOn(schedulerProvider.io()) before the publish(), so the underlying service.searchBeers Flowable will subscribe on io thread, when you put the subscribeOn after publish it will affect the subscription to the ConnectableFlowable itself only.
service.searchBeers(config.getKey(), name)
.map(Mappers.SEARCH_MAPPER)
.subscribeOn(schedulerProvider.io())
.publish();
Related
I'm currently using Retrofit 2.3 and RxAndroid for Android as my network communications. Its working fine most of the time. But sometimes, I get a SocketTimeOut exception (I'm assuming due to issues with the internet). I want to be able to handle this case but, putting a try catch around my retrofit calls in my activity doesn't catch this. Likewise, it doesn't go to the OnError method either (I don't see an option for an OnFailure method). The exception, instead, is shown in my RetrofitHelper class, at the return statement of the intercept method. Here is my Retrofit helper class:
public class RetrofitHelper {
/**
* The APICalls communicates with the json api of the API provider.
*/
public APICalls getAPICalls() {
final Retrofit retrofit = createRetrofit();
return retrofit.create(APICalls.class);
}
/**
* This custom client will append the "username=demo" query after every request.
*/
private OkHttpClient createOkHttpClient() {
final OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
final Request original = chain.request();
final HttpUrl originalHttpUrl = original.url();
final HttpUrl url = originalHttpUrl.newBuilder()
.build();
// Request customization: add request headers
final Request.Builder requestBuilder = original.newBuilder()
.url(url);
final Request request = requestBuilder.build();
return chain.proceed(request);
}
});
return httpClient.build();
}
/**
* Creates a pre configured Retrofit instance
*/
private Retrofit createRetrofit() {
return new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) // Library for parsing json responses
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // Library for easier threading/background processing
.client(createOkHttpClient())
.build();
}
}
And here is my interface for my API calls
public interface APICalls {
#GET("Vehicle/VehiclePositions.json")
Single<ResponseVehiclePosition> getVehiclePositions();
#GET("TripUpdate/TripUpdates.json")
Single<ResponseTripUpdate> getTripUpdates();
}
And here is the log:
2020-06-05 15:43:34.877 10007-10007/com.samramakrishnan.campusbustracker E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.samramakrishnan.campusbustracker, PID: 10007
java.net.SocketTimeoutException: failed to connect to transitdata.cityofmadison.com/204.147.0.120 (port 80) from /10.0.2.16 (port 35902) after 10000ms
at libcore.io.IoBridge.connectErrno(IoBridge.java:191)
at libcore.io.IoBridge.connect(IoBridge.java:135)
at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:142)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:390)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
at java.net.Socket.connect(Socket.java:621)
at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:63)
at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:223)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:149)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:192)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at com.samramakrishnan.campusbustracker.restapi.RetrofitHelper$1.intercept(RetrofitHelper.java:55)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
at okhttp3.RealCall.execute(RealCall.java:69)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
at io.reactivex.Observable.subscribe(Observable.java:10179)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:10179)
at io.reactivex.internal.operators.observable.ObservableSingleSingle.subscribeActual(ObservableSingleSingle.java:35)
at io.reactivex.Single.subscribe(Single.java:2558)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.Scheduler$1.run(Scheduler.java:134)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
I think you are able to catch the exception by the BiConsumer in the subscribe method when you call do the GET call.
According to the source file:
public final Disposable subscribe(final Consumer<? super T> onSuccess, final Consumer<? super Throwable> onError)
so I think you can do something like:
compositeDisopsable.add(getAPICalls().getVehiclePositions()
.subscribeOn(...)
...
.subscribe( response -> {
//do what you want to do with the `response`
}, throwable -> {
if(throwable instanceOf SocketTimeoutException){
//handle your exception
});
I am getting this random exception that crashes my app while making network calls with retrofit and am looking for some guidance for resolution
Fatal Exception: java.io.InterruptedIOException: thread interrupted
at okio.Timeout.throwIfReached(Timeout.java:145)
at okio.Okio$1.write(Okio.java:76)
at okio.AsyncTimeout$1.write(AsyncTimeout.java:180)
at okio.RealBufferedSink.flush(RealBufferedSink.java:216)
at okhttp3.internal.http2.Http2Writer.flush(Http2Writer.java:121)
at okhttp3.internal.http2.Http2Connection.newStream(Http2Connection.java:239)
at okhttp3.internal.http2.Http2Connection.newStream(Http2Connection.java:205)
at okhttp3.internal.http2.Http2Codec.writeRequestHeaders(Http2Codec.java:111)
at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:50)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.happycorp.happy.happyapp.util.Network$1.intercept(Network.java:80)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.happycorp.android.commondata.net.RetrofitFactory$CustomHttpMetricsLogger.intercept(RetrofitFactory.java:139)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:125)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.happycorp.android.commondata.net.RetrofitFactory$1.intercept(RetrofitFactory.java:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:212)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200)
at okhttp3.RealCall.execute(RealCall.java:77)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
at io.reactivex.Observable.subscribe(Observable.java:10179)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:10179)
at io.reactivex.internal.operators.observable.ObservableSingleSingle.subscribeActual(ObservableSingleSingle.java:35)
at io.reactivex.Single.subscribe(Single.java:2558)
at io.reactivex.internal.operators.single.SingleToFlowable.subscribeActual(SingleToFlowable.java:37)
at io.reactivex.Flowable.subscribe(Flowable.java:12218)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onNext(FlowableFlatMap.java:156)
at io.reactivex.internal.operators.flowable.FlowableFromIterable$IteratorSubscription.slowPath(FlowableFromIterable.java:238)
at io.reactivex.internal.operators.flowable.FlowableFromIterable$BaseRangeSubscription.request(FlowableFromIterable.java:123)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onSubscribe(FlowableFlatMap.java:110)
at io.reactivex.internal.operators.flowable.FlowableFromIterable.subscribe(FlowableFromIterable.java:68)
at io.reactivex.internal.operators.flowable.FlowableFromIterable.subscribeActual(FlowableFromIterable.java:46)
at io.reactivex.Flowable.subscribe(Flowable.java:12218)
at io.reactivex.internal.operators.flowable.FlowableFlatMap.subscribeActual(FlowableFlatMap.java:52)
at io.reactivex.Flowable.subscribe(Flowable.java:12218)
at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is what the service interface looks like:
#GET("categories")
Single<Category> getCategories();
#GET("categories/{categoryId}")
Single<Category> getCategory(#Path("categoryId") Integer id);
#GET("categories")
Single<Category> getCategoriesByRootCategory(#Query("rootCategoryId") Integer id);
#GET("products")
Single<ProductSearchResult> getProductsBySingleFilter(#Query(SINGLE_FILTER_BASE + "[field]")
String searchCriteria,
#Query(SINGLE_FILTER_BASE + "[conditionType]")
String conditionType,
#Query(SINGLE_FILTER_BASE + "[value]") String value);
Here is the code that creates the okhttp client and the retrofit interface:
public static OkHttpClient getOkHttpClient(#Nullable final File parentCacheDir,
#NonNull final AuthProvider authProvider,
#NonNull final ApiEnvironment apiEnvironment,
#Nullable final Interceptor cacheInterceptor) {
Cache cache = null;
if (parentCacheDir != null) {
File responseCacheDirectory = new File(parentCacheDir, RESPONSE_CACHE_DIRECTORY);
cache = new Cache(responseCacheDirectory, CACHE_SIZE);
}
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(NETWORK_REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(NETWORK_REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(NETWORK_REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.cache(cache);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
builder.addInterceptor(loggingInterceptor);
builder.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request.Builder requestBuilder = chain.request().newBuilder();
requestBuilder.header(KEY_CONTENT_TYPE, APPLICATION_JSON);
requestBuilder = addAuthHeaders(requestBuilder, apiEnvironment);
if (apiEnvironment.getDefaultRequestParams() != null) {
requestBuilder = addDefaultParams(requestBuilder, apiEnvironment);
}
return chain.proceed(requestBuilder.build());
}
});
builder.addNetworkInterceptor(new CustomHttpMetricsLogger());
if (cacheInterceptor != null) {
builder.addNetworkInterceptor(cacheInterceptor);
}
return builder.build();
}
public static Retrofit newInstance(#NonNull final AuthProvider authProvider,
#NonNull final ApiEnvironment apiEnvironment,
#Nullable final File parentCacheDir,
#Nullable final Interceptor debugInterceptor) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(getGson()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(getOkHttpClient(parentCacheDir, authProvider, apiEnvironment, debugInterceptor))
.baseUrl(apiEnvironment.getBaseUrl())
.build();
}
And here is the code that is generating the exception:
public static Flowable<ProductSearchResult> getAllItemsForCategories(final HebMagentoApi magentoService,
List<Category> categories) {
return Flowable.fromIterable(categories).flatMap(category ->
magentoService.getProductsBySingleFilter(HebMagentoApi.MAGENTO_CATEGORY_ID,
HebMagentoApi.MAGENTO_FILTER_EQUAL,
String.valueOf(category.getId())).toFlowable());
}
I think this issue is related to this line in the adapter code:
https://github.com/square/retrofit/blob/master/retrofit-adapters/rxjava2/src/main/java/retrofit2/adapter/rxjava2/BodyObservable.java#L59
The issue was with how we are creating the RxJava2CallAdapterFactory. We were using Dagger 2 to create the okhttp client and as far as I know that graph was constructed on the main thread and so the rxjava2calladapterfactory would use the main thread by default if we were creating flowables / observables by hand using retrofit.
To fix this so that all of the calls to Retrofit using RxJava2 happen on a background thread we create the call adapter in the the following way:
RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io())
IMPORTANT: The other thing to check to keep your app from crashing is to make sure that you are defining onError functions for each of your RxJava operators: flatMap, map, etc
I am using Retrofit and RxAndroid to send GET request to server which is based on Django, and the server response with a JSON data.
The interesting thing i found is, with stetho, the last right brace of Json is lost. So i got this error msg:
W/System.err: java.io.EOFException: End of input at line 1 column 812 path $.user
W/System.err: at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1393)
W/System.err: at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:482)
W/System.err: at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:414)
W/System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:214)
W/System.err: at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
W/System.err: at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
W/System.err: at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
W/System.err: at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
W/System.err: at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
W/System.err: at rx.Subscriber.setProducer(Subscriber.java:211)
W/System.err: at rx.internal.operators.OnSubscribeMap$MapSubscriber.setProducer(OnSubscribeMap.java:102)
W/System.err: at rx.Subscriber.setProducer(Subscriber.java:205)
W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
W/System.err: at rx.Observable.unsafeSubscribe(Observable.java:10142)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:48)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:33)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
W/System.err: at rx.Observable.unsafeSubscribe(Observable.java:10142)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:48)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:33)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
W/System.err: at rx.Observable.unsafeSubscribe(Observable.java:10142)
W/System.err: at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
W/System.err: at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
W/System.err: at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
W/System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:266)
W/System.err: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
W/System.err: at java.lang.Thread.run(Thread.java:764)
Here is the json data from stetho:
{"id":47,"credit_card":"","order_ordereditem_set":[{"id":36,"product_item":{"id":3,"category":{"id":1,"name":"Fruit"},"added_time":"2018-02-11 15:23:21","upc":"4321","desc":"Mandarin","price":150,"img_url":"http://15.32.134.74:8000/media/images/products/mandarin.jpg","not_sale":false,"is_hot":false,"is_recommended":false},"added_time":"2018-02-12 14:02:11","quantity":1,"order_id":47},{"id":37,"product_item":{"id":2,"category":{"id":1,"name":"Fruit"},"added_time":"2018-02-08 13:29:07","upc":"123456","desc":"Kiwi","price":500,"img_url":"http://15.32.134.74:8000/media/images/products/kiwi.jpg","not_sale":false,"is_hot":true,"is_recommended":false},"added_time":"2018-02-12 14:02:11","quantity":1,"order_id":47}],"added_time":"2018-02-12 14:02:11","payment":"0","total_quantity":2,"total_price":6.5,"user":1
But i checked wireshark, there is no missing of right brace.
I also used postman, but there is no problem.
This issue comes out 30%.
Following is my code details:
private HttpUtil() {
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(getOkHttpClient())
.baseUrl(SERVER_BASE_URL)
.build();
mService = retrofit.create(RestAPIService.class);
}
static public HttpUtil getHttpUtilInstance() {
if (mHttpUtil == null) {
synchronized (HttpUtil.class) {
mHttpUtil = new HttpUtil();
}
}
return mHttpUtil;
}
public RestAPIService getService() {
return mService;
}
private OkHttpClient getOkHttpClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
if (GlobalConfig.CONFIG_DEBUG)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
else
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
OkHttpClient.Builder httpClientBuilder = new OkHttpClient
.Builder();
httpClientBuilder.addInterceptor(loggingInterceptor)
.addInterceptor(mTokenInterceptor)
.addNetworkInterceptor(new StethoInterceptor());
//httpClientBuilder.addNetworkInterceptor(loggingInterceptor);
return httpClientBuilder.build();
}
private boolean shouldAddToken(String url) {
return !url.contains("api/login");
}
private boolean alreadyHasAuthorizationHeader(Request request) {
return request.header("Authorization") != null;
}
// Interceptor used for inserting token into Header
private Interceptor mTokenInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request;
Request originalRequest = chain.request();
//request = originalRequest.newBuilder().addHeader("Connection", "close").build();
request = originalRequest;
String token = UserDataRepository.getInstance().getToken();
if (token == null || token.length() == 0 || alreadyHasAuthorizationHeader(originalRequest)) {
return chain.proceed(originalRequest);
}
if (shouldAddToken(originalRequest.url().toString())) {
request = request.newBuilder()
.header("Authorization", "JWT " + token)
.build();
}
return chain.proceed(request);
}
};
#Headers({"Content-Type: application/json", "Accept: application/json"})
#GET("api/order")
Observable<List<Order>> getOrder();
it's a bit late for the answer, but if someone has the same problem, I'll tell you how I solved it.
I was having the same error, the last character of the response was missing, but when testing the api with postman everything was fine, so I try the same code with other apis and it works.
The problem is the server, I was using laravel server so I installed and configured apache and the problem is solved.
I have JSON wrapped by "_embedded" tag like below:
{
"_embedded" : {
"events" : [ { ... }]}
I'm using retrofit with interface:
public interface IEventRest {
#GET("/events/search/findByPlaceId")
Observable<List<Event>> getEventList(#Query("placeId")String placeId);
}
And this is my REST class:
public class EventRest implements IEventRest {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Event[].class, new MyDeserializer())
.create();
private Retrofit getRetrofitClient() {
final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
final OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(UrlUtils.URL)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
#Override
public Observable<List<Event>> getEventList(String placeId) {
final IEventRest placeRest = getRetrofitClient().create(IEventRest.class);
return placeRest.getEventList(placeId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread());
}
private class MyDeserializer implements JsonDeserializer<List<Event>> {
#Override
public List<Event> deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
JsonElement content = je.getAsJsonObject().get("_embedded");
Type collectionType = new TypeToken<Collection<Event>>(){}.getType();
return new Gson().fromJson(content, collectionType);
}
}
}
But calling placeRest.getEventList(placeId) throws exception:
java.lang.IllegalStateException: Exception thrown on Scheduler.Worker thread. Add `onError` handling.
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:57)
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: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
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.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.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:351)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:211)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:9860)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:221)
Could you help me to find out what is wrong? Thank you in advance.
I have JSON wrapped by "_embedded" tag
And that is exactly the problem. Gson doesn't know that your data is wrapped, it only cares that at the string that it sees, it is an object.
Pulling the preview, from here... We get a total of three objects
Event.java
(This wasn't generated because you left out what the JSON looks like)
Embedded.java
import java.util.ArrayList;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Embedded {
#SerializedName("events")
#Expose
private List<Event> events = new ArrayList<Event>();
public Embedded() {
}
public Embedded(List<Event> events) {
this.events = events;
}
public List<Event> getEvents() {
return events;
}
public void setEvents(List<Event> events) {
this.events = events;
}
}
Response.java
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Response {
#SerializedName("_embedded")
#Expose
private Embedded embedded;
public Response() {
}
public Response(Embedded embedded) {
this.embedded = embedded;
}
public Embedded getEmbedded() {
return embedded;
}
public void setEmbedded(Embedded embedded) {
this.embedded = embedded;
}
}
And, so now, Retrofit cares about a Call<Response>, which you can then call .getEmbedded().getEvents()
Or, you could mess around with MyDeserializer more, since that seems to be the issue with the existing code.
When I use Retrofit with rxjava, some machine throwing OOM, this is log:
java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.Worker thread.
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:59)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:1063)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:920)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1338)
at okhttp3.ConnectionPool.put(ConnectionPool.java:135)
at okhttp3.OkHttpClient$1.put(OkHttpClient.java:149)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:188)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:129)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:98)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:109)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:124)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:145)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:170)
at okhttp3.RealCall.execute(RealCall.java:60)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at rx.Subscriber.setProducer(Subscriber.java:205)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:9861)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:221)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
my retrofit code:
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
if (BuildConfig.LOG_DEBUG)
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
else
interceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.retryOnConnectionFailure(true)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(ConstantValue.URL + "/")
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
and my request code:
public interface GetUserFriendUsersService {
#FormUrlEncoded
#POST("IM/GetUserFriendUsers")
Observable<ResponseBody> getUserFriendUsers(#Field("UserID")String UserID);}
public void getUserFriendUsers(String userID, Subscriber<List<Users>> subscriber) {
retrofit.create(GetUserFriendUsersService.class)
.getUserFriendUsers(userID)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.map(new HttpResultFunc())
.map(new Func1<JSONObject, List<Users>>() {
#Override
public List<Users> call(JSONObject jsonObject) {
List<Users> users = JSON.parseArray(jsonObject.getString("users"), Users.class);
if (users.size() == 0)
throw new CustomizeException("no data");
return users;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
The .observeOn() should be in front of map(),and in back of unsubscribeOn()
Posted the same question at RxAndroid github.
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.