I have some code that first has to run on AndroidSchedulers.mainThread(), then has to do a HTTP request, so has to run on Schedulers.io(), and handle the result on UI, so back to AndroidSchedulers.mainThread().
I receive InterruptedIOException when switching from AndroidSchedulers.mainThread() to Scheulers.io().
Here's some code:
Model model = getModel();
Completable.fromAction(
new Action0() {
public void call() {
mSubject.onNext(model)
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.andThen(fetchFromServer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* handle success and error */);
...
public <T> Single<T> fetchFromServer() {
Request request = new Request(); // some request from server, not important
return bodyFrom2(request);
}
public <T> Single<T> bodyFrom2(final Request<T> request) {
return Single.defer(new Callable<Single<T>>() {
#Override
public Single<T> call() throws Exception {
try {
Response<T> response = request.execute();
if (response.error() != null)
return Single.error(response.error().getMessage());
else {
return Single.just(response.body());
}
} catch (IOException e) {
return Single.error(e);
}
}
});
}
public static <T> Single<T> bodyFrom1(final Request<T> request) {
return Single.create(new Single.OnSubscribe<T>() {
#Override
public void call(SingleSubscriber<? super T> subscriber) {
try {
Response<T> response = request.execute();
if (subscriber.isUnsubscribed())
return;
if (response.error() != null)
subscriber.onError(response.error().getMessage());
else {
subscriber.onSuccess(response.body());
}
} catch (IOException e) {
if (subscriber.isUnsubscribed())
return;
subscriber.onError(e);
}
}
});
}
The exception is thrown in bodyFrom() (1 or 2), at request.execute().
I used bodyFrom1(), but I found this question on SO and thought about trying with the second one. Regardless, I receive the exception.
Trying to find what and where the problem is, I tried this:
Completable.complete()
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.andThen(fetchFromServer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* handle success and error */);
which still throws InterruptedIOException, and this:
Completable.complete()
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.andThen(fetchFromServer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* handle success and error */);
which works fine.
EDIT:
It seems to work if I'm using Observable or Single instead of Completable.
Added an issue on RxAndroid's Github.
Related
I use retrofit2 with rxjava extension.
I have a list of REST API urls and want to do this:
for each
check whether a corresponding file locally exists
if yes: call the API and store the response or the HTTP error
if not: store a customized error
return the list of those results
My problem is: apply returns (with an empty RequestResult) before the server response is received. I think, I understand why, but I don't know how to fix it, because I need to return a RequestResult and not the Retrofit observable.
How can this be solved?
Here is my code:
#GET
Observable<Response<ResponseBody>> enroll(#Url String url);
class RequestResult {
CustomException error;
Response<ResponseBody> response;
}
Observable<ClassOfListItem> observable = Observable.fromIterable(listOfItems);
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
Observable<Response<ResponseBody>> callObservable = restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
callObservable
.subscribe(new DisposableObserver<Response<ResponseBody>>() {
#Override
public void onNext(Response<ResponseBody> responseBodyResponse) {
onPremiseEnrollmentResult.response = responseBodyResponse;
}
#Override
public void onError(Throwable e) {
onPremiseEnrollmentResult.error = new CustomException(e);
}
#Override
public void onComplete() {
}
});
}
else {
requestResult.error = new CustomException("file not found");
}
return Observable.just(requestResult);
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
)
The flatMap() operator allows you to turn one observable into a different observable. You have a nested observer chain inside your apply() which is not part of the observer chain, so it will be empty because it has not completed yet.
To fix this, when the file exists, return the observable.
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
return restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
}
return Observable.error( new CustomException("file not found") );
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
If you need to capture both errors and successes into the list, then you can add map() operator to wrap RequestResult around the response and onErrorResumeNext() to wrap RequestResult around the error before the toList() operator.
If you are making api call on background thread then what you can do is invoke it synchronously....in your case your retrofit api method would change to following
Call<Response<ResponseBody>> enroll(#Url String url);
and you'd invoke by calling restAPI.enroll(listItem.url).execute()
I have token to make requests with it, this token have some lifeTime.
So when I get 401 error, I need to refreshToken with other request.
I want to make it smoothly for user(without any interruptions): if get 401, than make refresh, and after it repeat first request with new available token.
here is my code:
#POST("/refresh_token")
Observable<Auth> refreshToken(#Body BodyAuth body);
#POST("/someLink")
Observable<Answer> someRequest(#Body OtherBody body);
Observable<Answer> makeSomeRequest(){
retutn someRequest(new Body(currentToken)).
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(StatusCodeVerifier())
.retryWhen(RetryWithNewToken(requestManager))
}
public class StatusCodeVerifier<T> implements Observable.Transformer<Response<T>,T> {
#Override
public Observable<T> call(Observable<Response<T>> responseObservable) {
return responseObservable.
flatMap(new Func1<Response<T>, Observable<?>>() {
#Override
public Observable<?> call(Response<T> tResponse) {
if (tResponse.code() == 401){
throw new UnAuthorizedException("custom exception");
} else {
return Observable.just(tResponse);
}
}
});
}
}
public class RetryWithNewToken implements Func1<Observable<Throwable>, Observable<?>> {
private UserDataManager requestManger;
public RetryWithNewToken(RequestManager requestManger){
this.requestManger = requestManger;
}
#Override
public Observable<?> call(final Observable<Throwable> attempts) {
return attempts.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(final Throwable throwableObservable) {
if (throwableObservable instanceof UnAuthorizedException){
return requestManger. refreshToken().subscribe(new Action1<CbAuth>() {
#Override
public void call(TokenData newToken) {
updateToken(newToken);
//here i try to repeat previous request that was failed because of 401 error
Observable.just(attempts);
}
});
}
return Observable.error(throwableObservable);
}
});
}
}
every time after successful refreshToken previous request called, but it is called with invalid (old one) data.
So how can I repeat request with new token data?
It's because of you initialize your someRequest Observable with its arguments only once. And when an error occurs you just resubscribes to it. But you have to poll currentToken on each subscription. It could be done by using Observable.fromCallable operator:
Observable<Answer> makeSomeRequest(){
return Observable.fromCallable(() -> currentToken) // called on each subscription
.flatMap(token -> someRequest(new Body(token)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(StatusCodeVerifier())
.retryWhen(RetryWithNewToken(requestManager))
}
My application must do two things in general:
Accept only one network request at the same time
Retry if request failed
That's how I implement it:
public class RequestsLocker {
private volatile boolean isLocked;
public <T> Observable.Transformer<T, T> applyLocker() {
if(!isLocked()) {
return observable -> observable
.doOnSubscribe(() -> {
lockChannel();
})
.doOnUnsubscribe(() -> {
freeChannel();
});
} else {
return observable -> Observable.error(new ChannelBusyException("Channel is busy now."));
}
}
private void lockChannel() {
isLocked = true;
}
private void freeChannel() {
isLocked = false;
}
public boolean isLocked() {
return isLocked;
}
}
Looks nice.
Now my retryWhen implementation:
public static Observable<?> retryWhenAnyIoExceptionWithDelay(Observable<? extends Throwable> observable) {
return observable.flatMap(error -> {
// For IOExceptions, we retry
if (error instanceof IOException) {
return Observable.timer(2, TimeUnit.SECONDS);
}
// For anything else, don't retry
return Observable.error(error);
});
}
There is how I use it:
public Observable<List<QueueCarItem>> finishService(int id, PaymentType paymentType, String notes) {
return carsQueueApi.finishService(id, new FinishCarServiceRequest(paymentType.getName(), notes))
.compose(requestsLocker.applyLocker(RequestsLocker.RequestChannel.CHANGE));
}
...
public void finishCarService(QueueCarItem carItem, PaymentType paymentType,
String notes, Subscriber<List<QueueCarItem>> subscriber) {
queueApiMediator.finishService(carItem.getId(), paymentType, notes)
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.doOnError(this::handleError)
.retryWhen(RxOperatorsHelpers::retryWhenAnyIoExceptionWithDelay)
.subscribe(subscriber);
}
The main problem that doOnUnsubscribe() called on any error and then locker is open for any new request until the timer expires and resubscribing happens again. That's the problem. While the timer is ticking user can make another request.
How I can fix it?
The problem is that you're applying your transformer to the source observable i.e. before your retrywhen.
When there is an error you're always going to unsubscribe from and then resubscribe to the source observable
leading to your doOnUnsubscribe being called.
I suggest you try
public Observable<List<QueueCarItem>> finishService(int id, PaymentType paymentType, String notes) {
return carsQueueApi.finishService(id, new FinishCarServiceRequest(paymentType.getName(), notes));
}
public void finishCarService(QueueCarItem carItem, PaymentType paymentType,
String notes, Subscriber<List<QueueCarItem>> subscriber) {
queueApiMediator.finishService(carItem.getId(), paymentType, notes)
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.doOnError(this::handleError)
.retryWhen(RxOperatorsHelpers::retryWhenAnyIoExceptionWithDelay)
.compose(requestsLocker.applyLocker(RequestsLocker.RequestChannel.CHANGE));
.subscribe(subscriber);
}
PS: The apply locker transformer looks a bit different i.e. it doesn't take an argument in the code you linked.
Using retryWhen, to avoid unsubscribe onError you must use onErrorResumeNext which wont unsubscribe you.
Take a look of this example
/**
* Here we can see how onErrorResumeNext works and emit an item in case that an error occur in the pipeline and an exception is propagated
*/
#Test
public void observableOnErrorResumeNext() {
Subscription subscription = Observable.just(null)
.map(Object::toString)
.doOnError(failure -> System.out.println("Error:" + failure.getCause()))
.retryWhen(errors -> errors.doOnNext(o -> count++)
.flatMap(t -> count > 3 ? Observable.error(t) : Observable.just(null)),
Schedulers.newThread())
.onErrorResumeNext(t -> {
System.out.println("Error after all retries:" + t.getCause());
return Observable.just("I save the world for extinction!");
})
.subscribe(s -> System.out.println(s));
new TestSubscriber((Observer) subscription).awaitTerminalEvent(500, TimeUnit.MILLISECONDS);
}
Also about concurrency, if you do an operation in a flatMap operator, you can specify the Max concurrent.
public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func, int maxConcurrent) {
if (getClass() == ScalarSynchronousObservable.class) {
return ((ScalarSynchronousObservable<T>)this).scalarFlatMap(func);
}
return merge(map(func), maxConcurrent);
}
You can see more examples here https://github.com/politrons/reactive
My current solution is not to unlock RequestLocker on IoException as in this case request will be repeated after delay.
public <T> Observable.Transformer<T, T> applyLocker() {
if(!isLocked()) {
return observable -> observable.doOnSubscribe(() -> {
lockChannel();
}).doOnNext(obj -> {
freeChannel();
}).doOnError(throwable -> {
if(throwable instanceof IOException) {
return; // as any request will be repeated in case of IOException
}
freeChannel(channel);
});
} else {
return observable -> Observable.error(new ChannelBusyException("Channel is busy now"));
}
}
Can I use Retrofit + RxJava to listen to an endless stream? For instance the Twitter stream. What I have is this:
public interface MeetupAPI {
#GET("http://stream.meetup.com/2/rsvps/")
Observable<RSVP> getRSVPs();
}
MeetupAPI api = new Retrofit.Builder()
.baseUrl(MeetupAPI.RSVP_API)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MeetupAPI.class);
api.getRSVPs()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(rsvp -> Log.d(TAG, "got rsvp"),
error -> Log.d(TAG, "error: " + error),
() -> Log.d(TAG, "onComplete"));
but the "onComplete" is invoked after the first object has been parsed. Is there a way to tell Retrofit to stay open until further notice?
Here my solution:
You can use the #Streaming annotation:
public interface ITwitterAPI {
#GET("/2/rsvps")
#Streaming
Observable<ResponseBody> twitterStream();
}
ITwitterAPI api = new Retrofit.Builder()
.baseUrl("http://stream.meetup.com")
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build().create(ITwitterAPI.class);
With #Streaming we can get raw input From ResponseBody.
Here my function to wrap body divided by lines with events:
public static Observable<String> events(BufferedSource source) {
return Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
try {
while (!source.exhausted()) {
subscriber.onNext(source.readUtf8Line());
}
subscriber.onCompleted();
} catch (IOException e) {
e.printStackTrace();
subscriber.onError(e);
}
}
});
}
And result usage:
api.twitterStream()
.flatMap(responseBody -> events(responseBody.source()))
.subscribe(System.out::println);
upd about gracefully stopping
When we unsubscribing, retrofit closes inputstream. But impossible to detect inputstream closed or not from inputstream themselves, so only way - try reading from stream - we gets exception with Socket closed message.
We can interpret this exception as closing:
#Override
public void call(Subscriber<? super String> subscriber) {
boolean isCompleted = false;
try {
while (!source.exhausted()) {
subscriber.onNext(source.readUtf8Line());
}
} catch (IOException e) {
if (e.getMessage().equals("Socket closed")) {
isCompleted = true;
subscriber.onCompleted();
} else {
throw new UncheckedIOException(e);
}
}
//if response end we get here
if (!isCompleted) {
subscriber.onCompleted();
}
}
And if connection closed because response end, we haven't any exceptions. Here isCompleted check for that. Let me know if i am wrong :)
Zella's answer is right for Retrofit2 with rxJava,
For rxJava2, I modified custom observable like this:
//imports
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okio.BufferedSource
import java.io.IOException
fun events(source: BufferedSource): Observable<String> {
return Observable.create { emitter ->
var isCompleted = false
try {
while (!source.exhausted()) {
emitter.onNext(source.readUtf8Line()!!)
}
emitter.onComplete()
} catch (e: IOException) {
e.printStackTrace()
if (e.message == "Socket closed") {
isCompleted = true
emitter.onComplete()
} else {
throw IOException(e)
}
}
if (!isCompleted) {
emitter.onComplete()
}
}
}
Changes in module level build.gradle dependencies:
//retrofit rxJava2 adapter
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
//rx-java
implementation 'io.reactivex.rxjava2:rxjava:2.2.11'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
Retrofit Adapter Changes:
ITwitterAPI api = new Retrofit.Builder()
.baseUrl("http://stream.meetup.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(ITwitterAPI.class);
And Called the Streaming API as
api.twitterStream()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.flatMap { responseBody-> events(responseBody.source()) }
.subscribe({ t ->
Log.i(TAG, "onNext t=$t")
}, { e ->
Log.i(TAG, "onError e=$e")
}, {
Log.i(TAG, "onFinish")
})
I'm pretty new to RxJava and Retrofit and am trying to write my API calls with it. All the API calls return a JSON body on error which is in the general format as,
{"errors":[{"code":100, "message":"Login/Password not valid", "arguments":null}]}
Currently my code for the login API call (others are also similar) is,
mConnect.login(id, password)
.subscribe(new Subscriber<Token>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted()");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError(): " + e);
if (e instanceof HttpException) {
// dump e.response().errorBody()
}
}
#Override
public void onNext(Token token) {
Log.d(TAG, "onNext(): " + token);
}
});
When I get an error at the onError(), I would like to automatically decode the JSON in the error body to a POJO instead and use that. Is there a way to do this preferably in one place for all other API calls. Any help is appreciated.
I would suggest the use of a reusable Transformer along with the onErrorResumeNext operator to encapsulate your logic. It'd look something like this:
<T> Observable.Transformer<T, T> parseHttpErrors() {
return new Observable.Transformer<T, T>() {
#Override
public Observable<T> call(Observable<T> observable) {
return observable.onErrorResumeNext(new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
if (throwable instanceof HttpException) {
HttpErrorPojo errorPojo = // deserialize throwable.response().errorBody();
// Here you have two options, one is report this pojo back as error (onError() will be called),
return Observable.error(errorPojo); // in this case HttpErrorPojo would need to inherit from Throwable
// or report this pojo back as part of onNext()
return Observable.just(errorPojo); //in this case HttpErrorPojo would need to inherit from <T>
}
// if not the kind we're interested in, then just report the same error to onError()
return Observable.error(throwable);
}
});
}
};
}
Pay attention to the comments in the code, since you have to make the decision whether you want to report the parsed response onError() or onNext().
Then you can use this transformer anywhere in your API calls like this:
mConnect.login(id, password)
.compose(this.<Token>parseHttpErrors()) // <-- HERE
.subscribe(new Subscriber<Token>() {
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted()");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError(): " + e);
if (e instanceof HttpErrorPojo) {
// this will be called if errorPojo was reported via Observable.error()
}
}
#Override
public void onNext(Token token) {
Log.d(TAG, "onNext(): " + token);
if (token instanceof HttpErrorPojo) {
// this will be called if errorPojo was reported via Observable.just()
}
}
});
Deserialize may be an issue too. You can use the retrofit converter to deserialize it (or do it yourself).
My solution adds a bit to the one from murki:
<T> Observable.Transformer<T, T> parseHttpErrors() {
return new Observable.Transformer<T, T>() {
#Override
public Observable<T> call(Observable<T> observable) {
return observable.onErrorResumeNext(new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
if ( throwable instanceof HttpException ) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(SERVER_URL) // write your url here
.addConverterFactory(GsonConverterFactory.create())
.build();
Converter<ResponseBody, Error> errorConverter =
retrofit.responseBodyConverter(Error.class, new Annotation[0]);
// Convert the error body into our Error type.
try {
Error error = errorConverter.convert(((HttpException) throwable).response().errorBody());
// Here you have two options, one is report this pojo back as error (onError() will be called),
return Observable.error(new Throwable(error.getMessage()));
}
catch (Exception e2) {
return Observable.error(new Throwable());
}
}
// if not the kind we're interested in, then just report the same error to onError()
return Observable.error(throwable);
}
});
}
};
}
and then at the onError(),
#Override
public void onError(Throwable e) {
progressBar.setVisibility(View.GONE); // optional
if ( !TextUtils.isEmpty(e.getMessage()) ) {
// show error as you like
return;
}
// show a default error if you wish
}