Возврат пользовательских исключений в случае сбоя с помощью RxJava + Retrofit

У меня есть класс репозитория, который должен возвращать это: Observable<List<SomeObject>, я делаю так:

 @Override
    public Observable<List<SomeObject>> getAllById(Long id) {
        if (!AndroidUtils.isNetworkAvailable(mContext))
            return Observable.error(new NoNetworkConnectionException());

        return mRestService.get(id);
    }

Этот подход работает нормально, проблема в том, что я хочу возвращать пользовательские исключения в случае сбоев, но я не знаю, как лучше всего это сделать с помощью rxjava.

Пока единственное решение, которое работает, это что-то вроде этого:

@Override
public Observable<List<SomeObject>> getAllById(Long id) {
    if (!AndroidUtils.isNetworkAvailable(mContext))
        return Observable.error(new NoNetworkConnectionException());

    return Observable.create(subscriber -> {
        mRestService.get(id).subscribe(new Observer<List<SomeObject>>() {
            @Override
            public void onCompleted() {
                subscriber.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                if (e instanceof HttpException  && ((HttpException) e).code() == 401)
                    subscriber.onError(new UnathorizedException());
                else
                    subscriber.onError(e);
            }

            @Override
            public void onNext(List<SomeObject> objects) {
                subscriber.onNext(objects);
            }
        });
    });
}

Я знаю, что использовать Observable.create не очень хорошо, но я не могу найти другого способа сделать это.

RestService это:

public interface RestService {

    @GET("objects/{id}")
    Observable<List<SomeObject>> get(@Path("id") Long id);
}

Если кто-то знает лучший подход, пожалуйста, скажите мне.

Спасибо!


person Luiz    schedule 06.08.2016    source источник


Ответы (3)


Вы можете использовать оператор onErrorResumeNext, чтобы сопоставить ваше исключение с другим.

mRestService.get(id)
            .onErrorResumeNext(e -> {
                if (e instanceof HttpException  && ((HttpException) e).code() == 401)
                    return Observable.error(new UnathorizedException());
                else
                    return Observable.error(e);
            })
            .subscribe();
person dwursteisen    schedule 08.08.2016
comment
Извините за задержку с отзывом. Я протестировал и отлично работает. Итак, любые исключения, которые могут возникнуть в запросе, заканчиваются вызовом onErrorResumeNext()? Есть ли недостатки в этой реализации, например, в использовании Observable.create()? - person Luiz; 23.08.2016
comment
Большое спасибо! я просто знаю об этом - person lukman nudin; 02.03.2021

Я сделал это в проекте, добавив перехватчик при создании остальной службы. Таким образом, ошибки проверяются до того, как запрос достигнет службы отдыха.

OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();

httpClientBuilder.addInterceptor(new ErrorInterceptor());

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(myBaseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .client(httpClientBuilder.build())
            .build();

Класс ErrorInterceptor выглядит так

public class ErrorInterceptor implements Interceptor {

    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Response response = chain.proceed(originalRequest);

        if (response.code() >= 400) {
            throwError(response);
            return response;
        } else {
            return response;
        }
    }

    private void throwError (Response response) throws IOException {
        ResponseBody responseBody = response.body();
        BufferedSource source = responseBody.source();
        source.request(Long.MAX_VALUE); // Buffer the entire body.
        Buffer buffer = source.buffer();

        Charset charset = UTF8;
        MediaType contentType = responseBody.contentType();
        if (contentType != null) {
            charset = contentType.charset(UTF8);
        }

        if (responseBody.contentLength() != 0) {
            String responseJSON = buffer.clone().readString(charset);
            Gson gson = new Gson();
            Type type = new TypeToken<ErrorResponse>() {}.getType();
            ErrorResponse error = null;
            try {
                error = gson.fromJson(responseJSON, type);
            }
            catch (Exception e) {
                int a = 1;
            }
            if (error != null && error.hasErrors())
                throw ErrorMapper.mapError(error.getFirstError());
        }
    }
}

И мой класс ErrorResponse

public class ErrorResponse {

    private List<Error> errors;

    public boolean hasErrors () {
        return errors != null && errors.size() > 0;
    }

    public Error getFirstError() {
        if (errors == null || errors.size() == 0) return null;
        return errors.get(0);
    }
}

В моем ErrorMapper я просто сравниваю сообщение об ошибке с набором возможных сообщений с сервера и создаю новую ошибку, содержащую сообщение для отображения на клиенте.

Я просто проверяю первую ошибку здесь, но вы легко сможете адаптировать ее к множеству ошибок.

person jkettmann    schedule 07.08.2016

Вы можете попробовать следующее:

    RestService restService = Mockito.mock(RestService.class);

    Observable<List<Object>> networkExOrEmpty = isNetworkAvailable() ?
            Observable.empty() :
            Observable.error(new NoNetworkConnectionException());

    Observable<List<Object>> resultOrEx = restService
            .getAllById(42L)
            .materialize()
            .map(res -> {
                if (res.isOnError()) {
                    Throwable err = res.getThrowable();
                    if (err instanceof HttpException && ((HttpException) err).code() == 401) {
                        return Notification.createOnError(new UnauthrizedException());
                    } else {
                        return Notification.createOnError(err);
                    }
                } else {
                    return res;
                }
            }).dematerialize();


    Observable<List<Object>> result = networkExOrEmpty.concatWith(resultOrEx);

Начните с Observable, который выдает либо ошибку, либо ничего, в зависимости от состояния подключения к сети, затем объедините его с результатом службы Retrofit. Observable.materialize() позволяет воздействовать на элементы ошибок: отправлять соответствующие исключения вниз по течению и передавать уведомления об ошибках как есть.

person m.ostroverkhov    schedule 07.08.2016