I am using Retrofit to access my API as follows:
public interface UserService {
...
#POST("/user/login")
public Observable<User> register(#Body() User user);
}
Here is how I access my API:
mUserService.register(user)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
#Override
public void onCompleted() {
....
}
#Override
public void onError(Throwable e) {
....
}
#Override
public void onNext(User user) {
....
}
});
This works perfectly well, except when there is an exception (i.e. IOException, or when connection times out), the onError method doesn't get fired, instead I get an exception on the main thread which terminates my application.
However, for some cases (such as when the API call responds with status code 400), the onError method is fired as expected.
After digging the logcat output, I have spotted this line, (not sure how I am supposed to deal with this)
rx.exceptions.OnErrorFailedException: Error occurred when trying to propagate error to Observer.onError
Can someone let me know where I am doing things wrong?
In my previous experience with RxJava the OnErrorFailedException means that an exception occurred while handling another exception in the onError method.
If you don't see stack trace of the real cause it's probably due the bug in RxJava with CompositeException (versions 0.19.2 and above) https://github.com/Netflix/RxJava/issues/1405
As a workaround try to wrap your onError code within try-catch block and log the exception. This way you will see what's the problem with your onError implementation and you will be able to solve the problem.
#Override
public void onError(Throwable e) {
try {
...
catch(Throwable e) {
// Log the exception
}
}
you can use unsafe unsafeSubscribe.
...
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.unsafeSubscribe(new Observer<User>() {
#Override
public void onCompleted() {
....
}
#Override
public void onError(Throwable e) {
....
}
#Override
public void onNext(User user) {
....
}
});
...
Related
I want to implement a logic using RxJava in my android application, which requires three parallel api calls. Only the third api call has a retry logic. If, after having three attempts, the success is achieved then a subsequent call will be made for the fourth api, else only the result of first and second api calls will be passed on to the subscriber.
I tried to achieve this using Zip operator but then got stuck with retry logic for third api call.
Observable<String> observable1 = Observable.just("A","B");
Observable<Integer> observable2 = Observable.just(1,2);
Observable<Boolean> observable3 = Observable.just(Boolean.TRUE, Boolean.FALSE);
Observable.zip(observable1, observable2, observable3, new Function3() {
#Override
public Object apply(String s, Integer integer, Boolean aBoolean) throws Exception {
if (aBoolean==null){
alphabets3.retry(3).doOnComplete(new Action() {
#Override
public void run() throws Exception {
// the result will never be used
}
});
}
return s+integer+aBoolean;
}
}).subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
Log.e("onNext-->", o.toString());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
if any Observable failed in the Zip operator, Zip will fail the stream, the only way I know to achieve parallel execution and error handling with Zip, is to add onErrorResumeNext to each Observable, that map the error to a new model to deal with later .. and handling what you want to do in the zip mapping function ... for example
Obsevable.zip(
observable1.onErrorResumeNext{Observable.just(Model(it)},
observable2.onErrorResumeNext{Observable.just(Model(it)},
observable3.retryWhen {t is TimeOutException} //here you can add your retry logic
.onErrorResumeNext(t -> Observable.just(Model(t)),(m1 , m2, m3) -> Result())
learner here and I'm trying to add a retry button for whenever there is an error in Retrofit Callback#onFailure method.
Somewhat following the Android Architecture Guide, I'm able to call, persist the data and show it on RecyclerView. Here is a general flow of what I've done so far:
On PagedList.BoundaryCallback I'm getting the response and saving it. Here I've also created a LiveData of NetworkState, which I'm observing within MainActivity through ViewModel class.
#Override
public void onZeroItemsLoaded() {
if (isFetchNeeded())
mClient.fetchFirstNetworkCall().enqueue(getRetrofitCallback());
}
#Override
public void onItemAtEndLoaded(#NonNull Item itemAtEnd) {
if (mNextPageToken != null)
mClient.fetchNextNetworkCall(mNextPageToken).enqueue(getRetrofitCallback());
}
#Override
public void onResponse(#NonNull Call<BloggerApi> call, #NonNull Response<BloggerApi> response) {
mObservableNetwork.setValue(NetworkState.LOADING);
if (response.isSuccessful()) {
mExecutors.diskIO().execute(() -> {
insertItemsToDb(responseBody.getItems());
mObservableNetwork.postValue(NetworkState.SUCCESS);
});
} else {
String error = response.errorBody() == null ? "Unknown Error" : String.valueOf(response.errorBody());
mObservableNetwork.setValue(NetworkState.error(error));
}
}
#Override
public void onFailure(#NonNull Call<BloggerApi> call, #NonNull Throwable t) {
mObservableNetwork.setValue(NetworkState.error(t.getMessage()));
}
And then on UI:
mViewModel.getNetworkState().observe(this, networkState -> {
if (networkState.getStatus() == Status.ERROR) {
retryButton.setOnClickListener(view -> {
// todo: Implement what to do
});
}
});
I'm lost here and don't know how implement a Retry button to make the last call if for some reason I get an error. Can you please help me out about what the Retry button should actually do to get the callback?
Thank you.
P.S. I'm new to Java, and as of now Kotlin is out of my league so couldn't figured out how Google sample projects implementing the retry method, and, also my sample project is on GitHub/DemoBlogApp for any reference. Any help is appreciated.
Figured it out myself long time back but was waiting for an opinion. While I didn't got any, thought of posting an answer to myself so that others may find it useful.
Retrofit has clone() method which can be super useful for situations for failures. So basically, make an interface:
public interface RetryCallback<T> {
void getCall(Call<T> call, ApiCallback<T> callback);
}
On Retrofit failure:
class Repository {
RetryCallback<Api> retryCallback;
//...
new Callback<Api>() {
// other Callback methods
public void onFailure(Call<Api> call, Throwable t) {
retryCallback.getCall(call, this);
}
}
public void setRetryCallback(RetryCallback<Api> retryCallback) {
this.retryCallback = retryCallback;
}
}
On MainActivity:
//...
// Using lambda instead of initializing with new operator
viewModel.setRetryCallback((call, callback) ->
call.clone().enqueue(callback);
);
I'm using RxJava's flatmap in order to execute multiple calls in sequence where one call relys on the previous call. I also need to know which observable emitted an error in the case that onError is called in order to properly implement my error handling. How do I achieve this?
Here is my code:
mSubscription = RxUtil.callObservable(mDataManager.createAccount(email, password))
.flatMap(new Func1<AuthResult, Observable<Void>>() {
#Override
public Observable<Void> call(AuthResult authResult) {
User user = new User(0, null, null, name, null, username, 0, 0);
return RxUtil.callObservable(mDataManager.createUser(authResult.getUser().getUid(), user));
}
})
.subscribe(new Subscriber<Void>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
try {
throw (Exception) throwable;
} catch (FirebaseAuthUserCollisionException ucException) {
getPickUpView().showError(PickUpApplication.getContext().getString(R.string.error_account_exists));
} catch (Exception exception) {
getPickUpView().showError(PickUpApplication.getContext().getString(R.string.error_account_general));
}
}
#Override
public void onNext(Void aVoid) {
getPickUpView().createAccountSuccessful(authResult);
}
});
I was thinking about this the wrong way. Is summary, I thought this was an issue that I needed to address when i didn't. RxJava will emit all errors in the onError method no matter what observable emits the error. Once onError is called the subscription is done, so the flatmap call will never take place.
In summary, all I need to do is handle my errors from both observables I call (the original and the one in the flatmap) in the same onError method.
When using the following pattern to synchronously get data from Firebase Realtime Database:
String s = Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("Got it");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
}).blockingGet();
It will hang and create an ANR error. If I use the same Firebase "innards" outside of the Single, it fires just fine. The Single without the Firebase code inside also will fire, so it seems there is some incompatibility between the two.
Any ideas?
Firebase delivers events on ui thread, waiting for result with blockingGet deadlocks it. In my opinion you should rethink app logic and subscribe without blocking with subscribe(SingleObserver)
Since you are creating your own Single, You should use DisposableSingleObserver in subscribeWith. Secondly, you shouldn't be calling blockingGet() like that. The reason is by default the Single or any observable/Processor/Flowable you create will be subscribed (run its operations on main thread) and observe on main thread. BlockingGet() causes the mainThread to pause. It's like executing Thread.sleep() on Main Thread. This always ends in a disaster.
The best option for you would be to rethink the logic you are trying to put in to the code. Since the Firebase operations are Async by nature, you should adapt your code to async pattern.
Anyways you can do something like the following to achieve what seems likes you might be trying to do. Note that I wrote the following code here so it might have syntactical errors.
Single.create(new SingleOnSubscribe<String>() {
// your firebase code
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("My String");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
})
.subscribeOn(Schedular.io())
.observeOn(AndroidThread.mainThread()) // if you aren't doing intensive/long running tasks on the data you got from firebase
.subscribeWith(new DisposableSingleObserver<String>() {
public void onSuccess(String myString) {
mMyString = myString;
}
public void onError(Throwable t) {
Timber.e("error in fetching data from firebase: %s", t);
}
});
I am using Retrofit for both asynchronous and synchronous api calls.
For both I have a custom error handler defined to handle unauthorised responses. For the synchronous calls I have declared the custom exception on the interface methods, I surround the interface implementation with a try/catch and it works perfect. I can catch Unauthorised Exceptions.
I have tried the same with asynchronous calls that use a callback and it doesn't work the same. Instead of the catching the Exception in the try/catch, I have to handle it in the failure method of the callback.
Here is the interface method:
#GET("getGardenGnomes")
void getGardenGnomes(#Header("Authorisation") String authorisation, Callback<GardenGnomes> callback) throws UnauthorisedException;
Here is the implementation:
void onClick() {
try {
getGardenGnomes()
} catch (UnauthorisedException exception) {
// .... handle the exception ....
}
}
void getGardenGnomes() throws UnauthorisedException {
// .... get client etc etc ....
client.getGardenGnomes(authorisation, new Callback<GardenGnomes>() {
#Override
public void success(GardenGnomes gardenGnomes, Response response) {
// .... do something ....
}
#Override
public void failure(RetrofitError error) {
// .... handle error ....
}
}
);
}
The question is:
Should I just handle the exception in the failure(RetrofitError error) method of the Callback and don't declare throws UnauthorisedException on the interface method of asynchronous calls?
Or what is the best way to implement this?
The anwser is yes. Using Retrofit interfaces you don't declare which exception is thrown from the implementation on the interface. RetrofitError is a RuntimeException therefore unchecked. It's expected that a RetrofitError will be thrown on failures from the Retrofit implementation and you're responsible for handling it accordingly. Using synchronous method you simply use the try/catch as you mentioned. Using the asynchronous method you handle it in the failure callback method.
public void methodToHandleRetrofitError(RetrofitError error) {
// handle the error
}
// Synchronous
try {
client.getGardenGnomes(authorization)
} catch (RetrofitError e) {
methodToHandleRetrofitError(e);
}
// Asynchronous
client.getGardenGnomes(authorisation, new Callback<GardenGnomes>() {
#Override
public void success(GardenGnomes gardenGnomes, Response response) {
// .... do something ....
}
#Override
public void failure(RetrofitError error) {
methodToHandleRetrofitError(error);
}
}
);
Hope this clarifies things for ya!