Дооснащение 2.0 + RxJava + Ошибка корпуса JSON

Я новичок в RxJava и Retrofit, и я пытаюсь написать свои вызовы API с ним. Все вызовы API возвращают тело JSON при ошибке, которая находится в общем формате как,

{"errors":[{"code":100, "message":"Login/Password not valid", "arguments":null}]}

В настоящее время мой код для вызова API входа (другие также похожи),

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);
            }
        });

Когда я получаю сообщение об ошибке в onError(), я хотел бы автоматически декодировать JSON в корпусе ошибки POJO и использовать это. Есть ли способ сделать это предпочтительно в одном месте для всех других вызовов API. Любая помощь приветствуется.

Ответы

Ответ 1

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

<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);
                }
            });
        }
    };
}

Обратите внимание на комментарии в коде, так как вам нужно принять решение, хотите ли вы сообщить об анализируемом ответе onError() или onNext().

Затем вы можете использовать этот трансформатор в любом месте своих API-вызовов, например:

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()
                }
            }
        });

Ответ 2

Также может возникнуть проблема десериализации. Вы можете использовать модифицированный конвертер для десериализации его (или сделать это самостоятельно).

Мое решение добавляет немного к мурти:

<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);
                    }
                });
            }
        };
    }

а затем в 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
}