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
Related
I integrated the witter login in android. But on response it always returning me in the logcat:
code=400, message=Bad Request
and
"data":"{\"+clicked_branch_link\":false,\"+is_first_session\":false}",
I am to see the token, secret value in logcat if I printed it. But response returning 400 always. I used here branch IO for deep linking.
public void attachTwitter(String token, String secret) {
apiService.attachTwitterAccount(PreferenceHandler.readString(EditProfileActivity.this, SIGNIN_ID, ""),
token, secret, "twitter").enqueue(new Callback<Object>() {
#Override
public void onResponse(Call<Object> call, Response<Object> response) {
Log.i("accessToken", "onResponse");
if (!response.isSuccessful()) {
try {
JSONObject object = new JSONObject(response.errorBody().string());
JSONObject error = object.optJSONObject("error");
String code = error.optString("code");
String description = error.optString("description");
if (code.equalsIgnoreCase("338")) {
showCustomDialog(EditProfileActivity.this, string(R.string.server_error_description_338));
switchTwitter.setChecked(false);
}
} catch (Exception e) {
}
}
}
#Override
public void onFailure(Call<Object> call, Throwable t) {
Log.i("accessToken", "onFailure");
switchTwitter.setChecked(false);
}
});
}
attachTwitterAccount methods code is:
#FormUrlEncoded
fun attachTwitterAccount(#Field("id") id: String,
#Field("authToken") token: String,
#Field("authSecret") authSecret: String,
#Field("typeAttach") typeAttach: String): Call<Any>
Can anyone please advise how I can fix this issue?
A Bad request means that the request that you are sending to the server is not in the way or form it is expecting. What do the docs say about that request? is it a Post? a Get?. If it is a POST then you should send a Body.
To send a body and a POST you first need to create a class for the body. In Kotlin it would be something like this:
class Body(var id: String,
var authToken: String,
var authSecret: String,
var accomplished: Double,
var typeAttach: String
)
Then you call the body in your request:
#POST("post value which")
fun attachTwitterAccount(#Body body:Body): Call<Any>
Finally you call it from your attach Twitter function. You create your Body instance and then pass it as argument.
If you are still getting the same error, check the Twitter docs, you might be missing something.
Its always a good idea to try that same call to the server in other enviroment (such as Postman) and see if it works there? this is a good way of determining if the problem is in the server side or in your application side.
Let me know if it works
I have a basic Retrofit setup for network requests. I have the following Authenticator that is added to the chain. It basically tries to refresh access token when authorization error (401) occurs.
class TokenAuthenticator(private val api: MyApi) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val retrofitResponse = api.refreshAccessToken("my refresh token here"))
val refreshResponse= retrofitResponse.blockingGet()
return if(refreshResponse != null) {
response.request().newBuilder()
.header(Const.HEADER_AUTHORIZATION, "Bearer " + refreshResponse.accessToken)
.build()
} else {
return null
}
}
}
The problem is my server might return 401 not only for authroization issues but also for some other cases. For example, i might get response with 401 if user phone number already exists in the database. Server returns me error_code paramter to differentiate this type of issues:
error_code = "token_expired" -> authoriation issue. shows that access token expired.
error_code = "phone_exists" -> shows phone number entered already exsits in the database.
So, I need to be able to check for this paramter before deciding that error was access token refresh error. How can I do that?
Currently, since I have not been able to check for that paramter, my app thinks that 401 is an authroization issue and continuously trying to refresh the access token even though my access token is not expired.
we had similar issue in our project, it may be a little mess because of hard-coded url but i think it is okey
we check the request's url and if it matched with the refresh token url then we start process of getting new token
Do you mean ErrorInterceptor?
import okhttp3.Interceptor
import okhttp3.Response
class ErrorInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain?): Response {
val originalResponse = chain!!.proceed(chain.request())
if (shouldLogout(originalResponse)) {
// your logout logic here
// send empty response down the chain
return Response.Builder().build()
}
return originalResponse
}
private fun shouldLogout(response: Response) : Boolean {
if (response.isSuccessful) {
return false
}
// 401 and auth token means that we need to logout
return (response.code() == 401 &&
!response.headers().names().contains(AUTH_HEADER_KEY))
}
}
I'm using a custom Interceptor along with Retrofit client in my Android app, that throws an Exception under some specific circumstances. I'm trying to make it work using Kotlin coroutines.
The problem is that I'm unable to handle the before mentioned error, since in the moment the exception is thrown from within the Interceptor instance, it crashes the whole app instead of being caught in the coroutine's try/catch statement. While I was using the Rx implementation, the exception was flawlessly propagated to the onError callback where I was able to handle it the way I needed.
I guess this is somehow related to the underlying threads that are being used for the network call, please see the logs below from the place where the call is made, from the interceptor just before throwing the exception, and the stacktrace:
2019-11-04 17:17:34.515 29549-29729/com.app W/TAG: Running thread: DefaultDispatcher-worker-1
2019-11-04 17:17:45.911 29549-29834/com.app W/TAG: Interceptor thread: OkHttp https://some.endpoint.com/...
2019-11-04 17:17:45.917 29549-29834/com.app E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.app, PID: 29549
com.app.IllegalStateException: Passed refresh token can\'t be used for refreshing the token.
at com.app.net.AuthInterceptor.intercept(AuthInterceptor.kt:33)
What am I supposed to do in order to be able to catch and handle this exception from the Interceptor correctly? Am I missing something?
You should subclass IOException and use that to send information from your interceptors to your calling code.
We consider other exceptions like IllegalStateException to be application crashes and do not send them over thread boundaries because we don’t want to burden most callers with catching them.
You may catch the exception in your custom Interceptor and return an empty response with some specific message and code. I have implemented a custom Interceptor to handle the situation like when you do not have or slow internet connection etc... Actually coroutine's suspend functions throws exception when dealing with network calls. In my experience, you can follow 2 approaches. 1. wrap your all network call in try...catch or 2. create a custom Interceptor and handle exceptions there and return some specific response.
Approach 1:
try {
webservice.login(username, password)
} catch (e: Exception) {
//...
}
Approach 2:
Create a custom Interceptor and handle exception there.
class LoggingInterceptor : Interceptor {
#Throws(Exception::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
try {
val response = chain.proceed(request)
val bodyString = response.body()!!.string()
return response.newBuilder()
.body(ResponseBody.create(response.body()?.contentType(), bodyString))
.build()
} catch (e: Exception) {
e.printStackTrace()
var msg = ""
when (e) {
is SocketTimeoutException -> {
msg = "Timeout - Please check your internet connection"
}
is UnknownHostException -> {
msg = "Unable to make a connection. Please check your internet"
}
is ConnectionShutdownException -> {
msg = "Connection shutdown. Please check your internet"
}
is IOException -> {
msg = "Server is unreachable, please try again later."
}
is IllegalStateException -> {
msg = "${e.message}"
}
else -> {
msg = "${e.message}"
}
}
return Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(999)
.message(msg)
.body(ResponseBody.create(null, "{${e}}")).build()
}
}
}
I have created gist for complete implementation of LoggingInterceptor with print logs of request and response. LoggingInterceptor
I dont know what exactly you need, but understood like this:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
// todo deal with the issues the way you need to
if (response.code() == SomeCode) {
//do something
return response;
}
return response;
}
})
.build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
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.
I have trying to parse actual response body even if server returns 401 HTTP Exception.
protected inline fun <RESPONSE : ParentResponse> executeNetworkCall(
crossinline request: () -> Single<RESPONSE>,
crossinline successful: (t: RESPONSE) -> Unit,
crossinline error: (t: RESPONSE) -> Unit) {
request().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ t: RESPONSE ->
errorHandler!!.checkApiResponseError(t)?.let {
listener?.onErrorWithId(t.message!!)
error(t)
return#subscribe
}
successful(t)
}
,
{ t: Throwable ->
listener?.onErrorWithId(t.message!!)
}
)
}
This is what I have written. It parses response and error very well when both are separate in usual ways. But I want to parse success response when I get even 401 HTTP Exception.
Thanks in advance..
Response with 401 HTTP looks like below.
401 Unauthorized - HTTP Exception
{"Message":"Authentication unsuccessful","otherData":"//Some data"}
By the way I have to check HTTP error code..
if (statusCode==401){
print("Authentication unsuccessful")
}
You can use Retrofit's Response class for that purpose, which is a wrapper over your response object, it has both your response's data and error bodies and also the success state, so instead of doing Single<RESPONSE> use Single<Response<RESPONSE>>.
Parsing the response object can be something like this:
{ t: Response<RESPONSE> ->
if (t.isSuccessful())
// That's the usual success scenario
else
// You have a response that has an error body.
}
,
{ t: Throwable ->
// You didn't reach the endpoint somehow, maybe a timeout or an invalid URL.
}