I have a service class which is used to call api requset
here is an example method:
public Observable<List<Category>> test(Location location, int radius) {
Observable<CategoryListResponse> observable = api.test();
return observable
.doOnNext(new Action1<CategoryListResponse>() {
#Override
public void call(CategoryListResponse categoryListResponse) {
//handle error
}
})
.flatMap(new Func1<CategoryListResponse, Observable<Category>>() {
#Override
public Observable<Category> call(CategoryListResponse categoryListResponse) {
return Observable.from(categoryListResponse.getCategories());
}
})
.map(new Func1<Category, Category>() {
#Override
public Category call(Category category) {
//do something...
return category;
}
})
.toList();
}
And subscribe() will be called in another class.
observable.subscribe(new Action1<List<Category>>() {
#Override
public void call(List<Category> categories) {
//on success
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//on error
}
});
I was thinking to do error handling in the doOnNext() before it is returned back. but how can I trigger onError()?
You should throw a runtime exception and control the exception in onError operator in case that happens
Observable<CategoryListResponse> observable = api.test();
return observable
.doOnNext(list -> {
try{
request(list);
catch(Exception e){
throw new RuntimeException(e);
}
}).onError(t->//Here you control your errors otherwise it will be passed to OnError callback in your subscriber)
.flatMap(item -> Observable.from(item.getCategories()))
.map(category-> category)
.toList();
}
Try to use lambdas, make your code much more clear and readable
You can see some RxJava examples here https://github.com/politrons/reactive
Related
I have been creating a project with Aspect Oriented Programming paradigm and
I have an "ExceptionLogAspect" class attribute which is used on business methods to log the errors throwing from them.
public class ExceptionLogAspect : MethodInterception
{
private readonly LoggerServiceBase _loggerServiceBase;
private static byte _risk;
public ExceptionLogAspect(Type loggerService, byte risk)
{
if (loggerService.BaseType != typeof(LoggerServiceBase))
{
throw new System.Exception(AspectMessages.WrongLoggerType);
}
_loggerServiceBase = (LoggerServiceBase)Activator.CreateInstance(loggerService);
_risk = risk;
}
protected override void OnException(IInvocation invocation, System.Exception e)
{
var logDetailWithException = GetLogDetail(invocation);
logDetailWithException.ExceptionMessage = e.Message;
_loggerServiceBase.Error(logDetailWithException);
}
}
This Aspect migrates MethodInterception class that I created with Castle.DinamicProxy package. And OnException method included by MethodInterception logs the exception data.
public abstract class MethodInterception:MethodInterceptionBaseAttribute
{
protected virtual void OnBefore(IInvocation invocation){}
protected virtual void OnAfter(IInvocation invocation){}
protected virtual void OnException(IInvocation invocation, System.Exception e){}
protected virtual void OnSuccess(IInvocation invocation){}
public override void Intercept(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed();//Business Method works here.
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if(isSuccess)
OnSuccess(invocation);
}
OnAfter(invocation);
}
}
When I run the code and try-catch block doesn't work for Exception. So catch block isn't called and no messages are logged.
If I turn the business method into a syncronous method, exception will be thrown and data will be logged.
How can I solve this asynchronous method problem?
I tried this solution, it works properly.
Intercept method has to be like this to make this process asynchronous.
Otherwise, this method doesn't work properly for async.
There are some other ways, for example Castle CoreAsync Interceptor, you can find it on Github or NuGet.
https://github.com/JSkimming/Castle.Core.AsyncInterceptor
public override void Intercept(IInvocation invocation)
{
var isSuccess = true;
OnBefore(invocation);
try
{
invocation.Proceed(); //Metodu çalıştır
if (invocation.ReturnValue is Task returnValueTask)
{
returnValueTask.GetAwaiter().GetResult();
}
if (invocation.ReturnValue is Task task && task.Exception != null)
{
throw task.Exception;
}
}
catch (Exception e)
{
isSuccess = false;
OnException(invocation, e);
throw;
}
finally
{
if (isSuccess)
OnSuccess(invocation);
}
OnAfter(invocation);
}
I am using retrofit 2.3.0 to consume API's in my app but a week ago I started receiving error message and existing code was not able to display error message in UI.
Previously, I was using errorBody.toString() then suddenly after few months I got error and then last week I tried with errorBody.string() but it dodn't work. Now today it's working.
I have attached screenshots of response from server and my error handling also. Here is my code to display error message.
private static void showToastForError(retrofit2.Response<Object> response, int requestType) {
if (response != null && response.errorBody() != null) {
try {
JSONObject jObjError = null;
try {
jObjError = new JSONObject(response.errorBody() != null ? response.errorBody().toString() : "");
Toast.makeText(Application.getAppContext(), jObjError.getString("message"), Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
I think you should custom call adapter to handle error.
Here my custom adapter
public final class ErrorHandlingAdapter {
/**
* A callback which offers granular callbacks for various conditions.
*/
public interface MyCallback<T> {
/**
* Called for [200, 300) responses.
*/
void success(Response<T> response);
/**
* Called for 401 responses.
*/
void unauthenticated(Response<?> response);
/**
* Called for [400, 500) responses, except 401.
*/
void clientError(Response<?> response);
/**
* Called for [500, 600) response.
*/
void serverError(Response<?> response);
/**
* Called for network errors while making the call.
*/
void networkError(IOException e);
/**
* Called for unexpected errors while making the call.
*/
void unexpectedError(Throwable t);
}
public interface MyCall<T> {
void cancel();
void enqueue(MyCallback<T> callback);
MyCall<T> clone();
boolean isExcute();
}
public static class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
#Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit) {
if (getRawType(returnType) != MyCall.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException(
"MyCall must have generic type (e.g., MyCall<ResponseBody>)");
}
Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
Executor callbackExecutor = retrofit.callbackExecutor();
return new ErrorHandlingCallAdapter<>(responseType, callbackExecutor);
}
private static final class ErrorHandlingCallAdapter<R> implements CallAdapter<R> {
private final Type responseType;
private final Executor callbackExecutor;
ErrorHandlingCallAdapter(Type responseType, Executor callbackExecutor) {
this.responseType = responseType;
this.callbackExecutor = callbackExecutor;
}
#Override
public Type responseType() {
return responseType;
}
#Override
public <R1> R adapt(Call<R1> call) {
return (R) new MyCallAdapter(call, callbackExecutor);
}
}
}
/**
* Adapts a {#link Call} to {#link MyCall}.
*/
static class MyCallAdapter<T> implements MyCall<T> {
private final Call<T> call;
private final Executor callbackExecutor;
MyCallAdapter(Call<T> call, Executor callbackExecutor) {
this.call = call;
this.callbackExecutor = callbackExecutor;
}
#Override
public void cancel() {
call.cancel();
}
#Override
public void enqueue(final MyCallback<T> callback) {
call.enqueue(new Callback<T>() {
#Override
public void onResponse(Call<T> call, Response<T> response) {
// on that executor by submitting a Runnable. This is left as an exercise for the reader.
callbackExecutor.execute(new Runnable() {
#Override
public void run() {
int code = response.code();
if (code >= 200 && code < 300) {
callback.success(response);
} else if (code == 401) {
if (Storage.getInstance().isLogin())
Storage.getInstance().logout(App.self().getApplicationContext());
} else if (code >= 400 && code < 500) {
callback.clientError(response);
} else if (code >= 500 && code < 600) {
callback.serverError(response);
} else {
callback.unexpectedError(new RuntimeException("Unexpected response " + response));
}
}
});
}
#Override
public void onFailure(Call<T> call, Throwable t) {
// on that executor by submitting a Runnable. This is left as an exercise for the reader.
callbackExecutor.execute(new Runnable() {
#Override
public void run() {
if (t instanceof IOException) {
if (call.isCanceled()) {
return;
}
callback.networkError((IOException) t);
Toast.makeText(App.self(), R.string.error_no_connect_internet, Toast.LENGTH_SHORT).show();
} else {
callback.unexpectedError(t);
}
}
});
}
});
}
#Override
public MyCall<T> clone() {
return new MyCallAdapter<>(call.clone(), callbackExecutor);
}
#Override
public boolean isExcute() {
return call.isExecuted();
}
}
}
Here my config to add custom call adapter
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addCallAdapterFactory(new ErrorHandlingAdapter.ErrorHandlingCallAdapterFactory()) // custom call adapter
.addConverterFactory(GsonConverterFactory.create())
.client(getHeader())
.build();
And handle request, ex:
#GET("api/getSomething")
ErrorHandlingAdapter.MyCall<BaseResponse> getSomething(#Query("param"),...)
Handle response:
ErrorHandlingAdapter.MyCall<BaseResponse> mCalls = ApiUtils.getSomething(...);
mCalls.enqueue(new ErrorHandlingAdapter.MyCallback<BaseResponse>() {
#Override
public void success(Response<BaseResponse> response) {
//handle response
}
#Override
public void unauthenticated(Response<?> response) {
//handle unauthenticated error
}
#Override
public void clientError(Response<?> response) {
//handle clientError error
}
#Override
public void serverError(Response<?> response) {
//handle serverError error
}
#Override
public void networkError(IOException e) {
//handle networkError error
}
#Override
public void unexpectedError(Throwable t) {
//handle unexpectedError error
}
}
I've created a HandlerMiddleware-class, which executes a handler for a specific URL.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace FailTest
{
public interface IHttpHandler
{
bool IsReusable { get; }
void ProcessRequest(HttpContext context);
}
public abstract class HandlerMiddleware<T>
where T : IHttpHandler
{
private readonly RequestDelegate _next;
public HandlerMiddleware()
{ }
public HandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await SyncInvoke(context);
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
}
public Task SyncInvoke(HttpContext context)
{
try
{
// IHttpHandler handler = (IHttpHandler)this;
T handler = System.Activator.CreateInstance<T>();
handler.ProcessRequest(context);
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
return Task.CompletedTask;
}
} // End Abstract Class HandlerMiddleware
}
Now I have a class which implements a handler, e.g.
[HandlerPath("/treedata", "GET,POST")]
public class SomeFailingTask
: HandlerMiddleware<SomeFailingTask>, IHttpHandler
{
public SomeFailingTask() : this(null)
{ }
public SomeFailingTask(RequestDelegate next) : base(next)
{ }
bool IHttpHandler.IsReusable
{
get { throw new System.NotImplementedException(); }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
// Do something in DB and return result as JSON
// e.g. SQL with invalid syntax, or a missing parameter
}
}
It works fine as long as there are no errors in ProcessRequest.
But when an exception is thrown in ProcessRequest, it finishes the request with HTTP 200 OK.
What am I doing wrong ?
I get the exception, in both Invoke and SyncInvoke, but the HTTP request always finished as if everything was fine...
I have the following code(*) that implements polling using a scheduler that recursively calls the supplied observable.
(*) inspired from https://github.com/ReactiveX/RxJava/issues/448
This is working correctly when I only pass the onNext event to the subscriber. But when I pass the onError event to the subscriber, the unsubscribe event is called and this in turn kills the scheduler.
I'd like to also pass the errors to the subscriber. Any ideas how to achieve that?
public Observable<Status> observe() {
return Observable.create(new PollingSubscriberAction<>(service.getStatusObservable(), 5, TimeUnit.SECONDS));
}
private class PollingSubscriberAction<T> implements Observable.OnSubscribe<T> {
private Subscription subscription;
private Subscription innerSubscription;
private Scheduler.Worker worker = Schedulers.newThread().createWorker();
private Observable<T> observable;
private long delayTime;
private TimeUnit unit;
public PollingSubscriberAction(final Observable<T> observable, long delayTime, TimeUnit unit) {
this.observable = observable;
this.delayTime = delayTime;
this.unit = unit;
}
#Override
public void call(final Subscriber<? super T> subscriber) {
subscription = worker.schedule(new Action0() {
#Override
public void call() {
schedule(subscriber, true);
}
});
subscriber.add(Subscriptions.create(new Action0() {
#Override
public void call() {
subscription.unsubscribe();
if (innerSubscription != null) {
innerSubscription.unsubscribe();
}
}
}));
}
private void schedule(final Subscriber<? super T> subscriber, boolean immediately) {
long delayTime = immediately ? 0 : this.delayTime;
subscription = worker.schedule(createInnerAction(subscriber), delayTime, unit);
}
private Action0 createInnerAction(final Subscriber<? super T> subscriber) {
return new Action0() {
#Override
public void call() {
innerSubscription = observable.subscribe(new Observer<T>() {
#Override
public void onCompleted() {
schedule(subscriber, false);
}
#Override
public void onError(Throwable e) {
// Doesn't work.
// subscriber.onError(e);
schedule(subscriber, false);
}
#Override
public void onNext(T t) {
subscriber.onNext(t);
}
});
}
};
}
}
Both onError and onCompleted are terminating events, what means that your Observable won't emit any new events after any of them occurrs. In order to swallow/handle error case see error operators - https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators. Also, in order to implement polling you might take advantage of this one - http://reactivex.io/documentation/operators/interval.html
So I've been playing with this one for some time, and I don't think it's possible in the way you're doing it. Calling onError or onCompleted terminate the stream, flipping the done flag within the SafeSubscriber wrapper, and there just isn't a way to reset it.
I can see 2 options available - neither I think are particularly elegant, but will work.
1 - UnsafeSubscribe. Possibly not the best idea but it works, because instead of wrapping your Subscriber in a SafeSubscriber, it calls it directly. Best read the Javadoc to see if this is OK for you. Or, if you're feeling adventurous write your own SafeSubscriber where you can reset the done flag or similar. With your example, call like:
observe.unsafeSubscribe(...)
2 - Implement something similar to this example. I appreciate it's in C#, but it should be readable. Simply put - you want to create a Pair<T, Exception> class, and then rather than calling onError, call onNext and set the exception side of your pair. Your subscriber will have to be a little more clever to check for each side of the pair, and you might need to do some data transformation between your source Observable and the Observable<Pair<T, Exception>>, but I can't see why it won't work.
I'd be really interested in seeing another way of doing this if anyone has any.
Hope this helps,
Will
As #Will noted, you can't directly call onError without terminating the observable. Since you can only call onNext, I decided to use a Notification to wrap the value and the throwable in a single object.
import rx.*;
import rx.functions.Action0;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
import java.util.concurrent.TimeUnit;
public class PollingObservable {
public static <T> Observable<Notification<T>> create(Observable<T> observable, long delayTime, TimeUnit unit) {
return Observable.create(new OnSubscribePolling<>(observable, delayTime, unit));
}
private static class OnSubscribePolling<T> implements Observable.OnSubscribe<Notification<T>> {
private Subscription subscription;
private Subscription innerSubscription;
private Scheduler.Worker worker = Schedulers.newThread().createWorker();
private Observable<T> observable;
private long delayTime;
private TimeUnit unit;
private boolean isUnsubscribed = false;
public OnSubscribePolling(final Observable<T> observable, long delayTime, TimeUnit unit) {
this.observable = observable;
this.delayTime = delayTime;
this.unit = unit;
}
#Override
public void call(final Subscriber<? super Notification<T>> subscriber) {
subscription = worker.schedule(new Action0() {
#Override
public void call() {
schedule(subscriber, true);
}
});
subscriber.onStart();
subscriber.add(Subscriptions.create(new Action0() {
#Override
public void call() {
isUnsubscribed = true;
subscription.unsubscribe();
if (innerSubscription != null) {
innerSubscription.unsubscribe();
}
}
}));
}
private void schedule(final Subscriber<? super Notification<T>> subscriber, boolean immediately) {
if (isUnsubscribed) {
return;
}
long delayTime = immediately ? 0 : this.delayTime;
subscription = worker.schedule(createInnerAction(subscriber), delayTime, unit);
}
private Action0 createInnerAction(final Subscriber<? super Notification<T>> subscriber) {
return new Action0() {
#Override
public void call() {
innerSubscription = observable.subscribe(new Observer<T>() {
#Override
public void onCompleted() {
schedule(subscriber, false);
}
#Override
public void onError(Throwable e) {
subscriber.onNext(Notification.<T>createOnError(e));
schedule(subscriber, false);
}
#Override
public void onNext(T t) {
subscriber.onNext(Notification.createOnNext(t));
}
});
}
};
}
}
}
To use this, you can either use the notification directly:
PollingObservable.create(service.getStatus(), 5, TimeUnit.SECONDS)
.subscribe(new Action1<Notification<Status>>() {
#Override
public void call(Notification<Status> notification) {
switch (notification.getKind()) {
case OnNext:
Status status = notification.getValue();
// handle onNext event
break;
case OnError:
Throwable throwable = notification.getThrowable();
// handle onError event
break;
}
}
});
Or you can use the accept method on the notification to use a regular Observable:
PollingObservable.create(service.getStatus(), 5, TimeUnit.SECONDS)
.subscribe(new Action1<Notification<Status>>() {
#Override
public void call(Notification<Status> notification) {
notification.accept(statusObserver);
}
});
Observer<Status> statusObserver = new Observer<Status>() {
// ...
}
UPDATE 2015-02-24
It seems that the polling observable wasn't working correctly sometimes, because the inner observable would call onComplete or onError even after it had been unsubscribed, thus rescheduling itself. I added the isUnsubscribed flag to prevent that from happening.
I'm using latest android-simple-facebook library
(https://github.com/sromku/android-simple-facebook)
and, want to get friends list with name, picture(profile image).
but i cann't get friends picture at all..
below is my code...
At LoginListener
private OnLoginListener mOnLoginListener = new OnLoginListener() {
#Override
public void onFail(String reason) {
Log.w(TAG, "Failed to login");
}
#Override
public void onException(Throwable throwable) {
Log.e(TAG, "Bad thing happened", throwable);
}
#Override
public void onThinking() {
// show progress bar or something to the user while login is
// happening
}
#Override
public void onLogin() {
PictureAttributes pictureAttributes = Attributes.createPictureAttributes();
pictureAttributes.setType(PictureType.NORMAL);
pictureAttributes.setHeight(500);
pictureAttributes.setWidth(500);
// change the state of the button or do whatever you want
Properties properties = new Properties.Builder()
.add(Properties.ID)
.add(Properties.LAST_NAME)
.add(Properties.PICTURE, pictureAttributes)
.add(Properties.BIRTHDAY).build();
mSimpleFacebook.getFriends(properties, mOnFriendsListener);
}
#Override
public void onNotAcceptingPermissions(Permission.Type type) {
}
};
and the friends listener
// get friends listener
private OnFriendsListener mOnFriendsListener = new OnFriendsListener() {
#Override
public void onFail(String reason) {
// insure that you are logged in before getting the friends
Log.w(TAG, reason);
}
#Override
public void onException(Throwable throwable) {
Log.e(TAG, "Bad thing happened", throwable);
}
#Override
public void onThinking() {
// show progress bar or something to the user while fetching profile
Log.i(TAG, "Thinking...");
}
#Override
public void onComplete(List<Profile> friends) {
for (Profile profile : friends) {
mLists.add(new FriendItem(profile.getName(), profile.getPicture()));
}
mAdapter = new FriendsListAdapter(getActivity());
mFriendsList.setAdapter(mAdapter);
}
};
but the profile object only contains id and name.
should i call get method with async?
or whatever else i can do with getFriends() methods.
The permission lists is likes:
Permission[] permissions = new Permission[] {
Permission.BASIC_INFO,
Permission.USER_CHECKINS,
Permission.USER_EVENTS,
Permission.USER_GROUPS,
Permission.USER_LIKES,
Permission.USER_PHOTOS,
Permission.USER_VIDEOS,
Permission.FRIENDS_EVENTS,
Permission.FRIENDS_PHOTOS,
Permission.PUBLISH_STREAM };
For some reason you have to override onComplete() method inside onLogin to handle with the things that you asked for.
Response will have everything that you asked for in the permissions builder.
I spent a huge amount of time to figure out this. Hope it helps :)
final OnLoginListener onLoginListener = new OnLoginListener() {
#Override
public void onLogin(String accessToken, List<Permission> acceptedPermissions, List<Permission> declinedPermissions) {
OnProfileListener onProfileListener = new OnProfileListener() {
#Override
public void onComplete(final Profile response) {
super.onComplete(response);
.
. // Your code in here`enter code here`
.
});
}
Plz try these permissions:
Permission.PUBLIC_PROFILE,
Permission.USER_BIRTHDAY