Retrofit 2 and rxJava with Flowable concat handle several response - android

I'm using retrofit, rxJava and realm I'm using flowable concat for launch a request to the server and another one for realm
This is how I'm creating the disposable :
disposable.add(launchRequest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Config>() {
#Override
public void accept(Config config) throws Exception {
ProfileManager.getInstance().setConfig(config);
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
if (!throwable.getMessage().equals(ErrorUtils.ERROR_CODE_304)) {
throwable.printStackTrace();
}
}
}));
launchRequest function create the Flowable :
Flowable.concat(cloudDataStore.getById(single, diskDataStore, repositoryItem).toFlowable(), diskDataStore.getById(id, repositoryItem).toFlowable())
.map(new Function<Object, T>() {
#Override
public T apply(Object o) throws Exception {
return repositoryItem.toView((T) o);
}
});
cloudDataStore param is the retrofit part and diskDataStore is the realm part.
Everything workings fine here, my trouble is when I handle the retrofit request :
return single.flatMap(new Function<Response<T1>, SingleSource<T1>>() {
#Override
public SingleSource<T1> apply(#NonNull Response<T1> response) throws Exception {
if (response.code() == Integer.valueOf(ErrorUtils.CODE_200)) {
return Single.just(response.body());
} else if (response.code() == Integer.valueOf(ErrorUtils.ERROR_CODE_304)) {
return Single.just(response.body());
} else {
return Single.error(new Throwable(String.valueOf(response.code())));
}
}
});
If the request is sucessfull (status 200) I return the server response to my disposable.
If I got a code 304 the response body is null so the disposable throwable is trigger, but if the throwable is trigger the disposable don't wait the realm response from concat and stop listening.
The workaround I find is to create a empty Object and return it like this :
if (response.code() == Integer.valueOf(ErrorUtils.ERROR_CODE_304)) {
return Single.just(new Config());
}
This trigger the disposable consumer with an empty object and I can after get the realm result with the good value because the throwable is not trigger.
But I don't want to receive this empty result I can do nothing with it and I need for all request to check if the content is not null like this :
.subscribe(new Consumer<Config>() {
#Override
public void accept(Config config) throws Exception {
if (config.getContent != null){
ProfileManager.getInstance().setConfig(config);
}
}
}
How can I return something with Single that don't trigger the consumer and the throwable ?

Thanks to EpicPandaForce the solution was to change the Single into Maybe like this :
.flatMap(new Function<Response<T>, Maybe<T>>() {
#Override
public Maybe<T> apply(#NonNull Response<T> response) throws Exception {
if (response.code() == Integer.valueOf(ErrorUtils.CODE_200)) {
return Maybe.just(response.body());
} else if (response.code() == Integer.valueOf(ErrorUtils.ERROR_CODE_304)) {
return Maybe.empty();
} else {
return Maybe.error(new Throwable(String.valueOf(response.code())));
}
}
});

Related

RxAndroid Emitter disposed without complete/error

I am having an issue using RxJava on Android to read from async I/O. I create a Flowable and subscribe to an emitter. It works the first time, calling onNext and then onComplete when finished, but when I try the same operations again(create a new Flowable and subscribe), I get an UndeliverableException if I intentionally throw an I/O error. If I do an emitter.isCancelled, it returns true. Not sure why this happens.
Flowable getFlowable(){
Flowable.create(new FlowableOnSubscribe<Object>() {
#Override
public void subscribe(FlowableEmitter<Object> emitter) throws Exception {
getIOResponse(new IOListener() {
#Override
public void onInfo(Object ioResponse) {
emitter.onNext(ioResponse);
emitter.onComplete();
}
#Override
public void onError(Exception e) {
emitter.onError(e); //Throws UndeliverableException, emitter already cancelled
}
}
}
}
}
static void subscribe(){
disposable = getFlowable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(TIME_OUT_SECONDS, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
#Override
public void accept(Object myObject) throws Exception {
someListener.onSuccess(myObject);
}
}, throwable -> {
someListener.onError(throwable);
}, () -> {
Log.d("****", "Completed");
});
}

How to do a Throwable call in a flatMap in RxJava2?

I'm working with RxJava2 and I was doing simple request, I should do it like the next example:
getCompositeDisposable().add(subscriptionManager.getSubscriptions(getUserAuth().getToken()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<HttpCustomRes<List<GetSubscriptions>>>() {
#Override
public void accept(#NonNull HttpCustomRes<List<GetSubscriptions>> listHttpCustomRes) throws Exception {
getMvpView().hideLoading();
ErrorCode errorCaught = bypassForErrors(listHttpCustomRes.getError());
if(errorCaught.equals(ErrorCode.SUCCESSFUL_REPSONSE))
getMvpView().showSubscriptions(listHttpCustomRes.getData());
else if(errorCaught.equals(ErrorCode.INVALID_TOKEN) || errorCaught.equals(ErrorCode.NULL_TOKEN))
getMvpView().showLogin();
else
getMvpView().showErrorDialog();
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
getMvpView().hideLoading();
getMvpView().showErrorDialog();
}
}));
Now, I need to do two sequential request, so I'm using the flatMap to do this. In the simple request I have the second call to the method new Consumer to catch errors, but with the flatMap I don't know how to do this. I post the code in the next lines.
getCompositeDisposable().add(accountUserManager.getUserData(getUserAuth().getToken()).flatMap(new Function<UserData, Flowable<HttpCustomRes<List<GetSubscriptions>>>>() {
#Override
public Flowable<HttpCustomRes<List<GetSubscriptions>>> apply(#NonNull UserData userData) throws Exception {
setUserData(userData);
return subscriptionManager.getSubscriptions(getUserAuth().getToken());
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<HttpCustomRes<List<GetSubscriptions>>>() {
#Override
public void accept(#NonNull HttpCustomRes<List<GetSubscriptions>> listHttpCustomRes) throws Exception {
getMvpView().hideLoading();
ErrorCode errorCaught = bypassForErrors(listHttpCustomRes.getError());
if(errorCaught.equals(ErrorCode.SUCCESSFUL_REPSONSE))
getMvpView().showSubscriptions(listHttpCustomRes.getData());
else if(errorCaught.equals(ErrorCode.INVALID_TOKEN) || errorCaught.equals(ErrorCode.NULL_TOKEN))
getMvpView().showLogin();
else
getMvpView().showErrorDialog();
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
getMvpView().hideLoading();
getMvpView().showErrorDialog();
}
})
);
As you can see, I have a throwable in the second method, but not in the first, so if the first crash, the app will crash too. How I can implement the throwable to the first request?
Thank you.
All unchecked exception (well, almost all) will be delivered to the onError handler in the subscriber which is the second Consumer in your subscribe method.
Thus in your case, both exception in the source getUserData Observable and the flatMap mapping function will be handled by the stream and will be delivered to the onError handler.
new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
try{
getMvpView().hideLoading();
getMvpView().showErrorDialog();
}catch(Exception e){
throw e;
}
}
})

How to change parameters in retry request after error in RxJava

I send a login request to server with retrofit 2.0, and server return to the client session token, wich I must use in other requests, but this token has limited life-time, and when it is expire server returns HTTP error 401.
I try make re-logon, after getting this error, with help a next code:
holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(holder.getServerId()), holder.getServerToken())
.subscribeOn(Schedulers.io())
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof HttpException && ((HttpException)throwable).code() == 401) {
RegistryLoginResult loginResult = holder.login().blockingSingle();
return holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(loginResult.getUserId()), loginResult.getSessionToken());
}
return Observable.error(throwable);
}
});
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ProfileResult>() {
#Override
public void accept(ProfileResult profileResult) throws Exception {
Log.d("Result", profileResult.toString());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e("Result", throwable.getLocalizedMessage());
}
});
And retry request is sent, but parameters of request are same as in incorrect request (before re-login). How I can change parameters of the request before sending it again?
You can use retryWhen, but the problem is that your retryWhen retry the same observable object that you create in lazy moment.
Your solution here is use the operator defer to get the host(), since defer it´s not creating the observable when you define it but when it´s consumed by the subscribed.
Observable.defer(()-> holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(holder.getServerId()),holder.getServerToken()))
.subscribeOn(Schedulers.io())
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof HttpException && ((HttpException)throwable).code() == 401) {
RegistryLoginResult loginResult = holder.login().blockingSingle();
return holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(loginResult.getUserId()), loginResult.getSessionToken());
}
return Observable.error(throwable);
}
});
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ProfileResult>() {
#Override
public void accept(ProfileResult profileResult) throws Exception {
Log.d("Result", profileResult.toString());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e("Result", throwable.getLocalizedMessage());
}
});
You can see some examples of retry here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java
You're using the wrong operator. retryWhen will retry your original observable if it encounters an error. What you need is onErrorResumeNext. Something like
holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(holder.getServerId()), holder.getServerToken())
.subscribeOn(Schedulers.io())
.onErrorResumeNext(new Function<Throwable, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Throwable throwable) {
if (throwable instanceof HttpException && ((HttpException)throwable).code() == 401) {
RegistryLoginResult loginResult = holder.login().blockingSingle();
return holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(loginResult.getUserId()), loginResult.getSessionToken());
}
return Observable.error(throwable);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ProfileResult>() {
#Override
public void accept(ProfileResult profileResult) throws Exception {
Log.d("Result", profileResult.toString());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e("Result", throwable.getLocalizedMessage());
}
});

How to do unit testing for retrofit2 callbacks?

I want to do an unit test that verifies if function1() or function2() were called. I haven't work with callbacks before, can you give me any idea about how to do it?
public void sendData(HttpService service, Document userData) {
Call<String> call = service.updateDocument(getId(), userData);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
function1(response.code());
}
#Override
public void onFailure(Call<String> call, Throwable t) {
function2();
}
});
}
I couldn't try, but it should work. Maybe you have to fix generic type
casting errors like mock(Call.class);.
#Test
public void should_test_on_response(){
Call<String> onResponseCall = mock(Call.class);
doAnswer(invocation -> {
Response response = null;
invocation.getArgumentAt(0, Callback.class).onResponse(onResponseCall, response);
return null;
}).when(onResponseCall).enqueue(any(Callback.class));
sendData(....);
// verify function1
}
#Test
public void should_test_on_failure(){
Call<String> onResponseCall = mock(Call.class);
doAnswer(invocation -> {
Exception ex = new RuntimeException();
invocation.getArgumentAt(0, Callback.class).onFailure(onResponseCall, ex);
return null;
}).when(onResponseCall).enqueue(any(Callback.class));
sendData(....);
// verify function2
}

Forcing request retry after custom API exceptions in RxJava

I'm really new in RxJava, but I'm trying to implement an API using Retrofit framework and RxJava.
On a server side there is an authorization service which handles user's session and in case of some delay in user's actions server breaks his session. After that user has to login again in order to perform new API call. Bad thing is - server always returns HTTP code 200 and for notification about expiration uses some custom JSON response with expiration code, so RxJava doesn't fire Exception during onNext operation because RxJava considers that request was passed successfully.
And the question is: How to implement correct flow to handle custom API exceptions like expiration and retry failed request after some other request (in my case relogin)?
Something like this:
app -> login()
server -> { code:0, ... }
app -> getUsers()
server -> { code:0, ... }
------- in 30 minutes -------
app -> getPicture()
server -> { code:99, ... } // session expired, user unauthorized
app -> login()
server -> { code:0, ... }
app -> getPicture()
server -> { code:0, ... }
I was thinking about something like this, but with no success:
Observable.create(new Observable.OnSubscribe<BackendResponse<String>>() {
#Override
public void call(Subscriber<? super Response<String>> subscriber) {
try {
Response<String> response;
subscriber.onNext(response = getInterface().getUsers());
if (response != null) {
response.checkData(); // throws ServerException in case of code != 0
}
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.io()).retryWhen(new RetryWithSessionRefresh(new SessionService())).subscribe();
and RetryWithSessionRefresh is:
public class RetryWithSessionRefresh implements
Func1<Observable<? extends Notification<?>>, Observable<?>> {
private final SessionService sessionSerivce;
public RetryWithSessionRefresh(SessionService sessionSerivce) {
this.sessionSerivce = sessionSerivce;
}
#Override
public Observable<?> call(Observable<? extends Notification<?>> attempts) {
return attempts
.flatMap(new Func1<Notification<?>, Observable<?>>() {
#Override
public Observable<?> call(final Notification notification) {
final Throwable throwable = notification.getThrowable();
if (throwable instanceof ServerException) {
final ServerException backendException = (ServerException) throwable;
if (backendException.getBackendErrorCode() == Response.AUTHORIZATION_FAILED) {
return sessionSerivce
.observeSessionToken()
.doOnNext(new Action1<TokenCallback>() {
#Override
public void call(TokenCallback token) {
if (token != null) {
DataHolder.getInstance().setAuthToken(token.getToken());
}
}
})
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
DataHolder.getInstance().setAuthToken("");
}
});
}
}
return Observable.error(notification.getThrowable());
}
});
}
Maybe you van flapMap your response, and return your observable in case of success with Observable.just(your item) or an error when the response isn't valid with Observable.error(your error)

Categories

Resources