Intereting Posts
Android Studio всегда отключается сразу после начала «отладки» Как отделить метаданные и дорожку от потока крики, не делая отдельного запроса на метаданные и потоковое В Android 7 (уровень API 24) моему приложению не разрешено отключать звук телефона (установить режим звонка на тихий) Как создать потокобезопасный ContentProvider? Соединение с adb было прервано, и попытки повторного подключения не удались После обновления службы Google Play до версии 13 я получил сообщение об ошибке Viewpager setCurrentItem не меняет tablayout выбранного цвета Auto Collapse ActionBar SearchView на мягкой клавиатуре закрыть Ошибка с аннотациями Override в Eclipse Рекурсивный метод работает в java с консолью, но не с android Динамический alertdialog с переключателями Видео с записью на Android без звука Интерфейс выполнения UI Замораживает / замедляет Android-колонка «_id» не существует? Как создать Drawable из ресурса

Обработка исключений API в RxJava

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

В принципе, у меня есть (Retrofit) сервис, который возвращает Observable<ServiceResponse> . ServiceResponse определяется следующим образом:

 public class ServiceResponse { private int status; private String message; private JsonElement data; public JsonElement getData() { return data; } public int getStatus() { return status; } public String getMessage() { return message; } } 

Теперь я хочу отобразить этот общий ответ на List<Account> содержащийся в поле данных JsonElement (я предполагаю, что вас не волнует, как выглядит объект Account , поэтому я не буду загрязнять эту запись). Следующий код работает очень хорошо для случая успеха, но я не могу найти хороший способ справиться с моими исключениями API:

 service.getAccounts() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(new Func1<ServiceResponse, AccountData>() { @Override public AccountData call(ServiceResponse serviceResponse) { // TODO: ick. fix this. there must be a better way... ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus()); switch (responseType) { case SUCCESS: Gson gson = new GsonBuilder().create(); return gson.fromJson(serviceResponse.getData(), AccountData.class); case HOST_UNAVAILABLE: throw new HostUnavailableException(serviceResponse.getMessage()); case SUSPENDED_USER: throw new SuspendedUserException(serviceResponse.getMessage()); case SYSTEM_ERROR: case UNKNOWN: default: throw new SystemErrorException(serviceResponse.getMessage()); } } }) .map(new Func1<AccountData, List<Account>>() { @Override public List<Account> call(AccountData accountData) { Gson gson = new GsonBuilder().create(); List<Account> res = new ArrayList<Account>(); for (JsonElement account : accountData.getAccounts()) { res.add(gson.fromJson(account, Account.class)); } return res; } }) .subscribe(accountsRequest); 

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

Заранее спасибо!

Редактировать:

Позвольте мне уточнить, чего я хочу достичь:

Я хочу иметь класс, который можно вызывать из пользовательского интерфейса (например, Activity, Fragment или что-то еще). Этот класс примет Observer<List<Account>> как параметр, например:

 public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) { ... } 

Этот метод вернет подписку, которая может быть отписана при отключении / уничтожении пользовательского интерфейса / etc.

Параметрированный наблюдатель обработает onNext для успешных ответов, проходящих в списке учетных записей. OnError обрабатывал бы любые исключения, но также получал бы любые исключения API (например, если бы статус ответа! = 200, мы бы создали Throwable и передали его onError). В идеале я не хочу просто «бросать» Исключение, я хочу передать его непосредственно Наблюдателю. Вот что я вижу на всех примерах.

Усложнение состоит в том, что моя служба Retrofit возвращает объект ServiceResponse , поэтому мой наблюдатель не может подписаться на это. Самое лучшее, что я придумал, – создать обсервер Observer вокруг моего наблюдателя, например:

 @Singleton public class AccountsDatabase { private AccountsService service; private List<Account> accountsCache = null; private PublishSubject<ServiceResponse> accountsRequest = null; @Inject public AccountsDatabase(AccountsService service) { this.service = service; } public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) { ObserverWrapper observerWrapper = new ObserverWrapper(observer); if (accountsCache != null) { // We have a cached value. Emit it immediately. observer.onNext(accountsCache); } if (accountsRequest != null) { // There's an in-flight network request for this section already. Join it. return accountsRequest.subscribe(observerWrapper); } if (accountsCache != null && !forceRefresh) { // We had a cached value and don't want to force a refresh on the data. Just // return an empty subscription observer.onCompleted(); return Subscriptions.empty(); } accountsRequest = PublishSubject.create(); accountsRequest.subscribe(new ObserverWrapper(new EndObserver<List<Account>>() { @Override public void onNext(List<Account> accounts) { accountsCache = accounts; } @Override public void onEnd() { accountsRequest = null; } })); Subscription subscription = accountsRequest.subscribe(observerWrapper); service.getAccounts() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(accountsRequest); return subscription; } static class ObserverWrapper implements Observer<ServiceResponse> { private Observer<List<Account>> observer; public ObserverWrapper(Observer<List<Account>> observer) { this.observer = observer; } @Override public void onCompleted() { observer.onCompleted(); } @Override public void onError(Throwable e) { observer.onError(e); } @Override public void onNext(ServiceResponse serviceResponse) { ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus()); switch (responseType) { case SUCCESS: Gson gson = new GsonBuilder().create(); AccountData accountData = gson.fromJson(serviceResponse.getData(), AccountData.class); List<Account> res = new ArrayList<>(); for (JsonElement account : accountData.getAccounts()) { res.add(gson.fromJson(account, Account.class)); } observer.onNext(res); observer.onCompleted(); break; default: observer.onError(new ApiException(serviceResponse.getMessage(), responseType)); break; } } } } 

Я все еще чувствую, что не использую это правильно. Я определенно не видел, чтобы кто-либо еще использовал ObserverWrapper раньше. Возможно, мне не следует использовать RxJava, хотя ребята из SoundCloud и Netflix действительно продали меня в своих презентациях, и я очень хочу ее изучить.

Solutions Collecting From Web of "Обработка исключений API в RxJava"

Пожалуйста, прочтите ниже. Я добавил редактирование.

Совершенно правильно бросать в Action / Func / Observer с RxJava. Исключение будет распространяться по структуре вплоть до вашего наблюдателя. Если вы ограничиваете себя вызовом onError только тогда, вы будете крутить себя, чтобы это произошло.

С учетом сказанного было бы предложение просто удалить эту оболочку и добавить простое действие проверки в цепочке service.getAccount … Observables.

Я бы использовал doOnNext (новый ValidateServiceResponseOrThrow), прикованный цепью с картой (новый MapValidResponseToAccountList). Это простые классы, которые реализуют необходимый код, чтобы сохранить цепочку Observable немного более читаемой.

Вот ваш метод loadAccount упрощен, используя то, что я предложил.

 public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) { if (accountsCache != null) { // We have a cached value. Emit it immediately. observer.onNext(accountsCache); } if (accountsRequest != null) { // There's an in-flight network request for this section already. Join it. return accountsRequest.subscribe(observer); } if (accountsCache != null && !forceRefresh) { // We had a cached value and don't want to force a refresh on the data. Just // return an empty subscription observer.onCompleted(); return Subscriptions.empty(); } accountsRequest = PublishSubject.create(); accountsRequest.subscribe(new EndObserver<List<Account>>() { @Override public void onNext(List<Account> accounts) { accountsCache = accounts; } @Override public void onEnd() { accountsRequest = null; } }); Subscription subscription = accountsRequest.subscribe(observer); service.getAccounts() .doOnNext(new ValidateServiceResponseOrThrow()) .map(new MapValidResponseToAccountList()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(accountsRequest); return subscription; } private static class ValidateResponseOrThrow implements Action1<ServiceResponse> { @Override public void call(ServiceResponse response) { ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus()); if (responseType != SUCCESS) throw new ApiException(serviceResponse.getMessage(), responseType)); } } private static class MapValidResponseToAccountList implements Func1<ServiceResponse, List<Account>> { @Override public Message call(ServiceResponse response) { // add code here to map the ServiceResponse into the List<Accounts> as you've provided already } } 

Изменить: если кто-то не говорит иначе, я думаю, что лучше всего возвращать ошибки с помощью flatMap. В прошлом я выбрал «Исключения из действия», но я не считаю, что это рекомендуемый способ.

Если вы используете flatMap, у вас будет более чистый стек Exception. Если вы выбросите изнутри действие, то в стеке Exception будут содержаться rx.exceptions.OnErrorThrowable$OnNextValue Exception, которое не является идеальным.

Позвольте мне продемонстрировать пример выше, используя вместо этого flatMap.

 private static class ValidateServiceResponse implements rx.functions.Func1<ServiceResponse, Observable<ServiceResponse>> { @Override public Observable<ServiceResponse> call(ServiceResponse response) { ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus()); if (responseType != SUCCESS) return Observable.error(new ApiException(serviceResponse.getMessage(), responseType)); return Observable.just(response); } } service.getAccounts() .flatMap(new ValidateServiceResponse()) .map(new MapValidResponseToAccountList()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(accountsRequest); 

Как вы видите, разница тонкая. ValidateServiceResponse теперь реализует Func1 вместо Action1 и мы больше не используем ключевое слово throw . Observable.error(new Throwable) этого мы используем Observable.error(new Throwable) . Я считаю, что это лучше подходит для ожидаемого контракта Rx.

Вы можете прочитать эту хорошую статью об обработке ошибок http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/