Conditional subsequent request with RxJava - android

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())

Related

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

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.

How to terminate sequence of switch maps in RxJava2 on error

I am trying to implement a refresh token flow in RxJava2 and Kotlin and I have trouble handling errors. There are several requests that need to be done in sequence but the sequence differs in case there are some errors.
Basically, if I try to use my refresh token and receive 400 - Bad Requestresponse because the token is not valid, I need to terminate the flow and do not execute the next switchMap (and ideally, I would like to return the final Observable<ApplicationRoot>). But I am not sure how to achieve this.
If I use onErrorReturn, I will just pass the returned result to the next switch map. And doOnError just executes the step when the request fails but the whole sequence continues.
fun refreshToken(): Observable<ApplicationRoot> {
// try to use the refresh token to obtain an access token
return authRepository.refreshToken(token)
.switchMap { response ->
// process response here
userRepository.getUser() // fetch user details
}.doOnError {
// TODO - return final result, do not jump to next switchMap
// refresh token is not valid -> go to login screen
Observable.just(ApplicationRoot.LOGIN) // not working
}.switchMap { response -> // excpects response of type UserResponse
// save user details here
}
}
Does anyone know who to jump out of the sequence of switch maps if some error occurs?
Probably, you should do something like this:
fun refreshToken(): Observable<ApplicationRoot> {
return authRepository.refreshToken(token)
.switchMap { response ->
userRepository.getUser()
}
.switchMap { response -> // do something with user response
}
.map { ApplicationRoot.ROOT_THAT_MEANS_SUCCESS }
.onErrorResumeNext(Observable.just(ApplicationRoot.LOGIN))
}
I am not aware about implementation of authRepository.refreshToken and how do responses look like, but in case if response is your custom object rather than retrofit2.Response<T> it should work.

Android JWT refresh token flow

I'm implementing codes with jwt on Android.
At point of using refresh token, I'm not sure my code is correct way.
Here is sequene diagram of my flow.
Server issued access token and refresh token. These expire time is 1hour and 3 days. These token is saved to sharedpreferences.
Here is above diagram's description.
When access token is expired, http call will be failed with 401 error.
So I implemented getAccessToken() for re-newing access token.
(1) : One AsyncTask is used for this whole http call step.
- My AsyncTask is too big, I want to refactor it.
(2) : (1)'s AynsTask has a logic for re-getting access token.
- This logic was duplicated all my HTTP call functions.
(3) : After renewing access token, my app re-try to call /api/foo
- To retry it, AsyncTask's doBackground() function is call recursivly.
Here is my code snippet.
class ApplyCheck extends AsyncTask<String, Void, ResponseTypeEnum> {
private List<ApplyEntity> applyEntityList = null;
#Override
protected ResponseTypeEnum doInBackground(String... strings) {
try {
response = restManager.getApplyList(strings[0],"","",""); // call /api/foo
} catch (RestRuntimeException e) {
return ResponseTypeEnum.SERVER_ERROR;
}
switch (response.code()) {
case 200:
//set applyEntityList
....
return ResponseTypeEnum.SUCCESS;
case 401:
//<-- This routine is duplcated all my AsyncTasks
if(getAccessToken()) {
//<-- recursive call to re-call api
return doInBackground(strings);
} else {
return ResponseTypeEnum.TOKEN_EXPIRE;
}
}
}
//re-issue new access token
private boolean getAccessToken() {
Response response = restManager.getAccessToken(); // call /auth/issue-token
if(response.code() == 200) {
String tokens = response.body().string();
JSONObject jsonObject = new JSONObject(tokens);
sharedPreferences.edit().putString("accessToken", jsonObject.getString("accessToken"));
sharedPreferences.edit().putString("refreshToken", jsonObject.getString("refreshToken"));
return true;
} else {
return false;
}
My Questions
1. Is my approach correct? If not, please inform me good practice.
2. If yes, are any good practice for extracting common function for my duplicated AsyncTasks?
The process you have is fine IMHO. The only change is that I would not recursively call doInBackground. What you're doing is feasible, but it violates the intention of doInBackground. Rather modify your AsyncTask to cope with processing different responses in onPostExecute, (ie chaining your requests), and call the AsyncTask again with the relevant parameters for each use case. It will make it much easier to maintain as you can add specific methods to the AsyncTask to cope with each response type and can see how it's triggered in a linear way. If you need to update onProgressUpdate, you should also pass a progress value to the chained AsyncTask calls so it can maintain consistency on the progress. Otherwise it would keep restarting on each call.

RxJava concatMap no response

Hope you guys are doing well,
I have been working on a personal Android Project using RxJava and Retrofit. It's a search request for GitHub issues, I am getting input from the user when he clicks Search Button and using a PublishSubject object to emit the search text.
button.setOnClickListener(view -> {
publishSubject.onNext(editText.getText().toString());
});
and I am mapping this emit to an Observable using retrofit like this
publishSubject.concatMap(dataModel::getIssues)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::loadData, this::onError);
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open");
}
In result I am expecting List of Issues
public void loadData(List<Issue> issues) {
mProgressDialog.setVisibility(View.INVISIBLE);
if( issues.size() == 0) {
noIssueText.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
mIssuesList.clear();
mIssuesList.addAll(issues);
mAdapter.notifyDataSetChanged();
}
}
But my code seems to have some implementation issue Since it never emits anything from the network, not even on error is called.
I have tested the same example with the Observable I get from Retrofit API, so there is no retrofit error and so I think there is some problem with my concatMap logic.
Any help will be much appreciated
On first parse, I think that you might be making the network call in the main thread. Have you tried the following?
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open")
.subscribeOn(Schedulers.io());
}
Thing is, your onClickListener callback runs on the main thread, and there's no other context switch in the observable pipeline.

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.

Categories

Resources