Search code examples
androidkotlingsonretrofit2okhttp

OkHttpClient sometimes getting incomplete json response


I have been facing this incomplete json error and unable to find the issue. The API response work fine in POSTMAN. But this issue happened in my android emulator and it only happened randomly. This project is build with kotlin dagger-hilt retrofit2 okhttp3 gson.

Success Response

I/okhttp.OkHttpClient: <-- 200 OK http://10.0.2.2:8000/api/v1/user/friend/mutual?name=as (217ms)
I/okhttp.OkHttpClient: Host: 10.0.2.2:8000
I/okhttp.OkHttpClient: Date: Thu, 03 Mar 2022 06:38:59 GMT
I/okhttp.OkHttpClient: Connection: close
I/okhttp.OkHttpClient: X-Powered-By: PHP/7.4.13
I/okhttp.OkHttpClient: Cache-Control: no-cache, private
I/okhttp.OkHttpClient: Date: Thu, 03 Mar 2022 06:38:59 GMT
I/okhttp.OkHttpClient: Content-Type: application/json
I/okhttp.OkHttpClient: X-RateLimit-Limit: 60
I/okhttp.OkHttpClient: X-RateLimit-Remaining: 52
I/okhttp.OkHttpClient: Access-Control-Allow-Origin: *
I/okhttp.OkHttpClient: {"friends":[{"user_id":16,"name":"Grant Erdman","username":"lilla.kulas","email":"[email protected]","image":"https:\/\/via.placeholder.com\/640x480.png\/00dd44?text=dolor","relationship":"friend"}],"requests":[]}
I/okhttp.OkHttpClient: <-- END HTTP (217-byte body)

Incomplete Json Response missing "}"

I/okhttp.OkHttpClient: <-- 200 OK http://10.0.2.2:8000/api/v1/user/friend/mutual?name=as (198ms)
I/okhttp.OkHttpClient: Host: 10.0.2.2:8000
I/okhttp.OkHttpClient: Date: Thu, 03 Mar 2022 06:39:00 GMT
I/okhttp.OkHttpClient: Connection: close
I/okhttp.OkHttpClient: X-Powered-By: PHP/7.4.13
I/okhttp.OkHttpClient: Cache-Control: no-cache, private
I/okhttp.OkHttpClient: Date: Thu, 03 Mar 2022 06:39:00 GMT
I/okhttp.OkHttpClient: Content-Type: application/json
I/okhttp.OkHttpClient: X-RateLimit-Limit: 60
I/okhttp.OkHttpClient: X-RateLimit-Remaining: 51
I/okhttp.OkHttpClient: Access-Control-Allow-Origin: *
I/okhttp.OkHttpClient: {"friends":[{"user_id":16,"name":"Grant Erdman","username":"lilla.kulas","email":"[email protected]","image":"https:\/\/via.placeholder.com\/640x480.png\/00dd44?text=dolor","relationship":"friend"}],"requests":[]
I/okhttp.OkHttpClient: <-- END HTTP (216-byte body)
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.findplan, PID: 4695
    java.io.EOFException: End of input at line 1 column 217 path $.requests
        at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1395)
        at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:481)
        at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:413)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:216)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
        at com.example.findplan.utils.NullOnEmptyConverterFactory$1.convert(NullOnEmptyConverterFactory.java:20)
        at com.example.findplan.utils.NullOnEmptyConverterFactory$1.convert(NullOnEmptyConverterFactory.java:16)
        at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        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:923)
I/Process: Sending signal. PID: 4695 SIG: 9

My NetworkModule

@Singleton
    @Provides
    fun provideOkHttpClient(mySharedPreferences: MySharedPreferences): OkHttpClient {
        val builder = OkHttpClient
            .Builder()
            .connectTimeout(1, TimeUnit.MINUTES)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(15, TimeUnit.SECONDS)
        val authenticationInterceptor = AuthenticationInterceptor(mySharedPreferences)
        builder.addInterceptor(authenticationInterceptor)
        val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addNetworkInterceptor(interceptor)

        return builder.build()
    }

    @Singleton
    @Provides
    fun provideGson(): Gson {
        return GsonBuilder()
            .setLenient()
            .create()
    }

    @Singleton
    @Provides
    fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(BuildConfig.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(NullOnEmptyConverterFactory())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
    }

NullOnEmptyConverterFactory

public class NullOnEmptyConverterFactory extends Converter.Factory {

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
        return new Converter<ResponseBody, Object>() {
            @Override
            public Object convert(ResponseBody body) throws IOException {
                if (body.contentLength() == 0) return null;
                return delegate.convert(body);
            }
        };
    }
}

Solution

  • I suspect the Android emulator might be interfering with you here. I’ve seen issues with it misbehaving, particularly on Windows.

    https://issuetracker.google.com/issues/119027639

    If you'd like to workaround, consider changing your server to use something other than Connection: close to terminate your response body. Perhaps chunked encoding or a content-length header.