[Android][Retrofit] Using interceptor to call different API - android

I am working on an Android project which uses retrofit to handle network calls. I have a hard time figuring out a use case.
I have an API (api1) which has already been implemented and is being called from multiple places.
Now, I need to call a new API (api2) before calling api1.
What would be the best way of doing this ?
Can I use interceptors for this purpose ? Are interceptors the best way to handle this use case ?
public class MyApi2Interceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
// original request
Request request = chain.request();
val api2Response = api2.execute()
if (api2Response.code() == 200) {
return chain.proceed(request);
} else {
return null;
}
}
}
Or
fun suspend callApi1() {
return api2.execute()
.map { api2Response ->
if (api2Response.code() == 200) api1.execute()
else return null
}
}
I personally like the interceptor approach I feel its clean, but not sure if interceptors are used for this purpose. Also which interceptors should I use addInterceptor or addNetwrokInterceptor (I guess in my case I can add them in any one of them ?)
I haven't actually tried out yet on my project and I am not sure if executing a different api in interceptor would actually work.
Please let me know your thoughts on this. Thanks in advance.

The second approach is more favorable as using interceptor would shadow the logic inside the interceptor and no one else would know about it. Also retrofit instances are usually created for single single service, this logic should be also handled in a business component as APIs are a data layer.

Related

How to make Parallel multiple non-blocking service request with RxJava2 & Retrofit2

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.

How to use RxJava to wait the end of two lists of Retrofit requests

Let me describe my situation:
I want to register new records via an API.
I want to update some records via an API.
I need to be notified when all of these requests have finished, to start another task.
Specifically I have two ArrayList:
ArrayList<Report> createdReports = myHelper.getOfflineCreatedReports();
ArrayList<Report> editedReports = myHelper.getOfflineEditedReports();
Each report can use methods to get Observable instances from my ApiService (Retrofit implementation).
Observable<NewReportResponse> createdReportsObs = Observable.from(createdReports) // .just() != .from()
.flatMap(new Func1<Report, Observable<NewReportResponse>>() {
#Override
public Observable<NewReportResponse> call(Report report) {
return report.postToServer();
}
});
Observable<NewReportResponse> editedReportsObs = Observable.from(editedReports)
.flatMap(new Func1<Report, Observable<NewReportResponse>>() {
#Override
public Observable<NewReportResponse> call(Report report) {
return report.updateInServer();
}
});
I am using the flatMap operator to get one Observable for each report.
But I am not sure how to wait until all of the requests have finished.
I was thinking in using the zip operator.
Observable.zip(createdReportsObs, editedReportsObs, new Func2<NewReportResponse, NewReportResponse, Boolean>() {
#Override
public Boolean call(NewReportResponse justOneResponse, NewReportResponse justOneResponse2) {
return false;
}
});
Unfortunately I saw some examples where zip is used to create pairs of Observables.
Please suggest me what operator I can use to achieve it. Or how to do it using rxJava with a different approach.
Thank you in advance.
Are you using RxJava 2? If so you can use the new completable api. This is assuming you don't need to know any of the server results, just need to wait for them to complete.
Completeable.merge(createdReportsObs.toCompleteable(),
editedReportsObs.toCompleteable())
.subscribe()
This is my way. May not best practice.
Observable.merge(createdReportsObs, editedReportsObs)
.toList()
.flatMap(Observable::from)
.xxx //Now they are completed, do what you want
.subscribe();

Creating custom rx.Observable and rx.Subsciber

I'm working on an Android App. I'm using Retrofit to manage the http request to the server-side endpoints. Currently when I'm doing a request I'm doing something like this:
Observable<List<ApiFeedResponse>> feedObservable = mFeedRepository.getFeed(0, 50)
.flatMap(apiFeedsResponse -> {
if (apiFeedsResponse.code() != 200) {
if (apiFeedsResponse.code() == 304) {
List<ApiFeedResponse> body = apiFeedsResponse.body();
return Observable.just(body);
} else {
return Observable.error(new ServerSideErrorException(apiFeedsResponse));
}
} else {
return Observable.just(apiFeedsResponse.body());
}
});
My FeedRepository calls the Retrofit service. I've an endpoint that is like myhost.com/rest/userfeed?page=0&pageSize=50. The thing is that I'm also using etags to get cached server-side responses. And I want to be able to differentiate between a normal 200 http response and a "not modified" 304 response. I want to extend the rx.Subscriber lifecycle methods (onNext, onError and onComplete) to be something like (onSuccess, onServerError, onNotModified, onServerResult). That way when I subscribe to this methods is going to look like this:
getFeed(0, 50).subscribe(new ServerSubscriber<List<Feed>>() {
//Executed when the response is 200
#Override
protected void onSuccess(List<Feed> feed) {
}
//Executed when the response is 304
#Override
protected void onNotModified(List<Feed> feed) {
}
//Executed if something goes wrong while doing the http request (code is different than 200 or 304)
#Override
protected void onServerSideError(ServerSideErrorException e) {
}
//Executed always that the result of the http request is successfull (200 or 304)
#Override
protected void onServerResult(List<Feed> feed) {
}
});
I have been looking through different repos trying to find if someone has done something similar and the closes thing that I found was this: https://github.com/ReactiveX/RxJava/issues/1034
But I still can't fully understand how to implement custom rx.Observables and custom rx.Subscribers. Any advice is welcome.
Why not repackage your logic in a reusable form?
public <T> Transformer<Response<T>, T> applyCache(
Supplier<T> src,
Consumer<Response<T>> sink) {
return responseSrc -> responseSrc.flatMap(response -> {
switch(response.code()) {
case 200:
sink.accept(response);
return Observable.just(apiFeedsResponse.body());
case 304:
return Observable.just(src.get());
default:
return Observable.error(new ServerSideErrorException(apiFeedsResponse));
}
}
Just add the cache get/set functions (and adjust to taste); use like:
mFeedRepository
.getFeed(0, 50)
.compose(applyCache(feedCache::get, feedCache::set)
I think the solutions to your problems can be fixed in Retrofit/OkHttp.
Retrofit2 uses OkHttp3 under the hood to execute the API calls. OkHttp can handle the 304 not modified status code and deliver you the result from cache. To do that you need to set up retrofit to use a custom OkHttp client with cache.
For more custom callbacks there the solution is custom CallAdapter. There is an example for that in the retrofit repo which looks similar to yours. RxJava already uses a call adapter, maybe you can base is on that.

Do we have any possibility to stop request in OkHttp Interceptor?

In our app we met with one special case - if our App.specialFlag == true, we need stop any request from our code. And we think, that the best approach in this situation is include special Interceptor which will stop any our requests, something like this:
if (App.specialFlag) {
// Somehow stop request
} else {
return chain.proceed(chain.request());
}
Also, we should note, that we use RxJavaCallAdapterFactory for wrapping responses into Observables.
But we don't see any methods for stopping request in OkHttp Interceptors.
We came up with two ways to solve our issue:
a) Create special ApiService.class for wrapping every request in our API like this:
public Observable<User> getUserDetails() {
if (App.specialFlag) {
return Observable.just(null);
}
return api.getUserDetails();
}
But we think that it is ugly and cumbersome solution.
b) Throw a RuntimeException in Interceptor, then catch it in every place we use our API.
The second solution is also not good, because we need to include a lot of onErrorResumeNext operators in our chains.
May be someone knows, how to handle our situation in more 'clever' way?..
Thanks in advance!
One thing missing in the accepted answer is that you need to specify the protocol and the message. If you don't specify that you will get an Exception. Looking like this:
if (App.specialFlag) {
return new Response.Builder()
.code(418) // Whatever code
.body(ResponseBody.create(null, "")) // Whatever body
.protocol(Protocol.HTTP_2)
.message("Dummy response")
.request(chain.request())
.build();
} else {
return chain.proceed(chain.request());
}
One way to prevent request from being executed(before it starts) it to simply do as you tried to, but with an addition of Response.Builder:
if (App.specialFlag) {
return new Response.Builder()
.code(600) //Simply put whatever value you want to designate to aborted request.
.protocol(Protocol.HTTP_2)
.message("Dummy response")
.request(chain.request())
.build();
} else {
return chain.proceed(chain.request());
}
EDIT 26/09/2019
Updated answer with details mentioned by Bobstring
I would suggest to put the control logic script outside of the OkHttp, since it is used for making http request only. How to trigger request and how to handle the response to update the UI, it doesn't need to care about;
If you wanna, you may create a Class inherited from okhttp3.Interceptor, then override the function intercept and put your control logic script inside.
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.addInterceptor(new Interceptor())
.build();

Conditional subsequent request with RxJava

I have an issue with my network client design. I have a use case, when the client tries to request an item from a REST API, but in case the API returns a 404 HTTP status code I need to send a request to create the item on the server and then request the item again.
I would like to use RxJava to avoid the callback hell. Is this a valid use case RxJava? Is it possible to create such a conditional sub-request?
Thank you for your time and answers.
Based on your question, I assume you have something that look like
public Observable<Item> getItem();
that will either return the item, or fire an error and
public Observable<?> createItem();
That will create one.
You can use those two together like so:
public Observable<Item> getOrCreateItem() {
return getItem().onErrorResumeNext(error -> {
// Depending on your framework, figure out which is the result code
if (error.getResultCode() == 404) {
return createItem().flatMap(ignored -> getItem());
} else {
return Observable.error(error);
}
});
}
With Retrofit, you'd have to simply make sure the exception is a RetrofitError, cast it, and get the response and the status code. (((RetrofitError) error).getResponse().getStatus())

Categories

Resources