I am using the following method to handle my requests
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
.let { originalResponse ->
Log.i("AMIRA999", "code : " + originalResponse.code())
when (originalResponse.code()) {
200 -> {
Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
originalResponse
}
401, 404 -> {
Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
originalResponse
/*return originalResponse.mapToBody(
originalResponse.body()?.contentType(),
getErrorResponse(originalResponse)
)*/
}
else -> {
Log.i("AMIRA999", "body : " + originalResponse.body().toString())
throw BadRequestException()
}
}
}
the method work perfect when the code is 200, but it crash if the code is 404 or 401
what I need to keep returning the json comes from server and does not crash to be able to handle it with error message
how can I do that ?
the crash that I got is the following
retrofit2.HttpException: HTTP 401 UNAUTHORIZED
at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
You use retrofit2-kotlin-coroutines-adapter and the exception throwing is by design. Any non-2xx HTTP response such as 401 will throw an exception. You can see this for yourself in the library source code
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
But this is not a problem. You can still access the response and your JSON by doing catch (e: HttpException) and then calling val yourJson = e.response()?.body() as? YourJson.
Note that retrofit2-kotlin-coroutines-adapter is deprecated and that you should migrate to Retrofit 2.6.0 or newer. Then you can prefix your Retrofit interface functions with suspend so you can write nice idiomatic Kotlin code.
Retrofit 2 has a different concept of handling "successful" requests than Retrofit 1. In Retrofit 2, all requests that can be executed (sent to the API) and for which you’re receiving a response are seen as "successful". That means, for these requests the onResponse callback is fired and you need to manually check whether the request is actually successful (status 200-299) or erroneous (status 400-599).
If the request finished successfully, we can use the response object and do whatever we wanted. In case the error actually failed (remember, status 400-599), we want to show the user appropriate information about the issue.
Example
Error Object
Let’s assume your API sends a JSON error body like this:
{
statusCode: 409,
message: "Email address already registered"
}
Note: you can see your JSON error body by printing response.errorBody()
To avoid these bad user experiences, we’re mapping the response body to a Java object, represented by the following class.
class APIError {
private val statusCode:Int = 0
private val message:String
fun status():Int {
return statusCode
}
fun message():String {
return message
}
}
Error Handler
object ErrorUtils {
fun parseError(response:Response<*>):APIError {
val converter = ServiceGenerator.retrofit()
.responseBodyConverter(APIError::class.java, arrayOfNulls<Annotation>(0))
val error:APIError
try
{
error = converter.convert(response.errorBody())
}
catch (e:IOException) {
return APIError()
}
return error
}
}
Error Handler in Action
Now you can handle error in API response using ErrorUtils like the following.
val call = service.me()
call.enqueue(object:Callback<User>() {
fun onResponse(call:Call<User>, response:Response<User>) {
if (response.isSuccessful())
{
// use response data and do some fancy stuff :)
}
else
{
// parse the response body …
val error = ErrorUtils.parseError(response)
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message())
}
}
fun onFailure(call:Call<User>, t:Throwable) {
// there is more than just a failing request (like: no internet connection)
}
})
The complete example with a video is here retrofit-2-error-handling.
Related
I am sending a post request to an API using retrofit 2. I am using a deferred object as the return type of the function
#POST("/some_path/")
fun sendPostReqAsync(#Body body: PostRequestBody): Deferred<PostReqResponse>
Here PostReqResponse is a DataClass and I am using Moshi as the convertor factory.
When the req is successful then I am getting the required result. When there are errors on the API side it sends errors in response like "invalid argument" or "user does not exist".
{"non_field_errors":["User does not exist."]}
But by using try-catch I just get "retrofit2.HttpException: HTTP 400 Bad Request."
uiScope.launch {
withContext(Dispatchers.IO) {
try {
val response = onboardingApi.sendPostReqAsync(body).await()
Log.i("api_data",response.toString())
} catch (e: Exception) {
val error = e.toString()
Log.i("api_data",error)
}
}
}
But I want the error string returned by the API.
Can someone please tell me how to get that error string?
I was facing same issue, i solved it by parsing the Json that obtained from Exception.
Here the solution that might be solve your problem
uiScope.launch {
withContext(Dispatchers.IO) {
try {
val response = onboardingApi.sendPostReqAsync(body).await()
Log.i("api_data",response.toString())
} catch (e: Exception) {
if (e is HttpException) {
val jsonObj = JSONObject(e.response()?.errorBody()!!.charStream().readText())
val error = jsonObj.getJSONArray("non_field_errors").getJSONObject(0).toString()
Log.i("api_data",error)
}
}
}
}
I'm using Retrofit2 in an Android app. Here's my response code:
override fun onResponse(call: Call<Asset>?, response: Response<Asset>?) {
val errorStart = response?.errorBody()?.string() // I get "ERROR" here
if (response != null && response.isSuccessful) {
// A successful response was returned
completion(response.body(), null)
} else {
// Not success
val errorElse = response?.errorBody()?.string() // I get "" here
val error = Error(errorText)
completion(null,error)
}
}
and here's the code I'm using to test my code:
response = Response.Builder()
.code(403)
.message(responseString)
.request(chain.request())
.protocol(Protocol.HTTP_1_0)
.body(ResponseBody.create(MediaType.parse("application/json"), "ERROR"))
.addHeader("content-type", "application/json")
.build()
When the code run and a response is returned response?.errorBody()?.string() is a string and I can capture it, as I have in this code as val errorStart.
...But...
when I run the IF code/logic and attempt to capture response?.errorBody()?.string() in the else code block it's now blank/gone.
Can someone explain what's going on or what I'm doing wrong? What is the proper way to capture error information in OnResponse?
First of all the errorBody() is of type stream, meaning that it is not read until you call the method. Streams can be read only once, take a look here for a detailed explanation.
I am using Retrofit2 with RxJava. So my call looks something like
subscriptions.add(authenticateUser(mReq, refreshRequest)
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.subscribe(authResponseModel -> {
processResponse(authResponseModel, userName, encryptedPass);
}, throwable ->
{
LOGW(TAG, throwable.getMessage());
}));
It's an authentication api. So when the api call fails, I get a response from the server like
{"messages":["Invalid username or password "]}
along with 400 Bad Request
I get 400 Bad Request in the throwable object as expected. But I want to receive the message thrown by the server. I am unable to figure out how to go about doing it.
Can someone help out.
if(throwable instanceof HttpException) {
//we have a HTTP exception (HTTP status code is not 200-300)
Converter<ResponseBody, Error> errorConverter =
retrofit.responseBodyConverter(Error.class, new Annotation[0]);
//maybe check if ((HttpException) throwable).code() == 400 ??
Error error = errorConverter.convert(((HttpException) throwable).response().errorBody());
}
Assuming you are using Gson:
public class Error {
public List<String> messages;
}
the content of messages should be a list of error messages. In your example
messages.get(0) would be: Invalid username or password
I'm using Retrofit to make a POST Request in my web server.
However, I can't seem to get the response body when the response status is 422 (unprocessable entity). The response body is always null.
I want to know if I'm doing something wrong or if there's a workaround for this. Because I'm using the same json in the request with Postman, and it returns the body normally.
This is the method:
#Headers("Content-Type: application/vnd.api+json")
#POST("my_endpoint")
Call<JsonObject> postEntry(#Header("Authorization") String authorization, #Body JsonObject json);
The body is a JsonObject, I'm not serializing like the documentation say. But I don't think this is the problem.
By default, when your server is returning an error code response.body() is always null. What you are looking for is response.errorBody(). A common approach would be something like this:
#Override
public void onResponse(Response<JsonObject> response, Retrofit retrofit) {
if (response.isSuccess()) {
response.body(); // do something with this
} else {
response.errorBody(); // do something with that
}
}
If you need something advanced take a look at Interceptors and how to use them
I got the same error. My API was working using POSTMAN request but not working from Android retrofit call.
At first I was trying using #Field but it was getting error but later I've tried with #Body and it worked.
Sample Retrofit interface call
#POST("api/v1/app/instance")
Call<InstanceResponse> updateTokenValue(
#HeaderMap Map<String, String> headers,
#Body String body);
and API calling code is:
Map<String, String> headerMap=new HashMap<>();
headerMap.put("Accept", "application/json");
headerMap.put("Content-Type", "application/json");
headerMap.put("X-Authorization","access_token");
Map<String, String> fields = new HashMap<>();
fields.put("app_name", "video");
fields.put("app_version", "2.0.0");
fields.put("firebase_token", "token");
fields.put("primary", "1");
ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
Call<InstanceResponse> call = apiInterface.updateTokenValue(
headerMap,new Gson().toJson(fields));
Well in this case you'll have to convert the response.
Have a look at this link
All the steps are already provided in the link above.
For Kotlin users here is the code solution.
ErrorResponse.kt (This obviously depends on your error response)
import com.squareup.moshi.Json
data class ErrorResponse(
#Json(name="name")
val name: String? = null,
#Json(name="message")
val message: String? = null,
#Json(name="errors")
val errors: Errors? = null,
#Json(name="statusCode")
val statusCode: Int? = null
)
ApiFactory.kt (Let me know if you need the entire code)
fun parseError(response: Response<*>): ErrorResponse {
val converter = ApiFactory.retrofit()
.responseBodyConverter<ErrorResponse>(
ErrorResponse::class.java, arrayOfNulls<Annotation>(0)
)
val error: ErrorResponse
try {
error = converter.convert(response.errorBody()!!)!!
} catch (e: IOException) {
e.printStackTrace()
return ErrorResponse()
}
return error
}
and in the Presenter (I use MVP)
GlobalScope.launch(Dispatchers.Main) {
try {
val response = ApiFactory.apiService.LOGIN(username, password)
.await()
val body = response.body()
body?.let {
// Handle success or any other stuff
if (it.statusCode == 200) {
mView.onSuccess(it.data!!)
}
} ?:
// This is the else part where your body is null
// Here is how you use it.
// Pass the response for error handling
mView.showMessage(ApiFactory.parseError(response).message!!)
} catch (e: Exception) {
e.printStackTrace()
}
}
And thats how you roll it!
That's All Folks!
I am using Retrofit 2.0 to make api calls that return Observables. It all works good when the call went through fine and the response is as expected. Now let's say we have an error response, it throws an onError. I would like to read the response body even when it is an Error.
Example
#FormUrlEncoded
#POST("tokenLogin")
Observable<LoginResponse> loginWithToken(
#Field("token") String pin
);
When the request and response are valid, I get the right observable and onError is being called as expected when there is an error.
Correct Response:
{ "status" : "authenticated" }
The Observable converts this into the right Observable and I can read the response as LoginResponse object.
Now, the Error Response is as follows:
{ "errorMessage" : "You need to take some xyz action" }
I would like to read that error response and display the message to the user. How do I go about doing that?
Just check if the throwable is an instance of HttpException and then you can access the retrofit response
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
...
}
Then you can use the converter to deserialize it (or do it yourself).
You can add this code block to display the error message.
#Override
public void onFailure(Throwable t) {
if (t instanceof HttpException) {
ResponseBody body = ((HttpException) t).response().errorBody();
Gson gson = new Gson();
TypeAdapter<ErrorParser> adapter = gson.getAdapter
(ErrorParser
.class);
try {
ErrorParser errorParser =
adapter.fromJson(body.string());
Logger.i(TAG, "Error:" + errorParser.getError());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Retrofit returns the Throwable Object which is a type of HttpException. First Step that you need to do is that you should know the structure of you error body object. Will show how to do it Kotlin. Once you know the structure, you need to create Error.kt file like shown below :
package com.test.test.qr.data.network.responsemodel
import com.google.gson.annotations.SerializedName
data class Error(
#SerializedName("code")
val code: String,
#SerializedName("message")
val message: String
)
Now you need to parse the body from HttpException to Error.Kt you created. This can be done as shown below :
if(it is HttpException) {
val body = it.response()?.errorBody()
val gson = Gson()
val adapter: TypeAdapter<Error> = gson.getAdapter(Error::class.java)
try {
val error: Error = adapter.fromJson(body?.string())
Log.d("test", " code = " + error.code + " message = " + error.message)
} catch (e: IOException) {
Log.d("test", " Error in parsing")
}
}
Where it is the Throwable you get in onError() from retrofit. Hope it helps. Happy Coding...:-)