I'm calling an endpoint to GET a list of objects from the server. Using Retrofit2.0 + RxJava for the api calls.
The server has made it in such a way that when there are no items in the list, instead of returning an empty list, it returns null with the response code 204...
If I use an Observable<List<Item>> when the list is empty it will hit onError because of the null body
If I use Completableor Observable<Void> I won't be able to handle any data returned....
I thought about handling this by verifying the error message and assuming that if is an null exception to continue with the normal flow but I'm not fully comfortable with this hack....
Is there a way to handle this situation?
204 : No content means that the query successfully processed but no information to return.
You can use this in OnError method to have more visibilty :
#Override
public void onError(Throwable e) {
try {
Log.e("errorL", ((HttpException) e).response().errorBody().string());
} catch (IOException e1) {
e1.printStackTrace();}}
Good luck !
Related
I have a requirement wherein I have to send saved API requests on a button click. These API requests are added to a list and this list is saved to SharedPreferences if the device is offline. Once the device regains connectivity, the saved requests should be sent on a click of a button. If one of the requests get a HTTP status code of 401, the whole process should stop. However, in case of other Exceptions, the process should not interrupted and the next saved request on the list should be sent. If a request succeeds, it is removed from the list of saved requests. At the end of the process, any requests that remain unsent are saved to SharedPreferences.
Now I have a special case for an Exception that I call InvalidRequestException. I want to remove the request from the list when it encounters this particular error, and at the same time I want to carry on sending the remaining requests in the list.
I modeled my code from this post. Here is the code for the method that kicks off the whole process:
public LiveData<UploadStatus> startUploading() {
MutableLiveData<UploadStatus> uploadStatus = new MutableLiveData<>();
compositeDisposable.add(paramRepository.getSavedOfflineRequest() // returns Observable<List<Request>>
.doOnComplete(() -> uploadStatus.setValue(UploadStatus.NO_ITEMS))
.flatMapIterable( requests -> {
requestList = requests;
requestListSizeText.set(Integer.toString(requestList.size()));
return requestList;
}) // observable should now be Observable<Request>
.flatMapCompletable(this::uploadProcess)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() ->{
paramRepository.setOfflineRequestString(""); // clear saved offline requests from shared preferences
uploadStatus.setValue(UploadStatus.SUCCESS);
},
error -> {
if (error instanceof SessionExpiredException) {
uploadStatus.setValue(UploadStatus.LOGGED_OUT);
} else {
if(!requestList.isEmpty()) {
paramRepository.saveRequestsToPrefs(requestList);
} else {
paramRepository.deleteSavedRequests();
}
uploadStatus.setValue(UploadStatus.FAIL);
}
}
)
);
return uploadStatus;
}
The actual sending of saved requests happens in uploadProcess. This is where I attempt to catch the occurrence of InvalidRequestException and delete the request that encounters it:
private Completable uploadProcess(Request request) {
return apiService.transact(saleUrl, BuildConfig.ApiKey,request)
.doOnSubscribe(disposable -> {
uploadAttempts++;
})
.toMaybe()
.onErrorResumeNext(error -> {
if(error instanceof InvalidRequestException) {
requestList.remove(request);
if(requestList.isEmpty()) {
return Maybe.error(new OfflineTxnsNotUploadedException());
}
}
else if (error instanceof SessionExpiredException) // inform UI that session has expired
return Maybe.error(error);
else if (requestList.size() == uploadAttempts) { // nothing was uploaded
return Maybe.error(new OfflineTxnsNotUploadedException());
}
return Maybe.empty();
})
.flatMapCompletable(response -> {
requestList.remove(request);
successCount++;
successCountText.set(Integer.toString(successCount));
return createTransaction(request, response);
});
}
Now when I tested this, I found out that the whole stream stops whenever InvalidRequestException is encountered, which is not the behavior I want. I want to continue sending the other requests in the list. I actually removed the part where the request is removed from the list (requestList.remove(request);), and the stream continued and the next request was sent via apiService.transact().
Am I mistaken in assuming that returning Maybe.empty() would resume the emission of Observable<Request> from the flatMapIterable?
EDIT: It seems I am encountering a ConcurrentModificationException, that's why the stream terminates immediately and the other Requests are not sent. I will have to study this exception first.
As I noted in my edit, I wasn't able to catch the ConcurrentModificationException, thus the entire stream was interrupted. I was in fact, modifying the List<Request> that is being emitted into individual Observable<Request>, since requestList is only a shallow copy of the List<Request> emitted by getSavedOfflineRequest.
One solution I tried was to first serialize the list via Moshi, so that the unserialzied list will not hold any reference to the original list. I found out soon enough that requestList.remove(request) will return false, since I had yet to properly override the equals() and hashCode() method for the Request class. In the end, I just settled to create a "failed requests" list, adding Requests to this list whenever an error is encountered.
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.
i am building my app on android repository by Fernando Cejas and i have a problem with subscribing to observable after calling dispose.
When i come to dashboard, i call method subscribeOnUserMessages.execute(new Subscriber(), new Params(token)), which is method in UseCase class
public void execute(DisposableObserver<T> observer, Params params) {
Preconditions.checkNotNull(observer);
final Observable<T> observable = this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
addDisposable(observable.subscribeWith(observer));
}
In child class SubscribeOnUserMessages i simply call repository like this
return messageRepository.subscribeOnUserMessages(params);
In my socket implementation i create like this
return Observable.create(emitter -> {
if (!isThereInternetConnection()) {
Timber.w("Network connection exception");
emitter.onError(new NetworkConnectionException());
return;
}
/*
* Open socket if not opened
*/
openSocket(params.getToken());
String channelName = CHANNEL_PRIVATE_USER + params.getAuthenticated().getUuid();
if (subscribedChannels.contains(channelName)) {
Timber.d("Channel %s is already subscribed", channelName);
return;
}
JSONObject auth;
try {
auth = createAuthJson(CHANNEL, channelName, params.getToken());
} catch (JSONException e) {
Timber.e("Couldn't create auth json");
emitter.onError(e);
return;
}
mSocket.emit(SUBSCRIBE, auth);
Timber.d("Emitted subscribe with channel: %s ", CHANNEL_PRIVATE_USER + params.getAuthenticated().getUuid());
subscribedChannels.add(CHANNEL_PRIVATE_USER + params.getAuthenticated().getUuid());
Timber.d("Subscribing on event: %s\n with user: %s", EVENT_USER_NEW_MESSAGE, params.getAuthenticated().getUuid());
if (mSocket.hasListeners(EVENT_USER_NEW_MESSAGE)) {
Timber.v("Socket already has listener on event: %s", EVENT_USER_NEW_MESSAGE);
return;
}
mSocket.on(EVENT_USER_NEW_MESSAGE, args -> {
if (args[1] == null) {
emitter.onError(new EmptyResponseException());
}
Timber.d("Event - %s %s", EVENT_USER_NEW_MESSAGE, args[1].toString());
try {
MessageEntity messageEntity = messageEntityJsonMapper.transform(args[1]);
emitter.onNext(messageEntity);
} catch (JSONException e) {
Timber.e(e, "Could not parse message json");
emitter.onError(e);
}
});
});
Symptoms are that first time i subscribe everything is going through to presentation layer. When i dispose after going to second screen and come back i only see logs coming to socket implementation, but not going through.
My question is: Is there a method for subscribing to same observable again? I've already tried to save that observable in my use case in singleton and subscribe to that observable, didn't help.
Without additional info and details regrading socket implementation it is hard to spot the problem exactly, but, from the code you've posted, you don't have dispose logic, so while you might properly call dispose() to the Observable at the correct lifecycle event, your socket will actually stay open, and it might not got disconnected/closed properly ever.
That might lead to a problems opening and connecting to the socket at the 2nd time, as you might try to reopen already open socket and depends on your internal socket impl that might be a problem.
(I can see in the comment that openSocket if not already opened, but still there might be problem elsewhere calling some method on the socket multiple times or setting listeners, again depends on the socket impl)
As a general guidelines, you should add dispose logic using emitter.setCancellable()/emitter.setDisposable() in order to dispose properly the socket resources when you no longer need them, thus - when applying subscribe again (whether the same object or not) will invoke your subscription logic again that will reopen the socket and listen to it.
It is not clear to me if you like to keep the socket open when you moving to a different screen (I don't think it is a good practice, as you will keep this resource open and might never get back to the screen again to use it), but if that's the case as #Phoenix Wang mentioned, you can use publish kind operators to multicast the Observable, so every new Subscriber will not try to reopen the socket (i.e. invoking the subscription logic) but will just get notify about messages running in the already opened socket.
Following the topic discussed here. I'm coding an Android App using the Clean Architecture. I've an Interactor that takes care of retriving the User's feed data. The flow is like this:
I must fetch the Feed data from the a Repository which calls a Retrofit's service to do the API call.
If something goes wrong I've to fetch the feed data from a FeedCache that internally works with Sqlite.
I've to merge this feed collection with another bunch of feeds from another cache called PendingPostCache. This cache contains all the articles that the user couldn't post (because something went wrong, didn't had internet connection, etc.)
My FeedCache and PendingPostCache both work with Sqlite. Botch can throw DBExceptions if something went wrong. My FeedRepository the ones that makes the requests against the server-side can also throw exceptions if something goes wrong (ServerSideException).
Here's the whole code from my Interactor:
mFeedRepository.getFeed(offset, pageSize) //Get items from the server-side
.onErrorResumeNext(mFeedCache.getFeed(userSipid)) //If something goes wrong take it from cache
.mergeWith(mPendingPostCache.getAllPendingPostsAsFeedItems(user)) //Merge the response with the pending posts
.subscribe(new DefaultSubscriber<List<BaseFeedItem>>() {
#Override
public void onNext(List<BaseFeedItem> baseFeedItems) {
callback.onFeedFetched(baseFeedItems);
}
#Override
public void onError(Throwable e) {
if (e instanceof ServerSideException) {
//Handle the http error
} else if (e instanceof DBException) {
//Handle the database cache error
} else {
//Handle generic error
}
}
});
I don't like having those instanceof. I'm thinking on creating a custom subscriber, something called like MyAppSubscriber, which implements the onError method, makes those instanceof comparations, and execute some methods called onServerSideError(), onDBError(). That way the code is going te be a lot cleaner and I can spare writing that instanceof boilerplate code. Has someone a better idea about how to approach this issue? Some way to avoid the custom Subscriber?
Just use composition:
public <T,E> Function<Throwable, Observable<T>> whenExceptionIs(Class<E> what, Function<E, Observable<T>> handler) {
return t -> {
return what.isInstance(t) ? handler.apply(what.cast(t)) : Observable.error(t);
};
}
Then you use it normally :
Observable.from(...).flatMap(...)
.onErrorResumeNext(whenExceptionIs(ServerSideException.class, e-> Observable.empty()))
.onErrorResumeNext(whenExceptionIs(DBException.class, e-> ...))
You can even abstract all that in one method:
public <T> Transformer<T, T> errorHandling() {
return src -> src
.onErrorResumeNext(whenExceptionIs(ServerSideException.class, e-> Observable.empty()))
.onErrorResumeNext(whenExceptionIs(DBException.class, e-> ...));
}
Observable.from(...).flatMap(...)
.compose(errorHandling())
.subscribe();
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())