skydoves / sandwich

🥪 Sandwich is an adaptable and lightweight sealed API library designed for handling API responses and exceptions in Kotlin for Retrofit, Ktor, and Kotlin Multiplatform.
https://skydoves.github.io/sandwich/
Apache License 2.0
1.57k stars 101 forks source link

NullPointer on iOS when receiving empty Response? #394

Open anox1337 opened 1 month ago

anox1337 commented 1 month ago

Please complete the following information:

Describe the Bug: NullPointerException gets thrown on iOS but not on Android.

Hello!

I call an Endpoint where I may get an ErrorResponse, but not always. Looks like this:

    @PATCH("api/stop")
    @Headers(
        "Accept: application/json",
        "Content-Type: application/json",
    )
    suspend fun stopTask(
        @Body task: Task,
    ): ApiResponse<ErrorResponse?>

See the ErrorResponse is optional.

When calling the Endpoint:

 taskApi.stopTask(task).getOrThrow()?.let {
   // ErrorResponse Handling
}
>>>RESPONSE: 204 No Content
METHOD: HttpMethod(value=PATCH)
FROM: 
COMMON HEADERS
-> Access-Control-Allow-Credentials: true
-> Access-Control-Allow-Headers: Content-Type, Authorization, Content-Length, X-Requested-With
-> Access-Control-Allow-Methods: POST, PUT, GET, DELETE, HEAD, PATCH
-> Access-Control-Max-Age: 3600
-> Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-> Connection: keep-alive
-> Content-Security-Policy: default-src 'none';
-> Content-Type: application/json
-> Date: Tue, 22 Oct 2024 13:17:23 GMT
-> Expires: 0
-> Pragma: no-cache
-> Server: nginx/1.18.0 (Ubuntu)
-> Strict-Transport-Security: max-age=31536000; includeSubDomains
-> Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
-> X-Content-Type-Options: nosniff, nosniff
-> X-Frame-Options: DENY, deny
-> X-XSS-Protection: 0, 1; mode=block;
BODY Content-Type: application/json
BODY START

BODY END

It throws the Nullpointer:

0   shared                              0x109193ef7        kfun:kotlin.Throwable#<init>(){} + 95 
1   shared                              0x10918d3a7        kfun:kotlin.Exception#<init>(){} + 87 
2   shared                              0x10918d5c7        kfun:kotlin.RuntimeException#<init>(){} + 87 
3   shared                              0x10918d7e7        kfun:kotlin.NullPointerException#<init>(){} + 87 
4   shared                              0x1091ca067        ThrowNullPointerException + 131 
5   shared                              0x10a549223        kfun:com.skydoves.sandwich.ktorfit.ApiResponseConverterFactory.object-1.$convertCOROUTINE$0.invokeSuspend#internal + 887 
6   shared                              0x10a549453        kfun:com.skydoves.sandwich.ktorfit.ApiResponseConverterFactory.object-1.convert#internal + 295 
7   shared                              0x10a52ba97 

What I would expect is that getOrThrow would return a NULL object, signaling me that the Api Call was successful but no Response object was available. This is exactly how it works on Android.

Am I missing something?

Thank you for your help :)

skydoves commented 1 month ago

Hey @anox1337, getOrThrow always throws an exception if the respond is not the properly backed. You can use getOrNull instead in this situation.

anox1337 commented 1 month ago

Hello @skydoves ,

in my opinion the response was properly defined.

In that call I am expecting ApiResponse<ErrorResponse?> . The Questionmark indicates that the returntype may or may not be filled. As I received a successful response (code 204) with no body, I would not expect an exception to be thrown. And this behaves as expected on android. But on iPhones this seems to work differently which has me stomped.

What would you suggest I should use to define a response that has an optional body?