I am using Retrofit/OkHttp (1.6) in my Android project.
I don't find any request retry mechanism built-in to either of them. On searching more, I read OkHttp seems to have silent-retries. I don't see that happening on any of my connections (HTTP or HTTPS). How to configure retries with okclient ?
For now, I am catching exceptions and retrying maintaining a counter variable.
For Retrofit 2.x;
You can use Call.clone() method to clone request and execute it.
For Retrofit 1.x;
You can use Interceptors. Create a custom interceptor
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
int tryCount = 0;
while (!response.isSuccessful() && tryCount < 3) {
Log.d("intercept", "Request is not successful - " + tryCount);
tryCount++;
// retry the request
response.close()
response = chain.proceed(request);
}
// otherwise just pass the original response on
return response;
}
});
And use it while creating RestAdapter.
new RestAdapter.Builder()
.setEndpoint(API_URL)
.setRequestInterceptor(requestInterceptor)
.setClient(new OkClient(client))
.build()
.create(Adapter.class);
I don't know if this is an option for you but you could use RxJava together with Retrofit.
Retrofit is able to return Observables upon rest calls. On Oberservables you can just call retry(count) to resubscribe to the Observable when it emits an error.
You would have to define the call in your interface like this:
#GET("/data.json")
Observable<DataResponse> fetchSomeData();
Then you can subscribe to this Observable like this:
restApi.fetchSomeData()
.retry(5) // Retry the call 5 times if it errors
.subscribeOn(Schedulers.io()) // execute the call asynchronously
.observeOn(AndroidSchedulers.mainThread()) // handle the results in the ui thread
.subscribe(onComplete, onError);
// onComplete and onError are of type Action1<DataResponse>, Action1<Throwable>
// Here you can define what to do with the results
I had the same problem like you and this was actually my solution. RxJava is a really nice library to use in combination with Retrofit. You can even do many cool things in addition to retrying (like e.g. composing and chaining calls).
I am of the opinion that you shouldn't mix API handling (done by retrofit/okhttp) with retries. Retrying mechanisms are more orthogonal, and can be used in many other contexts as well. So I use Retrofit/OkHTTP for all the API calls and request/response handling, and introduce another layer above, for retrying the API call.
In my limited Java experience so far, I have found jhlaterman's Failsafe library (github: jhalterman/failsafe) to be a very versatile library for handling many 'retry' situations cleanly. As an example, here's how I would use it with a retrofit instantiated mySimpleService, for authentication -
AuthenticationResponse authResp = Failsafe.with(
new RetryPolicy().retryOn(Arrays.asList(IOException.class, AssertionError.class))
.withBackoff(30, 500, TimeUnit.MILLISECONDS)
.withMaxRetries(3))
.onRetry((error) -> logger.warn("Retrying after error: " + error.getMessage()))
.get(() -> {
AuthenticationResponse r = mySimpleAPIService.authenticate(
new AuthenticationRequest(username,password))
.execute()
.body();
assert r != null;
return r;
});
The code above catches socket exceptions, connection errors, assertion failures, and retries on them maximum of 3 times, with exponential backoff. It also allows you to customise on-retry behaviour, and allows you to specify a fallback as well. It's quite configurable, and can adapt to most of the retry situations.
Feel free to check the documentation of the library as it offers many other goodies apart from just retries.
The problem with response.isSuccessful() is when you have an exception like SocketTimeoutException.
I modified the original code to fix it.
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
boolean responseOK = false;
int tryCount = 0;
while (!responseOK && tryCount < 3) {
try {
response = chain.proceed(request);
responseOK = response.isSuccessful();
}catch (Exception e){
Log.d("intercept", "Request is not successful - " + tryCount);
}finally{
tryCount++;
}
}
// otherwise just pass the original response on
return response;
}
});
Hope it helps.
Regards.
Courtesy to the top answer,This is what worked for me. If there is a connectivity issues, its better to wait for a few seconds before retry.
public class ErrorInterceptor implements Interceptor {
ICacheManager cacheManager;
Response response = null;
int tryCount = 0;
int maxLimit = 3;
int waitThreshold = 5000;
#Inject
public ErrorInterceptor() {
}
#Override
public Response intercept(Chain chain){
// String language = cacheManager.readPreference(PreferenceKeys.LANGUAGE_CODE);
Request request = chain.request();
response = sendReqeust(chain,request);
while (response ==null && tryCount < maxLimit) {
Log.d("intercept", "Request failed - " + tryCount);
tryCount++;
try {
Thread.sleep(waitThreshold); // force wait the network thread for 5 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
response = sendReqeust(chain,request);
}
return response;
}
private Response sendReqeust(Chain chain, Request request){
try {
response = chain.proceed(request);
if(!response.isSuccessful())
return null;
else
return response;
} catch (IOException e) {
return null;
}
}
}
A solution that worked for me on OkHttp 3.9.1 (considering other answers for this question):
#NonNull
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
int retriesCount = 0;
Response response = null;
do {
try {
response = chain.proceed(request);
// Retry if no internet connection.
} catch (ConnectException e) {
Log.e(TAG, "intercept: ", e);
retriesCount++;
try {
Thread.sleep(RETRY_TIME);
} catch (InterruptedException e1) {
Log.e(TAG, "intercept: ", e1);
}
}
} while (response == null && retriesCount < MAX_RETRIES);
// If there was no internet connection, then response will be null.
// Need to initialize response anyway to avoid NullPointerException.
if (response == null) {
response = chain.proceed(newRequest);
}
return response;
}
I found the way(OKHttpClient intercepter) provided by Sinan Kozak does not work when http connection failed, there is nothing yet concerned with HTTP response.
So i use another way to hook the Observable object, call .retryWhen on it.
Also, i have added retryCount limit.
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.HttpException;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;
import rx.Observable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
Then
RxJavaCallAdapterFactory originCallAdaptorFactory = RxJavaCallAdapterFactory.create();
CallAdapter.Factory newCallAdaptorFactory = new CallAdapter.Factory() {
#Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
CallAdapter<?> ca = originCallAdaptorFactory.get(returnType, annotations, retrofit);
return new CallAdapter<Observable<?>>() {
#Override
public Type responseType() {
return ca.responseType();
}
int restRetryCount = 3;
#Override
public <R> Observable<?> adapt(Call<R> call) {
Observable<?> rx = (Observable<?>) ca.adapt(call);
return rx.retryWhen(errors -> errors.flatMap(error -> {
boolean needRetry = false;
if (restRetryCount >= 1) {
if (error instanceof IOException) {
needRetry = true;
} else if (error instanceof HttpException) {
if (((HttpException) error).code() != 200) {
needRetry = true;
}
}
}
if (needRetry) {
restRetryCount--;
return Observable.just(null);
} else {
return Observable.error(error);
}
}));
}
};
}
};
Then
add or replace
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
with
.addCallAdapterFactory(newCallAdaptorFactory)
For example:
return new Retrofit
.Builder()
.baseUrl(baseUrl)
.client(okClient)
.addCallAdapterFactory(newCallAdaptorFactory)
.addConverterFactory(JacksonConverterFactory.create(objectMapper));
Note: For simplicity, i just treat HTTP code > 404 code as retry, please modify it for yourself.
Besides, if http response is 200, then above rx.retryWhen will not get called, if you insist check such a response, you can add rx.subscribeOn(...throw error... before .retryWhen.
For those prefer an interceptor to deal with the issue of retrying -
Building upon Sinan's answer, here is my proposed interceptor, which includes both retry count and back-off delay, and only retries attempts when network is available, and when request wasn't cancelled.
(only deals with IOExceptions (SocketTimeout, UnknownHost, etc.))
builder.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = null;
int tryCount = 1;
while (tryCount <= MAX_TRY_COUNT) {
try {
response = chain.proceed(request);
break;
} catch (Exception e) {
if (!NetworkUtils.isNetworkAvailable()) {
// if no internet, dont bother retrying request
throw e;
}
if ("Canceled".equalsIgnoreCase(e.getMessage())) {
// Request canceled, do not retry
throw e;
}
if (tryCount >= MAX_TRY_COUNT) {
// max retry count reached, giving up
throw e;
}
try {
// sleep delay * try count (e.g. 1st retry after 3000ms, 2nd after 6000ms, etc.)
Thread.sleep(RETRY_BACKOFF_DELAY * tryCount);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
tryCount++;
}
}
// otherwise just pass the original response on
return response;
}
});
I have play a lot with this problem trying to find how is the best way to retry Retrofit requests. I am using Retrofit 2 so my solution is for Retrofit 2. For Retrofit 1 you have to use Interceptor like the accepted answer here. The answer of #joluet is correct but he did not mention that retry method need to be called before .subscribe(onComplete, onError) method. This is very important otherwise the request wouldn't be retried again like #pocmo mentioned in #joluet answer. Here is my example:
final Observable<List<NewsDatum>> newsDetailsObservable = apiService.getCandidateNewsItem(newsId).map((newsDetailsParseObject) -> {
return newsDetailsParseObject;
});
newsDetailsObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry((integer, throwable) -> {
//MAX_NUMBER_TRY is your maximum try number
if(integer <= MAX_NUMBER_TRY){
return true;//this will retry the observable (request)
}
return false;//this will not retry and it will go inside onError method
})
.subscribe(new Subscriber<List<NewsDatum>>() {
#Override
public void onCompleted() {
// do nothing
}
#Override
public void onError(Throwable e) {
//do something with the error
}
#Override
public void onNext(List<NewsDatum> apiNewsDatum) {
//do something with the parsed data
}
});
apiService is my RetrofitServiceProvider object.
BTW : I am using Java 8 so a lot of lambda expressions are inside the code.
Just want to share my version. It uses rxJava retryWhen method. My version retries connection every N=15 sec and almost immediately emit retry when internet connection recover.
public class RetryWithDelayOrInternet implements Function<Flowable<? extends Throwable>, Flowable<?>> {
public static boolean isInternetUp;
private int retryCount;
#Override
public Flowable<?> apply(final Flowable<? extends Throwable> attempts) {
return Flowable.fromPublisher(s -> {
while (true) {
retryCount++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
attempts.subscribe(s);
break;
}
if (isInternetUp || retryCount == 15) {
retryCount = 0;
s.onNext(new Object());
}
}
})
.subscribeOn(Schedulers.single());
}}
And you should use it before .subscribe like this:
.retryWhen(new RetryWithDelayOrInternet())
You should manually change isInternetUp field
public class InternetConnectionReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
boolean networkAvailable = isNetworkAvailable(context);
RetryWithDelayOrInternet.isInternetUp = networkAvailable;
}
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}}
As a previous user said, if your are using Retrofit2 call.clone would suffice, but I also wanted to add a quick example on how that would look:
public class CallbackImpl implements Callback<ResponseBody> {
private final Set<Integer> retryCode = new HashSet<>(Arrays.asList(503, 504));
int requestRetry = 1;
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.code() == 201) {
// Object was created.
} else {
if (requestRetry != 0 && retryCode.contains(response.code())) {
call.clone().enqueue(this);
} else {
// Handle the error
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable) {
if (throwable instanceof IOException) {
// Network failure
} else {
// Conversion Issue
}
}
}
Resilience4j 1.x offered a highly configurable way of defining your retry behaviour and has retrofit adapters as an add-on module. This functionality has since been deprecated in version 2.0.
Example of the available options:
private final Retry retry = Retry.of("id", RetryConfig.<Response<String>>custom()
.maxAttempts(2)
.waitDuration(Duration.ofMillis(1000))
.retryOnResult(response -> response.code() == 500)
.retryOnException(e -> e instanceof WebServiceException)
.retryExceptions(IOException.class, TimeoutException.class)
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.failAfterMaxAttempts(true)
.build());
See the resilience4j documentation on how to integrate that with Retrofit.
It seems it will be present in retrofit 2.0 from the API Spec:
https://github.com/square/retrofit/issues/297.
Currently, the best way seems to be catch exception and retry manually.
As stated in the docs, a better might be to use the baked in authenticators, eg:
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
client.setAuthenticator(new Authenticator() {
#Override public Request authenticate(Proxy proxy, Response response) {
System.out.println("Authenticating for response: " + response);
System.out.println("Challenges: " + response.challenges());
String credential = Credentials.basic("jesse", "password1");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
#Override public Request authenticateProxy(Proxy proxy, Response response) {
return null; // Null indicates no attempt to authenticate.
}
});
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
Working prod solution.
public int callAPI() {
return 1; //some method to be retried
}
public int retrylogic() throws InterruptedException, IOException{
int retry = 0;
int status = -1;
boolean delay = false;
do {
if (delay) {
Thread.sleep(2000);
}
try {
status = callAPI();
}
catch (Exception e) {
System.out.println("Error occured");
status = -1;
}
finally {
switch (status) {
case 200:
System.out.println(" **OK**");
return status;
default:
System.out.println(" **unknown response code**.");
break;
}
retry++;
System.out.println("Failed retry " + retry + "/" + 3);
delay = true;
}
}while (retry < 3);
System.out.println("Aborting download of dataset.");
return status;
}
Related
This question already has answers here:
Get response status code using Retrofit 2.0 and RxJava
(3 answers)
Closed 5 years ago.
I used Retrofit with RxJava like this:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(HttpURL.BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
and when the request error, such as password is wrong, the status code is 400, and the error msg will int the errorBoby to get me just like {code: 1000, message: "password is wrong"}.
However, the gons GsonConverterFactory will not fromJson in respone.getErrorBody , so I change my code just like this
Call<Result<User>> call = ApiManger.get().getLoginApi().login1(login);
call.enqueue(new Callback<Result<User>>() {
#Override
public void onResponse(Call<Result<User>> call, Response<Result<User>> response) {
if (response.code() == 0) {
mLoginView.onLoginSuccess(response.body().getData());
} else {
try {
Result result = new Gson().fromJson(response.errorBody().string(),Result.class);
ToastUtils.showShort(result.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<Result<User>> call, Throwable t) {
}
});
so it can not be used with Rxjava, how can I change it?
This can be done using Rx and here is how:
mSubscription.add(mDataManager.login(username, password)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<User>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
if (NetworkUtil.isHttpStatusCode(e, 400) || NetworkUtil.isHttpStatusCode(e, 400)) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
getMvpView().onError(body.string());
} catch (IOException e1) {
Timber.e(e1.getMessage());
} finally {
if (body != null) {
body.close();
}
}
}
}
#Override
public void onNext(User user) {
//TODO Handle onNext
}
}));
}
NetworkUtil
public class NetworkUtil {
/**
* Returns true if the Throwable is an instance of RetrofitError with an
* http status code equals to the given one.
*/
public static boolean isHttpStatusCode(Throwable throwable, int statusCode) {
return throwable instanceof HttpException
&& ((HttpException) throwable).code() == statusCode;
}
}
You need to serialise the error body string first
try something like this in your onNext():
if (!response.isSuccessful()) {
JSONObject errorBody = new JSONObject(response.errorBody().string());
String message = errorBody.getString("message");
}
Its usually a better idea to accept the response in a standard format -
Class Response{
int code;
String message;
Data data; //////now data is the actual data that u need
/////getter setters
}
Now add an api method like this -
#GET("api_name")
Observable<Response> getResponse(Params);
now call retrofit.getResponse(params) and you will get the observable, subscribe to that observable and check its value in onNext and implement your logic. So in your case(password error) the data would be null, but you will have code and message.
I am new to RxJava so please forgive me if this sounds too newbie :-).
As of now I have an abstract CallbackClass that implements the Retofit Callback. There I catch the Callback's "onResponse" and "onError" methods and handle various error types before finally forwarding to the custom implemented methods.
I also use this centralized class to for request/response app logging and other stuff.
For example: for specific error codes from my sever I receive a new Auth token in the response body, refresh the token and then clone.enqueue the call.
There are of course several other global behaviors to the responses from my server.
Current solution (Without Rx):
public abstract void onResponse(Call<T> call, Response<T> response, boolean isSuccess);
public abstract void onFailure(Call<T> call, Response<T> response, Throwable t, boolean isTimeout);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (_isCanceled) return;
if (response != null && !response.isSuccessful()) {
if (response.code() == "SomeCode" && retryCount < RETRY_LIMIT) {
TokenResponseModel newToken = null;
try {
newToken = new Gson().fromJson(new String(response.errorBody().bytes(), "UTF-8"), TokenResponseModel.class);
} catch (Exception e) {
e.printStackTrace();
}
SomeClass.token = newToken.token;
retryCount++;
call.clone().enqueue(this);
return;
}
}
} else {
onResponse(call, response, true);
removeFinishedRequest();
return;
}
onFailure(call, response, null, false);
removeFinishedRequest();
}
#Override
public void onFailure(Call<T> call, Throwable t) {
if (_isCanceled) return;
if (t instanceof UnknownHostException)
if (eventBus != null)
eventBus.post(new NoConnectionErrorEvent());
onFailure(call, null, t, false);
removeFinishedRequest();
}
My question is: Is there any way to have this sort of centralized response handling behavior before finally chaining (or retrying) back to the subscriber methods?
I found these 2 links which both have a nice starting point but not a concrete solution. Any help will be really appreciated.
Forcing request retry after custom API exceptions in RxJava
Retrofit 2 and RxJava error handling operators
Two links you provided are a really good starting point, which I used to construct solution to react to accidental
network errors happen sometimes due to temporary lack of network connection, or switch to low throughtput network standard, like EDGE, which causes SocketTimeoutException
server errors -> happen sometimes due to server overload
I have overriden CallAdapter.Factory to handle errors and react appropriately to them.
Import RetryWithDelayIf from the solution you found
Override CallAdapter.Factory to handle errors:
public class RxCallAdapterFactoryWithErrorHandling extends CallAdapter.Factory {
private final RxJavaCallAdapterFactory original;
public RxCallAdapterFactoryWithErrorHandling() {
original = RxJavaCallAdapterFactory.create();
}
#Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit));
}
public class RxCallAdapterWrapper implements CallAdapter<Observable<?>> {
private final Retrofit retrofit;
private final CallAdapter<?> wrapped;
public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<?> wrapped) {
this.retrofit = retrofit;
this.wrapped = wrapped;
}
#Override
public Type responseType() {
return wrapped.responseType();
}
#SuppressWarnings("unchecked")
#Override
public <R> Observable<?> adapt(final Call<R> call) {
return ((Observable) wrapped.adapt(call)).onErrorResumeNext(new Func1<Throwable, Observable>() {
#Override
public Observable call(Throwable throwable) {
Throwable returnThrowable = throwable;
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
returnThrowable = httpException;
int responseCode = httpException.response().code();
if (NetworkUtils.isClientError(responseCode)) {
returnThrowable = new HttpClientException(throwable);
}
if (NetworkUtils.isServerError(responseCode)) {
returnThrowable = new HttpServerException(throwable);
}
}
if (throwable instanceof UnknownHostException) {
returnThrowable = throwable;
}
return Observable.error(returnThrowable);
}
}).retryWhen(new RetryWithDelayIf(3, DateUtils.SECOND_IN_MILLIS, new Func1<Throwable, Boolean>() {
#Override
public Boolean call(Throwable throwable) {
return throwable instanceof HttpServerException
|| throwable instanceof SocketTimeoutException
|| throwable instanceof UnknownHostException;
}
}));
}
}
}
HttpServerException is just a custom exception.
Use it in Retrofit.Builder
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(new RxCallAdapterFactoryWithErrorHandling())
.build();
Extra: If you wish to parse errors that come from API (error that don't invoke UnknownHostException, HttpException or MalformedJsonException or etc.) you need to override Factory and use custom one during building Retrofit instance. Parse the response and check if it contains errors. If yes, then throw error and error will be handled inside the method above.
have you consider using the rxjava adapter for retrofit?
https://mvnrepository.com/artifact/com.squareup.retrofit2/adapter-rxjava/2.1.0
in your gradle file add
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
here's a interface for retrofit
public interface Service {
#GET("userauth/login?")
Observable<LoginResponse> getLogin(
#Query("v") String version,
#Query("username") String username,
#Query("password") String password);
}
and here's my implementation
Service.getLogin(
VERSION,
"username",
"password")
.subscribe(new Subscriber<LoginResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(LoginResponse loginResponse) {
}
});
please note I'm using the gson converter factory to parse my response so I get an pojo (Plain Ole Java Object) returned.
See how you can do it.
Here is api call and pass Request model and response model in this.
public interface RestService {
//SEARCH_USER
#POST(SEARCH_USER_API_LINK)
Observable<SearchUserResponse> getSearchUser(#Body SearchUserRequest getSearchUserRequest);
}
This is the retrofit call,I used retrofit2
public RestService getRestService() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiConstants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(getOkHttpClient())
.build();
return retrofit.create(RestService.class);
}
//get OkHttp instance
#Singleton
#Provides
public OkHttpClient getOkHttpClient() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(httpLoggingInterceptor);
builder.readTimeout(60, TimeUnit.SECONDS);
builder.connectTimeout(60, TimeUnit.SECONDS);
return builder.build();
}
This is the api call, call it in your activity.
#Inject
Scheduler mMainThread;
#Inject
Scheduler mNewThread;
//getSearchUser api method
public void getSearchUser(String user_id, String username) {
SearchUserRequest searchUserRequest = new SearchUserRequest(user_id, username);
mObjectRestService.getSearchUser(searchUserRequest).
subscribeOn(mNewThread).
observeOn(mMainThread).
subscribe(searchUserResponse -> {
Timber.e("searchUserResponse :" + searchUserResponse.getResponse().getResult());
if (isViewAttached()) {
getMvpView().hideProgress();
if (searchUserResponse.getResponse().getResult() == ApiConstants.STATUS_SUCCESS) {
} else {
}
}
}, throwable -> {
if (isViewAttached()) {
}
});
}
Hope this will help you.
application interceptor com.blueware.agent.android.instrumentation.okhttp3.d#41c1cec0 returned null
When I used OkHttp3, this error occurred a NullPointerException many times! especially without a network.
Here are the details:
Here are the resource codes where the error occurred:
class ApplicationInterceptorChain implements Interceptor.Chain {
private final int index;
private final Request request;
private final boolean forWebSocket;
ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
this.index = index;
this.request = request;
this.forWebSocket = forWebSocket;
}
#Override public Connection connection() {
return null;
}
#Override public Request request() {
return request;
}
#Override public Response proceed(Request request) throws IOException {
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
}
I solved the problem. It had a conflict with other SDK.
I have a repository class that must return this: Observable<List<SomeObject>,
I do this:
#Override
public Observable<List<SomeObject>> getAllById(Long id) {
if (!AndroidUtils.isNetworkAvailable(mContext))
return Observable.error(new NoNetworkConnectionException());
return mRestService.get(id);
}
This approach works normally, the problem is I want to return custom exceptions
in case of failures, but I don't know the best way to do this with rxjava.
So far, the only solution that works is something like that:
#Override
public Observable<List<SomeObject>> getAllById(Long id) {
if (!AndroidUtils.isNetworkAvailable(mContext))
return Observable.error(new NoNetworkConnectionException());
return Observable.create(subscriber -> {
mRestService.get(id).subscribe(new Observer<List<SomeObject>>() {
#Override
public void onCompleted() {
subscriber.onCompleted();
}
#Override
public void onError(Throwable e) {
if (e instanceof HttpException && ((HttpException) e).code() == 401)
subscriber.onError(new UnathorizedException());
else
subscriber.onError(e);
}
#Override
public void onNext(List<SomeObject> objects) {
subscriber.onNext(objects);
}
});
});
}
I know that is not a good thing to use Observable.create, but I can't figure out
another way to do this.
RestService is this:
public interface RestService {
#GET("objects/{id}")
Observable<List<SomeObject>> get(#Path("id") Long id);
}
If anyone knows a better approach, please tell me.
Thanks!
You can use the operator onErrorResumeNext to map your exception to another one.
mRestService.get(id)
.onErrorResumeNext(e -> {
if (e instanceof HttpException && ((HttpException) e).code() == 401)
return Observable.error(new UnathorizedException());
else
return Observable.error(e);
})
.subscribe();
I did this in a project by adding an interceptor when creating the rest service. This way the errors are checked before the request reaches your rest service.
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.addInterceptor(new ErrorInterceptor());
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
The ErrorInterceptor class looks like
public class ErrorInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Response response = chain.proceed(originalRequest);
if (response.code() >= 400) {
throwError(response);
return response;
} else {
return response;
}
}
private void throwError (Response response) throws IOException {
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (responseBody.contentLength() != 0) {
String responseJSON = buffer.clone().readString(charset);
Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse error = null;
try {
error = gson.fromJson(responseJSON, type);
}
catch (Exception e) {
int a = 1;
}
if (error != null && error.hasErrors())
throw ErrorMapper.mapError(error.getFirstError());
}
}
}
And my ErrorResponse class
public class ErrorResponse {
private List<Error> errors;
public boolean hasErrors () {
return errors != null && errors.size() > 0;
}
public Error getFirstError() {
if (errors == null || errors.size() == 0) return null;
return errors.get(0);
}
}
In my ErrorMapper I just check the error message against a set of possible messages from the server and create a new Error containing the message to display on the client.
I'm just checking the first error here, but you should easily be able to adopt it to multiple errors.
You can try following:
RestService restService = Mockito.mock(RestService.class);
Observable<List<Object>> networkExOrEmpty = isNetworkAvailable() ?
Observable.empty() :
Observable.error(new NoNetworkConnectionException());
Observable<List<Object>> resultOrEx = restService
.getAllById(42L)
.materialize()
.map(res -> {
if (res.isOnError()) {
Throwable err = res.getThrowable();
if (err instanceof HttpException && ((HttpException) err).code() == 401) {
return Notification.createOnError(new UnauthrizedException());
} else {
return Notification.createOnError(err);
}
} else {
return res;
}
}).dematerialize();
Observable<List<Object>> result = networkExOrEmpty.concatWith(resultOrEx);
Start with Observable which emits either Error or nothing, depending on network conectivity state, then concatenate it with result from Retrofit service. Observable.materialize() allows one to act on error items: push appropriate exception downstream, and pass non-error notifications as is.
I've created a method to check if my app is able to connect to my server using OkHttp.
This is my test class:
public class NetworkTest {
static boolean resultWeb = false;
public static boolean pingTestWeb() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.google.com")//My server address will go here
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Request request, IOException e) {
resultWeb = false;
Log.i("Error","Failed to connect: "+e.getMessage());
}
#Override
public void onResponse(Response response) throws IOException {
Log.i("Success","Success: "+response.code());
if (response.code() == 200) {
resulWeb = true;
}
}
});
return resultWeb;
}
And here is where I'm making the test on my activity on OnCreate():
if (NetworkTest.pingTestWeb()) {
// Do something if true...
} else {
// Do something if false, like showing an AlertDialog...
}
The question is, my pingTestWeb have the default Timeout time of 10000ms, how do I make the activity create the AlertDialog only if the pingTestWeb is false? Because it isn't waiting for the response.
You can also use CountDownLatch, in this way you can put async in your test:
public class NetworkTest {
static boolean resultWeb = false;
public static boolean pingTestWeb() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.google.com")//My server address will go here
.build();
CountDownLatch countDownLatch = new CountDownLatch(1);
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Request request, IOException e) {
resultWeb = false;
Log.i("Error","Failed to connect: "+e.getMessage());
countDownLatch.countDown();
}
#Override
public void onResponse(Response response) throws IOException {
Log.i("Success","Success: "+response.code());
if (response.code() == 200) {
resulWeb = true;
}
countDownLatch.countDown();
}
});
countDownLatch.await();
return resultWeb;
}
I think what may be happening here is you're performing the OkHttp call asynchronously, so you're hitting the return statement before the task is complete. For the sake of the test would it be possible to do the OkHttp call synchronously? You can handle the success/failure case with response.isSuccessful seen below.
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("your_url_here")
.build();
Response response = client.newCall(request).execute();
if(response.isSuccessful()){
return true;
}else return false;
}