.subscribe(
new Action1<Response>() {
#Override
public void call(Response response) {
if (response.isSuccess())
//handle success
else
//throw an Throwable(reponse.getMessage())
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//handle Throwable throw from onNext();
}
}
);
I don't wanna handle (!response.isSuccess()) in onNext(). How can I throw it to onError() and handle with other throwable together?
If FailureException extends RuntimeException, then
.doOnNext(response -> {
if(!response.isSuccess())
throw new FailureException(response.getMessage());
})
.subscribe(
item -> { /* handle success */ },
error -> { /* handle failure */ }
);
This works best if you throw the exception as early as possible, as then you can do retries, alternative responses etc. easily.
you can flatMap your response to Response or Error
flatMap(new Func1<Response, Observable<Response>>() {
#Override
public Observable<Response> call(Response response) {
if(response.isSuccess()){
return Observable.just(response);
} else {
return Observable.error(new Throwable(response.getMessage()));
}
}
})
The solution is to add an operator in the middle. My suggestion is to use map as it does not generate new Observable object (in comparison to flatMap which does it):
.map(new Func1<Response, Response>() {
#Override
public Response call(Response response) {
if (response.isSuccess()) {
return response;
} else {
throw new Throwable(reponse.getMessage()));
}
}
})
Related
I am calling the request it self in a base class
#Override
public Single<BaseResponse<D>> call() {
Single<BaseResponse<D>> singleResponse = Single.create(emitter -> request().getAsOkHttpResponseAndParsed(typeToken(), new OkHttpResponseAndParsedRequestListener<BaseData<D>>() {
#Override
public void onResponse(Response okHttpResponse, BaseData<D> response) {
try {
BaseResponse<D> r = new BaseResponse<D>(okHttpResponse) {
};
r.setData(response.getData());
r.setStatus(response.getStatus());
emitter.onSuccess(r);
} catch (Throwable throwable) {
Log.e("error", "throwable");
emitter.onError(throwable);
}
}
#Override
public void onError(ANError anError) {
Log.e("error", "error");
emitter.onError(anError);
}
}));
singleResponse.doOnSubscribe(__ ->
EspressoIdlingResource.countingIdlingResource.increment()).
doFinally(EspressoIdlingResource.countingIdlingResource::decrement).subscribe();
return singleResponse;
}
then I have my single observer builder class
public class SingleObserverBuilder<T> extends BaseObserverBuilder<T, SingleObserver<T>> {
private final Consumer<Disposable> disposableConsumer;
public SingleObserverBuilder(#NonNull Consumer<Disposable> disposableConsumer) {
this.disposableConsumer = disposableConsumer;
}
#Override
public SingleObserver<T> build() {
return new SingleObserver<T>() {
#Override
public void onSubscribe(Disposable d) {
disposableConsumer.accept(d);
}
#Override
public void onSuccess(T t) {
if (onSuccessListeners() != null) {
for (Consumer<T> onSuccess : onSuccessListeners()) {
onSuccess.accept(t);
}
}
}
#Override
public void onError(Throwable e) {
Log.e("error", "base error");
if (onFailureListeners() != null)
for (Consumer<Throwable> onFailure : onFailureListeners()) {
onFailure.accept(e);
}
}
};
}
}
and in my base observer fragment I access this single observer
public abstract class BaseObserverFragment extends Fragment {
protected CompositeDisposable disposable = new CompositeDisposable();
protected <T> SingleObserverBuilder<T> getDefaultSingleObserver(Object tag) {
SingleObserverBuilder<T> builder = new SingleObserverBuilder<>(disposable -> this.disposable.add(disposable));
builder.tag(tag).onSuccess(object -> {
Logger.e(builder.tag().toString(), "onSuccess");
}).onFailure(throwable -> {
Log.e("error", "base fragment");
Logger.e(builder.tag().toString(), "onFailure");
ErrorLogger.log(getContext(), builder.tag().toString(), throwable);
});
return builder;
}
#Override
public void onDetach() {
super.onDetach();
if(!disposable.isDisposed()) {
Log.e("error", "detach");
disposable.clear();
disposable.dispose();
}
}
}
then in the fragment itself that's how I call api request
viewModel.setAvailabilityStatus(isAvailable)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this.<BaseResponse<ChangeSpAvailabilityResponse>>getDefaultSingleObserver(OperationTag.CHANGE_AVAILABILITY)
.onSuccess(response -> {
})
.onFailure( throwable -> {
Log.e("error", "last");
if (throwable instanceof ANError) {
Log.e("error", "throwable");
if (((ANError) throwable).getErrorCode() == 400) {
((ANError) throwable).getErrorBody();
handleChangeAvailabilityResponse(((ANError) throwable).getErrorAsObject(ChangeSpAvailabilityResponse.class));
}
}
}).build());
}
in that api request I wanna handle failure request body and it all work fine, but sometimes I get this error :
The exception was not handled due to missing onError handler in the
subscribe() method call. Further reading:
https://github.com/ReactiveX/RxJava/wiki/Error-Handling |
com.androidnetworking.error.ANError
and I don't know why or what should I do to handle error
also I have this line
RxJavaPlugins.setErrorHandler(throwable -> {
Log.e("error", throwable.getMessage());
});
in my application class
I have a java SDK,which use OkHttp client(4.0.0) to get token from IAM server and return token to application.The relation may like this:Applicaiton Sync call SDK,SDK Async call IAM.Refer to this answerJava - Retrieving Result from OkHttp Asynchronous GET,the code like:
The Async Class:
class BaseAsyncResult<T> {
private final CompletableFuture<T> future = new CompletableFuture<>();
T getResult() {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
void onFailure(IOException e) {
future.completeExceptionally(e);
}
void onResponse(Response response) throws IOException {
String bodyString = Objects.requireNonNull(response.body()).string();
future.complete(IasClientJsonUtil.json2Pojo(bodyString, new TypeReference<T>() {}));
}
}
Okhttp call like this:
public void invoke(Request request, BaseAsyncResult result) {
okHttpClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
result.onFailure(e);
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
result.onResponse(response);
}
});
}
The application use sdk code like,iasClient is a wrapper of okhttp client :
BaseAsyncResult<AuthenticationResponse> iasAsyncResult = new BaseAsyncResult();
iasClient.invoke(request, iasAsyncResult);
AuthenticationResponse result = iasAsyncResult.getResult();
The erroe message:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to x.x.x.AuthenticationResponse
What have I missed?
You need to make sure jackson knows which class to deserialize the value to . In this case, you are asking Jackson to deserialize the response to a TypeReference , which will resolve to a Map by default unless you specify the class (in this case, AuthenticationResponse ) . The Future resolves to a linkedHashMap due to this and causes the class cast.
try replacing the below line .
future.complete(IasClientJsonUtil.json2Pojo(bodyString, new TypeReference<T>() {}));
with
future.complete(IasClientJsonUtil.json2Pojo(bodyString, new TypeReference<AuthenticationResponse>() {}));
One method from #Arpan Kanthal is add a private Class type variable to BaseAsyncResult and then use that class in your json2Pojo function,then the BaseAsyncResult may like this:
public class BaseAsyncResult<T> {
private final CompletableFuture<T> future = new CompletableFuture<>();
private Class<T> classType;
public BaseAsyncResult(Class<T> classType) {
this.classType = classType;
}
public T getResult() {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
void onFailure(IOException e) {
future.completeExceptionally(e);
}
void onResponse(Response response) throws IOException {
future.complete(JacksonUtil.json2Pojo(response.body().string(), classType));
}
}
I have a method:
public void getVmsAdminToken(HttpClient httpClient, handler<AsyncResult<String>> handler) {
httpClient.postAbs(url, h -> h.bodyHandler(bh -> {
try {
switch (h.statusCode()) {
case 200:
JsonObject vmsResponse = bh.toJsonObject();
handler.handle(Future.succeededFuture(Json.encode(vmsResponse)));
break;
default:
LOG.error("VMS call failed {}", h.statusCode());
handler.handle(Future.failedFuture(500 + ""));
break;
}
} catch (Throwable t) {
LOG.error("Exception in getVmsAdminToken", t);
handler.handle(Future.failedFuture(500 + ""));
}
}))
.setTimeout(timeOutMs)
.putHeader("content-type", "application/json")
.putHeader("stub", apiKey)
.end(vehicleReqBody.encode());
}
I use this inside the following method call :
private void getAdminToken(RoutingContext ctx, RedisFleetStorage storage, HttpClient httpClient) {
getVmsAdminToken(fleetId, user, vehicle, httpClient, replyVms -> {
if (reply.succeeded()) {
// why succeeded?!!
}
});
}
And even if the getVmsToken fails, the execution falls into the if (reply.succeeded())
Why might that be?
You should check the same AsyncResult object being the result of your HTTP call:
private void getAdminToken(RoutingContext ctx, RedisFleetStorage storage, HttpClient httpClient) {
getVmsAdminToken(fleetId, user, vehicle, httpClient, replyVms -> {
if (replyVms.succeeded()) {
// do you thing
}
});
}
I have playing with Rxjava recently trying to implement a chain of events(Api callas/Database operations) and seem to have hit a roadblock when it comes to handling errors.
This is what I am trying to do. I am calling an Api that will check if user exists in the database. Based on the response I get, I am trying to chain a few sequences using rxjava. Following diagram might explain a little better.
checkUser()
/ \
No Yes
/ \
createUserRemote() FetchUserNotesRemote()
| |
End SaveUserNotesLocal()
|
End
I am able to chain together checkUser() -> FetchUserNotesRemote() -> SaveUserNotesLocal() sequence with the following code.
checkUser()
.flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
The issue I am mainly trying to solve.
I can't figure out how to handle a case where checkUser() returns
a 404 http status. Because when that happens, subscriber's onError
method gets called which seems to me is what should happen. How can I
handle it so that when I get an error (404) response from API,
instead of executing FetchUserNotesRemote() and SaveUserNotesLocal(),
I execute a different chain of events?
Another thing I am not sure about is, if there is an error called on
any of the observables in a chain, how does the subscriber's onError method know
which observable called it?
1) To execute different chain of observables on error you can use method onErorrResumeNext(). More info here: github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
Example:
checkUser().flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return doSomethingDifferent(); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
2) If the exception is thrown somewhere in your stream, it is passed down to subscriber onError(). If you want to know at which part of stream error was thrown, you can add multiple onErorrResumeNext() calls, that throw concrete exception after each api call.
checkUser()
.onErrorResumeNext(throwable -> { return Observable.error(new CheckUserException()); }
.flatMap(id -> {return fetchData(id);})
.onErrorResumeNext(throwable -> { return Observable.error(new FetchDataException()); }
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return Observable.error(new SaveDataException()); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
I completely forgot about this. But #mol pushed me in the right direction. My solution was a bit different. This may not be the best solution but it worked for me at the time.
I first created my own custom exception classes like following.
public class CreateUserLocalException extends Exception {
public CreateUserLocalException(String message) {
super(message);
}
}
Then in my checkUser() function I throw exception of type I created above like following.
public Single<String> checkUser(String id) {
return Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> emitter) throws Exception {
try {
GetUserResponseObject getUserResponseObject = apiClient.usersIdGet(id);
Log.d("Test", "checkUserCall: Status: " + getUserResponseObject.getStatus());
emitter.onSuccess(getUserResponseObject.getBody().getUserId());
} catch (AmazonServiceException e) {
Log.d("Test", "AmazonServiceException : " + e.getErrorMessage());
e.printStackTrace();
if (e.getErrorMessage().equals("timeout")) {
throw new SocketTimeoutException();
} else {
throw new CheckUserException(Integer.toString(e.getStatusCode()));
}
} catch (Exception e) {
e.printStackTrace();
throw new CheckUserException(Integer.toString(AppConstants.ERROR));
}
}
});
}
Then in my chain of calls, in the event of an error, onError(throwable) gets invoked where I am checking the instanceof Exception to identify what kind of exception occurred. Below is the code for chain of functions.
cloudSyncHelper.checkUser(user.getUser_id())
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
})
.flatMap(s -> {
return cloudSyncHelper.createUserLocal(user)
.onErrorResumeNext(throwable -> {
Log.d("Test", "onErrorResumeNext, throwable message: " + throwable.getMessage());
if (throwable instanceof CreateUserLocalException) {
if (Integer.parseInt(throwable.getMessage()) == AppConstants.LOCAL_DB_DUPLICATE) {
return Single.just(user.getUser_id());
}
}
return Single.error(new CreateUserLocalException(Integer.toString(AppConstants.LOCAL_DB_ERROR)));
});
})
.flatMap(id -> {
return cloudSyncHelper.fetchData(id)
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
});
})
.flatMap(notesResponseObject -> {
return cloudSyncHelper.saveFetchedData(notesResponseObject);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onSuccess Called");
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
}
#Override
public void onError(Throwable e) {
if (e instanceof SocketTimeoutException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Socket Time Out");
Utils.createToast(LoginScreen.this, "Socket timed out");
return;
}
int code = Integer.parseInt(e.getMessage());
Log.d("Test", "onError Called");
if (e instanceof CheckUserException) {
Log.d("Test", "onError CheckUserException");
if (code == AppConstants.NOTFOUND) {
newUserSequence(user);
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Utils.createToast(LoginScreen.this, "Unable to user information from cloud. Try again.");
}
}
if (e instanceof CreateUserLocalException) {
Log.d("Test", "onError CreateUserLocalException");
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
}
if (e instanceof FetchDataException) {
Log.d("Test", "onError FetchDataException");
if (code == AppConstants.NOTFOUND) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Unable to fetch data from cloud");
Utils.createToast(LoginScreen.this, "Unable to fetch data from cloud. Try again.");
}
}
if (e instanceof SaveDataLocalException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onError SaveDataLocalException");
if (code == AppConstants.LOCAL_DB_ERROR) {
Log.d("Test", "Unable to save data fetched from cloud");
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
} else {
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
}
}
}
});
Hope this helps.
On the change "SortBy", my program will do a NetworkIO to retrieve the top movies and display them.
However, it seems that though I have done subscribeOn(Schedulers.io()), the NetworkIO MovieDB.getPopular() and MovieDB.getTopRated() in the function call in map are excuted on the main thread and I get a android.os.NetworkOnMainThreadException.
I was wondering how to make the public Movie[] call(SortBy sortBy) asynchronous.
sortObservable.map(new Func1<SortBy, Movie[]>() {
#Override
public Movie[] call(SortBy sortBy) {
try {
switch (sortBy) {
case POPULAR:
return MovieDB.getPopular(); // NETWORK IO
case TOP_RATED:
return MovieDB.getTopRated(); // NETWORK IO
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return new Movie[0];
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});
Please check if the below works for you. It uses flatMap instead of map.
sortObservable.flatMap(new Func1<SortBy, Observable<Movie[]>>() {
#Override
public Observable<Movie[]> call(SortBy sortBy) {
try {
switch (sortBy) {
case POPULAR:
return Observable.just(MovieDB.getPopular()); // NETWORK IO
case TOP_RATED:
return Observable.just(MovieDB.getTopRated()); // NETWORK IO
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return Observable.just(new Movie[0]);
}
}).subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});
From your source code on Github, it seems like you are using synchronous mode of executing requests using OkHttp. OkHttp also supports asynchronous requests and that can be preferred. Below would be the changes required in few of the methods.
run method should consume enqueue instead of execute.
Observable<String> runAsync(String url){
return Observable.create(subscriber -> {
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) throws IOException {
subscriber.onNext(response.body().string());
}
#Override
public void onFailure(Call call, IOException e) {
subscriber.onError(e);
}
});
});
}
getApi can return an Observable<Movie[]> instead of Movie[]
public Observable<Movie[]> getApiAsync(String type){
return runAsync("http://api.themoviedb.org/3/movie/" + type
+ "?api_key=412e9780d02673b7599233b1636a0f0e").flatMap(response -> {
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(response,
new TypeToken<Map<String, Object>>() {
}.getType());
Movie[] movies = gson.fromJson(gson.toJson(map.get("results")),
Movie[].class);
return Observable.just(movies);
});
}
Finally I sort it out by myself:
sortObservable.flatMap(new Func1<SortBy, Observable<Movie[]>>() {
#Override
public Observable<Movie[]> call(SortBy sortBy) {
switch (sortBy) {
case POPULAR:
return Observable.fromCallable(() -> MovieDB.getPopular()).subscribeOn(Schedulers.io());
case TOP_RATED:
return Observable.fromCallable(() -> MovieDB.getTopRated()).subscribeOn(Schedulers.io());
default:
return Observable.fromCallable(() -> new Movie[0]).subscribeOn(Schedulers.io());
}
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});