when I user the OkHttp Library with a asynchronous way like this:
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
}
});
In the onFailure method, how I get the response status code to distinguish different errors. For example, Network error or Server error ?
As far as I remember, onFailure gets triggered when you get no response. So, if your receive an error, onResponse will be called. You can do something like this in onResponse:
#Override
public void onResponse(Call call, Response response) throws IOException {
switch(response.code()){
//your desired catched codes here.
}
}
And official doc for onResponse method:
Note that transport-layer success (receiving a HTTP response code, headers and body) does not necessarily indicate application-layer success: response may still indicate an unhappy HTTP response code like 404 or 500.
https://github.com/square/okhttp/issues/1769
According to the link, above, onFailure() is called if and only if there were problems with the client.
If the request was successfully delivered but there was a server problem you can check response.isSuccessful(). If it returns false, check response.code() and handle the error.
You check that using response.code()
You can also make use of response.message() to get more info.
Related
So I wanted to implement the example of response from the API like in this video
droidcon NYC 2017 - Advanced Networking with RxJava + Retrofit
And this is my code:
Presenter.java
compositeDisposable.add(RetrofitClient.createService(GetApi.class)
.getResponseFromServer(token)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ResponseFromServer>() {
#Override
public void accept(ResponseFromServer responseFromServer) throws Exception {
mView.setResponseObject(responseFromServer);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
throwable.printStackTrace();
if (throwable instanceof HttpException) {
int responseCode = ((HttpException) throwable).code();
}
}
}));
So here, when I get some 4xx error response from the server, I can go to Throwable and get the response code, and if response is okay I can get my object, and everything is cool.
However, in the video example above, the guy suggest that I wrap my ResponseFromServer with Response like this:
Single<Response<ResponseFromServer>> getResponseFromServer(#Header("X-Authorize") String token); so I can access response codes also, but in that case, my Throwable never gets called, so I can access to the codes only in the first accept method, but in the video he catch the errors in the Throwable section. So, I cant figure it out what I'm doing wrong? Maybe I'm using the wrong Observer?
I think i figured it out, if we wrap our response object with Observable<Response<Object>> all of the response code will be caught in the regular accept method, so we kinda need to extract the codes manually and do the checks. However, if we keep the Observable<Object>, errorCode < 200 || errorCode > 400 will be caught in the onError method.
When Response from Server is code < 200 || code >= 300 in those cases onError() will be invoked. and other cases onNext() will invoke.
Also, If your code from onNext() throws any exception, that will be catch in onError()
How can I be sure that calling cancel() on the Retrofit cause request is not delivered to the server?
Currently I know, that calling cancel on the Retrofit will invoke onFailure branch of the callback. And then I can check if the cause of the failure is because call was cancelled.
https://futurestud.io/tutorials/retrofit-2-cancel-requests
But can I be somehow sure, that request didn't arrive to the server?
Code snippet from here
new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.d(TAG, "request success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
if (call.isCanceled()) {
Log.e(TAG, "request was cancelled");
}
else {
Log.e(TAG, "other larger issue, i.e. no network connection?");
}
}
};
According to that this will raise in case call was canceled, as it say
// something happened, for example: user clicked cancel button
call.cancel();
If you call call.cancel() let's say later it means you already got a response from the server, therefore you won't get to onFailure point where call.isCanceled() is being checked :)
If you cancel the request, Retrofit will classify this as a failed request and thus call the onFailure() callback in your request declaration. This callback is also used when there is no Internet connection or something went wrong on the network level. From the app perspective, these errors are quite a bit different.
I know there are lots of tutorials for OkHttp, but basically all of them do something different in the onResponse method and most don't bother to explain why. Some check for if (response.isSuccessful), some surround it with try/catch, some don't do any of this at all.
This is my example project. What is the proper way to handle the onResponse method?
public class MainActivity extends AppCompatActivity {
private TextView textViewResult;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textViewResult = findViewById(R.id.text_view_result);
OkHttpClient client = new OkHttpClient();
String url = "https://reqres.in/api/users?page=2";
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
final String myResponse = response.body().string();
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
textViewResult.setText(myResponse);
}
});
}
});
}
}
Update
onResponse of okhttp runs on background thread. So, yes, it's necessary to do MainActivity.this.runOnUiThread(...).
Original answer
onResponse callback already runs on ui thread AFAIK. So, you don't actually need to do MainActivity.this.runOnUiThread(...).
And everyone's onResponse is different because everyone has different needs. Use try/catch if your operations in onResponse might give error and you don't want it to crash.
For some network requests you may need to check if response is successful for other you may not. It all depends on use cases. Do what works for you best.
I'd suggest you surround your code in onResponse in a try/catch block because the user might close the app before the network request is finished. And when you set the textview text in onResponse it will crash because the activity and that textview doesn't exist anymore.
Adding to the answer from rafid. There are basically three cases you want to check.
response.isSuccessful() => status code between 200 and 300
response.code() => to manually check after response is not successful
onFailure() => Network error or parsing error etc.
Ideally your callback would handle those cases something like
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
// network error or parsing error
}
#Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
// do stuff all good
} else {
// handle different cases for different status codes or dump them all here
}
}
});
The reason you need a try-catch is because OkHttp is trying to parse the response. This is the case for example for response.errorBody().string();. Another case would be if your Callback<T> has actually a type parameter. Again OkHttp will try to parse the response to that type. If it fails it will result in a callback onto the onFailure method.
I think you need to make sure you know the legal response from the request, like an json or File. if it's just a json, use like below:
#Override
public void onResponse(Call call, Response response) throws IOException {
final String myResponse = response.body().string();
if (response.isSuccessful() && !TextUtils.isEmpty(myResponse)) {
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
textViewResult.setText(myResponse);
}
});
}
}
Edit: To be more clear.
Callback is running in mainThread so there is no need to call runOnUiThread.
If response is not successful you can try to parse error body as below. If response is successful you can parse with Gson as i show.
String message = "";
if (response.errorBody() != null) {
try {
message = response.errorBody().string();
} catch (IOException ignored) {
Log.e("OkHttp IOException", "error while parsing response");
}
Log.d("Error Message", message);
}
I recommend you to use Gson Library. First you should create your pojo class. You can use http://www.jsonschema2pojo.org/ to create your pojo class. Then you can parse body like below
Gson gson = new Gson();
MyPojo myPojo = gson.fromJson(response.body().charStream(), MyPojo.class);
I'm trying to upgrade to Retrofit 2.0 and add RxJava in my android project. I'm making an api call and want to retrieve the error code in case of an error response from the server.
Observable<MyResponseObject> apiCall(#Body body);
And in the RxJava call:
myRetrofitObject.apiCall(body).subscribe(new Subscriber<MyResponseObject>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(MyResponseObject myResponseObject) {
//On response from server
}
});
In Retrofit 1.9, the RetrofitError still existed and we could get the status by doing:
error.getResponse().getStatus()
How do you do this with Retrofit 2.0 using RxJava?
Instead of declaring the API call like you did:
Observable<MyResponseObject> apiCall(#Body body);
You can also declare it like this:
Observable<Response<MyResponseObject>> apiCall(#Body body);
You will then have a Subscriber like the following:
new Subscriber<Response<StartupResponse>>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError: %", e.toString());
// network errors, e. g. UnknownHostException, will end up here
}
#Override
public void onNext(Response<StartupResponse> startupResponseResponse) {
Timber.d("onNext: %s", startupResponseResponse.code());
// HTTP errors, e. g. 404, will end up here!
}
}
So, server responses with an error code will also be delivered to onNext and you can get the code by calling reponse.code().
http://square.github.io/retrofit/2.x/retrofit/retrofit/Response.html
EDIT: OK, I finally got around to looking into what e-nouri said in their comment, namely that only 2xx codes will to to onNext. Turns out we are both right:
If the call is declared like this:
Observable<Response<MyResponseObject>> apiCall(#Body body);
or even this
Observable<Response<ResponseBody>> apiCall(#Body body);
all responses will end up in onNext, regardless of their error code. This is possible because everything is wrapped in a Response object by Retrofit.
If, on the other hand, the call is declared like this:
Observable<MyResponseObject> apiCall(#Body body);
or this
Observable<ResponseBody> apiCall(#Body body);
indeed only the 2xx responses will go to onNext. Everything else will be wrapped in an HttpException and sent to onError. Which also makes sense, because without the Response wrapper, what should be emitted to onNext? Given that the request was not successful the only sensible thing to emit would be null...
Inside onError method put this to get the code
((HttpException) e).code()
You should note that as of Retrofit2 all responses with code 2xx will be called from onNext() callback and the rest of HTTP codes like 4xx, 5xx will be called on the onError() callback, using Kotlin I've came up with something like this in the onError() :
mViewReference?.get()?.onMediaFetchFinished(downloadArg)
if (it is HttpException) {
val errorCode = it.code()
mViewReference?.get()?.onMediaFetchFailed(downloadArg,when(errorCode){
HttpURLConnection.HTTP_NOT_FOUND -> R.string.check_is_private
else -> ErrorHandler.parseError(it)
})
} else {
mViewReference?.get()?.onMediaFetchFailed(downloadArg, ErrorHandler.parseError(it))
}
I am making call using the following callback method:
Callback<PeopleList> callback = new Callback<PeopleList>() {
#Override
public void onResponse(Response<PeopleList> response) {
Toast.makeText(LoginActivity.this,getString(R.string.login_failed), Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Throwable t) {
Toast.makeText(LoginActivity.this,getString(R.string.login_failed), Toast.LENGTH_SHORT).show();
}
};
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
retrofit.create(MyService.class).getPeopleData().enqueue(callback);
To the following interface:
public interface MyService {
#Headers("Accept: application/json")
#GET("/data/people/")
Call<PeopleList> getPeopleData();
}
This callback works just fine on successful requests. On unsuccessful ones however it does not give me the opportunity to investigate further as the onFailure method does not allow me to retrieve the http error code that came with the response.
On investigating further I found that according to several stackoverflow threads, the onResponse method should be called even on unsuccessful requests. This however seems to be at odds not only with my personal experience but also with the documentation of the Callback interface, which states that:
Communicates responses from a server or offline requests. One and only one method will be
invoked in response to a given request.
So the question is, how do I get the HTTP error code from a failed response if the onResponse method isn't called?
I think that the onResponse method gets called even if there is a response with an Error so something like this might work(sorry if I did something wrong first attempt to answer anybody :)
#Override
public void onResponse(Response<PeopleList> response) {
if(response.isSuccess()){ //good http request, do something with response.body()....
Toast.makeText(LoginActivity.this,getString(R.string.login_failed), Toast.LENGTH_SHORT).show();
} else { //bad http response do something with error message
try {
Toast.makeText(LoginActivity.this,response.errorBody().string().toString(), Toast.LENGTH_SHORT).show();
} catch (IOException e){
//IOException caught from response.errorBody().string() method
}
}
}
onResponse() will be always called, for failed requests .body() is null. response.isSuccess() is used to quickly distinguish requests with http codes between 200 and 300.
If you want to access http codes you can do the following:
int htppResultCode = response.raw().code();
It accesses the raw Response object which holds information about general outcome of the request.