I have a request call which can return 200 if an user has been subscribed to an event, 204 if the user is not subscribed or 404 if the event no longer exist.
I'm using retrofit 2 and Observables for calling the server.
how can I check If I have a code 200 or 204?
If I got a 404 I know it is an error and I easily deal with it, but I the response is different I don't know how to get the actual code.
mApiEvents.isSubscribed(idEvent, uniqueId )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(getLifecycleProvider())
.flatMap( data-> {
return ?? //How do I check if I got 200 or 204?
}
.subscribe(new LifecycleObserver<Boolean>(){
#Override
public void onNext(Boolean aBoolean) {
}
#Override
public void onError(Throwable e) {
//404 or another error
}
}
)
I finally found it, at the end the solution is quite straight forward.
we have to use a Retrofit2 Response class for wrapping our data.
https://square.github.io/retrofit/2.x/retrofit/retrofit2/Response.html
#POST("some/endpoint")
Observable<Response<Data>> getData(...)
then we can manipulate it with a flatMap and check the Code
getData()
....
.flatMap( response -> {
if(response.code() == 200)
//do something
else
//do something else
}
Not really what you asked, but you can check isEmpty(), because as the body of a 204 is empty, retrofit won't emit any item and you'll receive an onComplete without any onNext.
Related
How to continue executing a Retrofit network call observable that takes in as items as input obtained from another observable that is converted to flatMapIterable even if an error was encountered and stop iterating only if I encounter a specific HTTP status code?
I have a list of JSON requests saved in shared preferences, that I need to send one by one using Retrofit, only stopping whenever I get a certain HTTP status code. If I get other exceptions, I just need to continue sending the next items in the requests list. Whenever a request receives a successful response, I remove that particular request from my request list. If other requests have encountered errors, they do not get removed from the list and I save them again to the shared preferences.
So far, I do this inside a ViewModel object. First, I fetch these requests via a method (paramRepository.getSavedOfflineRequest()) that returns an RxJava Observable<List<Request>>. I want to iterate through all the requests so that I can send the items as inputs to apiService.sale, which is my Retrofit call, so I use flatMapIterable. If the request is successful, I remove the request and save a Transaction object to DB.
public LiveData<UploadStatus> startUploading() {
MutableLiveData<UploadStatus> uploadStatus = new MutableLiveData<>();
compositeDisposable.add(paramRepository.getSavedOfflineRequest()
.doOnComplete(() -> uploadStatus.setValue(UploadStatus.NO_ITEMS))
.flatMapIterable( requests -> requests)
.flatMapCompletable(request -> apiService.sale(saleUrl, BuildConfig.ApiKey,request)
.doOnSuccess(response -> {
requestList.remove(request);
transactions.add(createTransaction(request, response));
}).ignoreElement()
)
.andThen(saveUploadedToDb(transactions))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> uploadStatus.setValue(UploadStatus.SUCCESS),
error -> {
Log.d(TAG, "error");
if (error instanceof HttpException) {
HttpException httpException = (HttpException) error;
int statusCode = httpException.code();
if (statusCode == 401) {
Log.d(TAG, "logged out");
uploadStatus.setValue(UploadStatus.LOGGED_OUT);
}
} else {
uploadStatus.setValue(UploadStatus.FAIL);
}
}));
return uploadStatus;
}
I expect that if if I get other errors/exceptions, I just continue making calls using apiService.sale with the next Request item. But I noticed that the whole chain stops when just one error is encountered, thus the other Requests have not been sent.
I have tried onErrorResumeNext, but it expects a return of another kind of Exception, which is totally different from what I want (do nothing for other Exceptions).
You probably want the observable to return UploadStatus and map the response.
E.g.
.map { response ->
switch(response.code()) {
case 200:
return UploadStatus.SUCCESS;
case 401:
return UploadStatus.LOGGED_OUT;
default:
return UploadStatus.FAIL;
}
}
In the case of an exception, you can use onErrorReturn to return UploadStatus.FAIL/ERROR. This won't terminate the stream.
.onErrorReturn { UploadStatus.FAIL }
So I wanted to implement the example of response from the API like in this video
droidcon NYC 2017 - Advanced Networking with RxJava + Retrofit
And this is my code:
Presenter.java
compositeDisposable.add(RetrofitClient.createService(GetApi.class)
.getResponseFromServer(token)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ResponseFromServer>() {
#Override
public void accept(ResponseFromServer responseFromServer) throws Exception {
mView.setResponseObject(responseFromServer);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
throwable.printStackTrace();
if (throwable instanceof HttpException) {
int responseCode = ((HttpException) throwable).code();
}
}
}));
So here, when I get some 4xx error response from the server, I can go to Throwable and get the response code, and if response is okay I can get my object, and everything is cool.
However, in the video example above, the guy suggest that I wrap my ResponseFromServer with Response like this:
Single<Response<ResponseFromServer>> getResponseFromServer(#Header("X-Authorize") String token); so I can access response codes also, but in that case, my Throwable never gets called, so I can access to the codes only in the first accept method, but in the video he catch the errors in the Throwable section. So, I cant figure it out what I'm doing wrong? Maybe I'm using the wrong Observer?
I think i figured it out, if we wrap our response object with Observable<Response<Object>> all of the response code will be caught in the regular accept method, so we kinda need to extract the codes manually and do the checks. However, if we keep the Observable<Object>, errorCode < 200 || errorCode > 400 will be caught in the onError method.
When Response from Server is code < 200 || code >= 300 in those cases onError() will be invoked. and other cases onNext() will invoke.
Also, If your code from onNext() throws any exception, that will be catch in onError()
I'm still trying to figure out observers in RxJava. I have a Retrofit client, a utility methods and a class that manages a data syncing feature. When I attempt to observe my observables I am not seeing any indication that the observer is subscribed.
My Utility method
public static Single<Response<SyncResponse>> getSyncData() {
Single response = FestApiClient.getInstance().postSyncData()
.observeOn(Schedulers.newThread())
.subscribeOn(AndroidSchedulers.mainThread());
return response;
}
My retorfit client
public Single<Response<SyncResponse>> postSyncData() {
Single response = mFestApiClientService.postEventSyncList("my endpoint");
return response;
}
And my data syncing manager
Disposable syncDisposable = ScheduleUtils.getSyncData().subscribe(syncResponse -> {
if (syncResponse.isSuccessful()){
Log.d(TAG, "Successfully posted events!");
addEventsFromSync(syncResponse.body());
mSyncDialog.dismiss();
} else {
getFailureMessage(syncResponse.body());
Log.d(TAG, "Failed posting events");
}
}, throwable -> {
Log.d(TAG, "Failed posting events");
});
mCompositeDisposable.add(syncDisposable);
I thought that the syncResponse.onFailure and onSuccess methods would be hit, but i never see the log messages or hit break points. I'd appreciate if you let me know if you see anything smelly. thanks!
You are observing on the wrong thread. Switch AndroidSchedulers so that it is on the main thread.
public static Single<Response<SyncResponse>> getSyncData() {
Single response = FestApiClient.getInstance().postSyncData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
return response;
}
My goal
I want to check if the server's token is still valid, let's say I know that information just by calling this getter : preferenceHelper.isTokenValid(). Then, if the token is invalid, calling a request to get a new token and updating the token locally, THEN, proceed with the next request to post the point to the server. That's because I need a valid token in order to make any further server request.
Let say I have those two server request that returns Observable:
This request is meant to get the server token, then upon reception, updating it.
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username,password);
This request is meant to post the current location to the server, then if it succeed, return the saved point
Observable<Response<EntityPoint>> updateServerToken = retrofitApi.postPoint(point);
Issues i'm facing currently:
Both observable that needs to be merged are from different type
Executing the token update request only if it needs to
Waiting for the token update request to complete before executing the request to post points
How should I write my RxJava Observable to satisfy all those condition?
First, I would create a method that checks if the entityToken is valid or not. If valid, use Observable.just() but you have to create an instance of Response somehow. If invalid, then call the server using the API in your requirement retrofitApi.authenticate(). Either path is taken, the method getTokenObservable() emits Observable<Response<EntityToken>>.
public Observable<Response<EntityToken>> getTokenObservable(EntityToken entityToken, String username, String password) {
boolean isTokenValid = preferenceHelper.isTokenValid(entityToken);
if (isTokenValid) {
//my assumption that you have something like this
Response<EntityToken> responseToken = new Response<EntityToken>();
responseToken.setEntityToken(entityToken);
return Observable.just(new Response<EntityToken>(entityToken.class));
} else {
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username, password);
return updateServerToken;
}
}
and then when calling it, use flatMap() which take emisssions of Observable<Response<EntityToken>> and returns emissions of Observable<Response<EntityPoint>>. Subscribe and proceed as normal.
Observable<Response<EntityToken>> updatePointObservable = getTokenObservable(entityToken, username, password);
updatePointObservable
.flatMap(new Func1<Response<EntityToken>, Observable<Response<EntityPoint>>>() {
#Override
public Observable<Response<EntityPoint>> call(Response<EntityToken> responseToken) {
EntityToken entityToken = responseToken.getEntityToken(); //my assumption
saveTokenLocally(entityToken); //this is where you save your token locally, change to the right method that you have
Observable<Response<EntityPoint>> updateServerTokenObservable = retrofitApi.postPoint(point, entityToken); //pass your entityToken to the call?
return updateServerTokenObservable;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<EntityPoint>>() {
#Override
public void onCompleted() {
//your own logic
}
#Override
public void onError(Throwable e) {
//your own logic
}
#Override
public void onNext(Response<EntityPoint> entityPoint) {
//your own logic
}
});
As there is a dependency between the three calls, merge does not make any sense. instead, use flatMap:
Observable<Response<EntityPoint>> response =
retrofitApi.isTokenValid()
.flatMap(isValid ->
isValid
? Observable.just("")
: retrofitApi.authenticate(username,password)
.doOnNext(token -> doSomethingWithTheToken(token)
)
.flatMap(dummy -> retrofitApi.postPoint(point));
I'm trying to upgrade to Retrofit 2.0 and add RxJava in my android project. I'm making an api call and want to retrieve the error code in case of an error response from the server.
Observable<MyResponseObject> apiCall(#Body body);
And in the RxJava call:
myRetrofitObject.apiCall(body).subscribe(new Subscriber<MyResponseObject>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(MyResponseObject myResponseObject) {
//On response from server
}
});
In Retrofit 1.9, the RetrofitError still existed and we could get the status by doing:
error.getResponse().getStatus()
How do you do this with Retrofit 2.0 using RxJava?
Instead of declaring the API call like you did:
Observable<MyResponseObject> apiCall(#Body body);
You can also declare it like this:
Observable<Response<MyResponseObject>> apiCall(#Body body);
You will then have a Subscriber like the following:
new Subscriber<Response<StartupResponse>>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError: %", e.toString());
// network errors, e. g. UnknownHostException, will end up here
}
#Override
public void onNext(Response<StartupResponse> startupResponseResponse) {
Timber.d("onNext: %s", startupResponseResponse.code());
// HTTP errors, e. g. 404, will end up here!
}
}
So, server responses with an error code will also be delivered to onNext and you can get the code by calling reponse.code().
http://square.github.io/retrofit/2.x/retrofit/retrofit/Response.html
EDIT: OK, I finally got around to looking into what e-nouri said in their comment, namely that only 2xx codes will to to onNext. Turns out we are both right:
If the call is declared like this:
Observable<Response<MyResponseObject>> apiCall(#Body body);
or even this
Observable<Response<ResponseBody>> apiCall(#Body body);
all responses will end up in onNext, regardless of their error code. This is possible because everything is wrapped in a Response object by Retrofit.
If, on the other hand, the call is declared like this:
Observable<MyResponseObject> apiCall(#Body body);
or this
Observable<ResponseBody> apiCall(#Body body);
indeed only the 2xx responses will go to onNext. Everything else will be wrapped in an HttpException and sent to onError. Which also makes sense, because without the Response wrapper, what should be emitted to onNext? Given that the request was not successful the only sensible thing to emit would be null...
Inside onError method put this to get the code
((HttpException) e).code()
You should note that as of Retrofit2 all responses with code 2xx will be called from onNext() callback and the rest of HTTP codes like 4xx, 5xx will be called on the onError() callback, using Kotlin I've came up with something like this in the onError() :
mViewReference?.get()?.onMediaFetchFinished(downloadArg)
if (it is HttpException) {
val errorCode = it.code()
mViewReference?.get()?.onMediaFetchFailed(downloadArg,when(errorCode){
HttpURLConnection.HTTP_NOT_FOUND -> R.string.check_is_private
else -> ErrorHandler.parseError(it)
})
} else {
mViewReference?.get()?.onMediaFetchFailed(downloadArg, ErrorHandler.parseError(it))
}