You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retrofit 2如何处理登录请求中时而返回空响应体、时而返回非空响应体的场景

Hey there, let's tackle this tricky Retrofit scenario you're facing! I've run into similar issues before, so here are a few solid solutions that should work for you:

Solution 1: Use ResponseBody as the Return Type (Simplest Approach)

This is the most straightforward fix for your specific login endpoint. By using ResponseBody, you can handle both empty success responses and error responses with custom parsing:

First, update your API interface:

interface LoginAPIService {
    @POST("login")
    fun login(@Body body: RequestBody): Call<ResponseBody>
}

Then, handle the response in your callback:

loginApiService.login(yourRequestBody).enqueue(object : Callback<ResponseBody> {
    override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
        when {
            response.isSuccessful -> {
                // Success case: empty response body, proceed with login success logic
                showLoginSuccess()
            }
            else -> {
                // Failure case: parse the error message from error body
                response.errorBody()?.let { errorBody ->
                    try {
                        // Use your JSON parser (Gson/Moshi/Jackson) to deserialize the error
                        val errorResponse = Gson().fromJson(
                            errorBody.string(),
                            ErrorResponse::class.java
                        )
                        showLoginError(errorResponse.message)
                    } catch (e: Exception) {
                        // Fallback if parsing fails
                        showLoginError("Unknown error occurred")
                    }
                } ?: showLoginError("No error message available")
            }
        }
    }

    override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
        // Handle network errors (e.g., no internet, timeout)
        showLoginError(t.message ?: "Network error")
    }
})

// Define a data class to map the error response
data class ErrorResponse(val message: String)

Pros: No Retrofit configuration changes needed, perfect for one-off endpoints like login.
Cons: Requires manual parsing of the error body each time you use this pattern.

Solution 2: Create a Custom Converter Factory (Global Approach)

If you have multiple endpoints with similar behavior (empty success, error with message), a custom converter will let you handle this uniformly across your app.

First, define a sealed class to represent your API responses:

sealed class ApiResult {
    object Success : ApiResult()
    data class Error(val message: String) : ApiResult()
}

Then, create a custom converter factory that handles empty success bodies and error parsing:

class EmptyBodyConverterFactory(private val gson: Gson) : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        // Only apply this converter if the target type is ApiResult
        return if (type == ApiResult::class.java) {
            Converter<ResponseBody, ApiResult> { body ->
                if (body.contentLength() == 0L) {
                    ApiResult.Success
                } else {
                    // This should only happen for error responses, but add a fallback
                    try {
                        val errorResponse = gson.fromJson(body.string(), ErrorResponse::class.java)
                        ApiResult.Error(errorResponse.message)
                    } catch (e: Exception) {
                        ApiResult.Error("Unknown error")
                    }
                }
            }
        } else {
            // Delegate to other converters for other types
            super.responseBodyConverter(type, annotations, retrofit)
        }
    }
}

Add this converter to your Retrofit instance:

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(EmptyBodyConverterFactory(Gson()))
    .addConverterFactory(GsonConverterFactory.create())
    .build()

Update your API interface to use ApiResult:

interface LoginAPIService {
    @POST("login")
    fun login(@Body body: RequestBody): Call<ApiResult>
}

Now handling the response becomes cleaner:

loginApiService.login(yourRequestBody).enqueue(object : Callback<ApiResult> {
    override fun onResponse(call: Call<ApiResult>, response: Response<ApiResult>) {
        when (val result = response.body()) {
            is ApiResult.Success -> showLoginSuccess()
            is ApiResult.Error -> showLoginError(result.message)
            null -> {
                // Handle cases where response body is null but request wasn't successful
                response.errorBody()?.let {
                    val errorMsg = Gson().fromJson(it.string(), ErrorResponse::class.java).message
                    showLoginError(errorMsg)
                } ?: showLoginError("Unknown error")
            }
        }
    }

    override fun onFailure(call: Call<ApiResult>, t: Throwable) {
        showLoginError(t.message ?: "Network error")
    }
})

Pros: Uniform handling across all endpoints with this pattern, less repetitive code.
Cons: Requires setting up a custom converter, which adds a bit of initial overhead.

Bonus Tip: Use Kotlin Coroutines (If You're Using Them)

If you're using Retrofit with Kotlin coroutines, you can simplify the handling even more:

interface LoginAPIService {
    @POST("login")
    suspend fun login(@Body body: RequestBody): Response<ResponseBody>
}

// In your repository/viewmodel
try {
    val response = loginApiService.login(yourRequestBody)
    if (response.isSuccessful) {
        showLoginSuccess()
    } else {
        response.errorBody()?.let {
            val errorMsg = Gson().fromJson(it.string(), ErrorResponse::class.java).message
            showLoginError(errorMsg)
        } ?: showLoginError("Unknown error")
    }
} catch (e: Exception) {
    showLoginError(e.message ?: "Network error")
}

This avoids callback boilerplate and makes the code more readable.


内容的提问来源于stack exchange,提问作者Alex Petev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:12:29