I'm trying to log all my API responses with status code 400+ in Fabric as non-fatal exception. Simplified method for obtain created RestAdapter look's like this:
public static Api get() {
if (mInstance == null) {
mInstance = new RestAdapter
.Builder()
.setEndpoint(API_URL)
.setErrorHandler(new ErrorHandler() {
#Override
public Throwable handleError(RetrofitError cause) {
Crashlytics.logException(new ApiException(cause.getMessage() + ": " + cause.getUrl(), cause));
return cause;
}
})
.build()
.create(Api.class);
}
return mInstance;
}
The problem is, all of this exceptions are collapsed to one in Fabric, because the same exception is thrown everytime at same line of code. I want to classify them accoring to status code of response.
I'm thinking how to solve this problem and few ideas come to my mind:
Generate exeptions (for example ApiException404 etc.) at runtime.
Throw exceptions in different lines (enormous switch with handling all of the status codes)
... but both of the approaches are really unsightly. Thanks for every advice or idea.
Related
I have an Android app that has this particular retrofit code that has not been touched in years. All of a sudden we started seeing 404 errors when making certain GET calls. When debugging through it, looking at the response, it looks like this:
Response{protocol=h2, code=404, message=, url=https://www.mainstreetartsfest.org/feed/artist/getAllCategories}
When I copy that URL into a browser, it loads the data fine. We are doing something similar on iOS with no issues, but on Android, it's failing now. I am completely stumped as to any reasons why this would have started occurring. Any ideas? The interface for the Retrofit API looks like this:
#GET("feed/artist/getAllCategories")
Call<List<Category>> getAllCategories();
Our base URL is defined as follows:
public static final String BASE_URL = "http://www.mainstreetartsfest.org/";
Let me know if more info is needed.
I have try this api on postman and this api is giving 404 Not Found. my backhand team says that there is some mistake in backhand side .
Something alike this should work - and won't break once they've fixed the problem on their side:
Call<Categories> api = SomeClient.getAllCategories();
api.enqueue(new Callback<Categories>() {
#Override
public void onResponse(#NonNull Call<Categories> call, #NonNull Response<Categories> response) {
switch(response.code()) {
/* temporary workaround: treating HTTP404 as if it would be HTTP200. */
case 200:
Log.e(LOG_TAG, "a proper HTTP200 header had been received.");
case 404:
if (response.body() != null) {
Categories items = response.body();
...
} else if (response.errorBody() != null) {
String errors = response.errorBody().string();
...
}
break;
}
}
}
Just logging an error on HTTP200, so that one can see when it's about time to fix the code.
I need some help in implementing parallel asynchronous calls using RxJava2 & Retrofit2.
My requirements are;
1) I have multiple Insurer(for now I take only two),and I need to send multiple parallel requests using that insurer name.
2)If any of them give server error then remaining requests should not gets block.
Following is what I tried until now;
ArrayList<String> arrInsurer = new ArrayList<>();
arrInsurer.add(AppConstant.HDFC);
arrInsurer.add(AppConstant.ITGI);
RequestInterface service = getService(ServiceAPI.CAR_BASE_URL);
for (String insurerName : arrInsurer) {
service.viewQuote(Utils.getPrefQuoteId(QuoteListActivity.this), insurerName)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ViewQuoteResDTO>() {
#Override
public void accept(#NonNull ViewQuoteResDTO viewQuoteResDTO) throws Exception {
Log.e("Demo", viewQuoteResDTO.getPremiumData().getIDV()+"");
updateList();
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
Log.e("Demo", throwable.getMessage());
}
});
}
private RequestInterface getService(String baseUrl) {
Gson gson = new GsonBuilder()
.setLenient()
.create();
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build().create(RequestInterface.class);
}
Now, Above code works fine only if both request give successful response. But when any of request give response as a internal server error then rest of request also gets block.
following Log error which I get when any of request give Failure response;
E/Demo: HTTP 500 Aww Snap, Some thing happened at server. Please try back again later.
E/Demo: unexpected end of stream on Connection{100.xxx.xxx.xx:portNo, proxy=DIRECT# hostAddress=/100.xxx.xxx.xx:portNo cipherSuite=none protocol=http/1.1}
How to handle this error?
I guess like any other Rx related question this has multiple answers. I will give you mine which I use in our app and solves exactly this use case. Hope it helps.
Short version - This relies on mergeDelayError. Check it out here
Why merge? Because unlike concat it will execute the observables in parallel. Why mergeDelayError? It delays the error... essentially it will execute every observable and deliver the error when everything finishes. This makes sure that even if one or several error, the others will still be executed.
You have to be careful with some details. The order of events is no longer preserved, meaning the merge operator may interleave some of the observable events (Given how you were doing things before, this shouldn't be an issue). As far as I know, even if multiple observables fail, you'll only get one onError call. If both of these are ok, then you could try the following:
List<Observable<ViewQuoteResDTO>> observables = new ArrayList<>();
for (String insurerName : arrInsurer) {
observables.add(service.viewQuote(
Utils.getPrefQuoteId(QuoteListActivity.this), insurerName));
}
Observable.mergeDelayError(observables)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* subscriber calls if you need them */);
The idea is to create all observables that you're going to run and then use mergeDelayError to trigger them.
So currently, Im making an async network request with a date parameter (using Retrofit), and if that request returns with a response code that isnt 200 (or if its 429, 400, or if the response body is just null, whatever is easiest to determine), I make a new request with a date parameter 1 day earlier. Again, if this request comes back with a response code that isnt 200, I make one more request with a date 1 day earlier than the previous, for a total of 3 possible requests if the first two fail.
I'm currently achieving this with a bunch of callbacks and calling a new method set up to perform the request with the day -1 for each try.
I know that I can achieve a cleaner solution with Rx and Retrofits built in Rx features. How would this be done?
First you need to add RxAndroid and RxJava adapter in your dependencies
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'io.reactivex:rxandroid:1.0.1'
and then you need to register the call adapter to your Retrofit declaration
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseurl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
And then you can change your service interface return type from Call to Observable
public interface MyAPIService {
#POST("user")
Call<User> getUser();
#POST("user")
Observable<Response<User>> getUserWithRxJava();
#POST("user_friends")
Observable<Response<List<User>>> getUserFriends();
}
And this is an example for chaining call
myService.getUserWithRxJava()
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Response<User>, Observable<Response<List<User>>>>() {
#Override
public Observable<List<Home>> call(Response<User> responseUser) {
// You can also use responseUser.code to get the response code
// but isSuccess() function will return true if the code
// is in the range [200..300)
if (responseUser.isSuccess()) {
return myService.getUserFriends();
} else {
// You can also use Observable.empty() if you want to ignore unsuccessful response
return Observable.error(myThrowable);
}
}
})
.subscribe(new Subscriber<Response<List<User>>>() {
#Override
public void onCompleted() {
// TODO Completed
}
#Override
public void onError(Throwable e) {
// TODO Handle the error
}
#Override
public void onNext(Response<List<User>> friendListResponse) {
// TODO do something with the data
// To get the serialized data you can use friendListResponse.body();
}
});
The non-200 responses should come back as RetrofitError objects, which contain Response objects with a status code.
You could do something like this:
observable
.retryWhen(new RetryStrategy())
.subscribe(...);
and RetryStrategy might look like this (note, I'm using retrolambda, so wherever you see -> just replace with a new anonymous inner class):
public class RetryStrategy implements Func1<Observable<? extends Throwable>, Observable<?>> {
public RetryStrategy() {}
#Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts.flatMap((throwable) -> {
if (throwable instanceof RetrofitError) {
RetrofitError error = (RetrofitError) throwable;
if (error.getKind() == RetrofitError.Kind.HTTP) {
if (error.getResponse().getStatus() == 401) {
// This is where you attempt to recover
return someRecoveryObservable???;
}
}
}
// Bubble any other errors back up, e.g. connection loss.
return Observable.error(throwable);
});
}
}
You could also implement Exponential Backoff here by adding support for a retry count, along with using Observable.timer(long, TimeUnit) in your recovery phase (and compute the timing based on the current number of tries). Spotty connection problems sometimes benefit greatly from this approach - especially with fire & forget tasks that run in the background.
I have a custom error handler that checks RetrofitError it gets passed and rethrows it as custom exceptions
private static ErrorHandler getErrorHandler() {
return new ErrorHandler() {
#Override
public Throwable handleError(RetrofitError cause) {
switch (cause.getKind()) {
case NETWORK: return new NetworkException(cause);
case HTTP: return new ApiException(cause);
default: return cause;
}
}
};
}
If this is my endpoint
#GET(USERS_GET_URL)
User getUsers() throws NetworkException, ApiException;
while executing synchronous request I try...catch and handle each custom exception as I want. When it is done asynchronously using
#GET(USERS_GET_URL)
void getUsers(Callback<User> cb) throws NetworkException, ApiException;
the handled exception gets rethrown as RetrofitError. The following snippet of code is from CallbackRunnable class of Retrofit which executes the request
try {
final ResponseWrapper wrapper = obtainResponse();
callbackExecutor.execute(new Runnable() {
#Override public void run() {
callback.success((T) wrapper.responseBody, wrapper.response);
}
});
} catch (RetrofitError e) {
Throwable cause = errorHandler.handleError(e);
final RetrofitError handled = cause == e ? e : unexpectedError(e.getUrl(), cause);
callbackExecutor.execute(new Runnable() {
#Override public void run() {
callback.failure(handled);
}
});
}
As it can be seen, my custom exceptions are getting rethrown as RetrofitError which makes me loose valuable information. Is there any way I can bypass custom error handling for just the async requests?
In your ErrorHandler you pathing original RetrofitError as cause, so as result in your Callback#failure(RetrofitError error) to get actual information you need to write next code: error.getCause().getCause(). This error will contain response that server send with all the data.
But error handler was created for sync request and after some time square team decided to close this gap this way. For more info you can read: https://gist.github.com/benvium/66bf24e0de80d609dac0
As for me, I don't recommend to use ErrorHander for async way, because I don't find any good solution to handle different types of error. It was much easier to get data right from initial RetrofitError.
I want to display some tweets with the twitter API in my app. To do so I have fetched some tweet ids (which is working without any hassle) and use the TweetViewFetchAdapter adapter provided by the Twitter API to display my tweets.
The weird thing is: this has worked at some point! But then suddenly it stopped working (company app, multiple people working on the code but I haven't seen any changes to the twitter stuff in the time between working and not working)
The code is super straight forward taken from the official twitter site:
// fill the tweet adapter with the loaded tweet ids
#Override
protected void onPostExecute(List<Long> params){
if (params != null && params.size() > 0) {
adapter.setTweetIds(params,
new LoadCallback<List<Tweet>>() {
#Override
public void success(List<Tweet> tweets) {
Log.i("twitter", "Success!");
}
#Override
public void failure(TwitterException exception) {
Log.e("twitter", "Exception: " + exception.getMessage());
}
});
}
Log.i("twitter", "params.size = " + params.size() + "adapter.tweetCount = " + adapter.getCount());
}
(inside an AsyncTask). The adapter seems to fail to set the tweet ids as the debug output is I/twitter﹕ params.size = 10 adapter.tweet Count = 0
I tried to debug/have a log output in the success/failure callbacks, but I never got anything as if the methods would never be called (quite weird actually..)
Regarding log cat output I haven't seen any, but I'm afraid there is a little chance I might have messed it up as we just recently moved to Android Studio and I just can't get my head around some stuff there yet.
Issue was caused by an wrong version of okhttp / okhttp-urlconnection.
The weird part is that no debug messages was shown. This code resolved the debug message issue and helped resolve the issue overall:
final Fabric fabric = new Fabric.Builder(this)
.kits(new Twitter(authConfig))
.logger(new DefaultLogger(Log.DEBUG))
.debuggable(true)
.build();
Fabric.with(fabric);
Overall fix: change build.gradle:
dependencies {
// ...
compile 'com.squareup.okhttp:okhttp:2.3.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
}
Original conversation: https://twittercommunity.com/t/adapter-settweetid-seems-to-be-unable-to-load-the-tweets-but-doesnt-fire-a-success-failure-callback/36506/13