Woah! Very weird. I just updated my Retrofit type ...
# chucker
c
Woah! Very weird. I just updated my Retrofit type to
Response<MyResponseType>
instead of
Response<Void>
and now everything shows in chucker... going to dig a bit more because that does NOT seem right. Even with Response<Void> I should be getting a proper body showing in chucker. Edit: Yep. Changed back to
Response<Void>
and now chucker shows an empty response. This is not how Chuck used to work? Still investigating...
m
I don’t know about Chuck. I suspect it showed response in both cases. Which would be a bug. About Chucker - yes, and this is expected. Retrofit does not read
Response<Void>
or
Response<Unit>
at all and just closes the response body once the response is received. Chucker observes the network traffic that happens between your client and the server. It did prefetch network responses in the past (I think before
3.2.0
?) but it was the cause of the OOM issues and I would consider it also a UX bug. If you are not interested in the response neither is Chucker.
c
Interesting. Let me switch back to chuck really quick and see what happens. Is there any Type I can use in there to make sure that it actually shows in Chucker? I could have sworn this worked... but like I said, I'll revert and check it now.
Aha! Confirmed that if I switch back to Chuck this issue does not exist. It's only a Chucker issue. @Vova Buberenko let me know if you want me to still put together a sample that breaks. From what @MiSikora said... it sounds like this is expected? From a users perspective... this definitely doesn't make sense to me. I used to use Chuck in order to find the response my backend team sends me since they dont document anything (eye roll i know, i know) Seems like this is a legitimate use case though. Just because I return Unit doesn't mean that I just should see an empty response.
Also, I asked jake wharton in #squarelibraries about this whole Response<Unit> thing and asked if Retro does not read Response<Void> and he said
the response is still read. closing spends up to 100ms discarding the body before it simply abandons the connection
m
I might have misspoken. Retrofit does read the response but does not read response body for
Void
and
Unit
types. So what you experience with Chucker is 100% correct. If you (or more precisely Retrofit) do not read body bytes over the connection why Chucker should?
Unit
and
Void
are reserved in Retrofit for closing the response body stream without reading it https://github.com/square/retrofit/blob/65de04d1da61f9e8124956f6fff8902fc81d05e2/retrofit/src/main/java/retrofit2/BuiltInConverters.java#L66-L84. You can add a converter for
Any
that reads the bytes and represent your model with that type, since it would make more sense in context of
Retrofit
.
c
If you (or more precisely Retrofit) do not read body bytes over the connection why Chucker should?
I'm not sure I agree. I get your point, but hopefully you can see my point... from a network inspector perspective... it's "wrong" (IMO) for it to not show a response body. I'm still getting the response. Charles shows the response. Chuck shows the response. Chucker does not.
m
Yes, I understand what you’d like to happen. But you want to observe something that does not happen in your application. Charles is a proxy tool that hooks into HTTP traffic on a device and is outside of scope of your application. Chuck hooks into OkHttp with an interceptor and reads the whole body into memory even if you don’t do it (https://github.com/jgilfelt/chuck/blob/152f9a79f94ea23e9f0542137765961918909a76/library/src/main/java/com/readystatesoftware/chuck/ChuckInterceptor.java#L197-L198). This is really bad as it will lead to OOM issues and some others like pulling bytes from the connection when you don’t do it yourself (which was reported in the past). Chucker hooks into OkHttp as well and observers only what happens before it gets to intercept traffic. If you do not pull anything neither Chucker does. Chucker could do something similar to Stetho where it consumes the body as a stream regardless of what you consume but this changes behaviour of the app in debug and release modes. And as I mentioned reading of whole body was reported as a bug in Chucker. Chucker is first and foremost an OkHttp tool and OkHttp does not read the body on its own. At least that is the current state. I’m fine with it being different if maintainers want to change the behaviour.
c
I asked Jake again ME:
Retrofit does read the response but does not read response body
Is that correct? Just trying to figure out why having a return of Response<Unit> does not populate the response body in the network interceptor. Jake Wharton [JW]  [8:14 AM] It has no effect on the actual request or response
Unless I'm interpreting something incorrectly, or a miscommunication, or jake is wrong... it doesn't seem like Response<Unit> should change anything?
m
Well, yes. It has no effect on the request or response. It, however, does not read body and closes it. Once again, look at the source - https://github.com/square/retrofit/blob/65de04d1da61f9e8124956f6fff8902fc81d05e2/retrofit/src/main/java/retrofit2/BuiltInConverters.java#L66-L84
c
@MiSikora what do you think is the best way forward for something like this. I do think it's misleading for Response<Unit> to have an empty body in chucker. Maybe we should call this out in the readme or something
m
Perhaps. I’m not confused by it so I might not be the best judge of it.
Chucker works as an OkHttp Interceptor persisting all those events inside your application, and providing a UI for inspecting and sharing their content.
- for me it’s quite clear what is the expected output. If no bytes go through the interceptor then nothing will be observed. It rather comes down to the knowledge how Retrofit treats
Unit
and
Void
. Anyway, to solve your problem you can add a converter like this to your Retrofit instance. It will allow you to use
suspend fun foo(): Response<Any>
Copy code
object AnyReadingConverterFactory : Converter.Factory() {
  override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
    return if (type == Any::class.java) AnyConverter else null
  }

  private object AnyConverter : Converter<ResponseBody, Any> {
    private val any = Any()

    override fun convert(value: ResponseBody): Any? {
      value.source().use { it.readAll(Okio.blackhole()) }
      return any
    }
  }
}