Как проигнорировать неудачный запрос и продолжить отвечать на следующие запросы?

#android #observable #retrofit2 #rx-java2 #rx-android

Вопрос:

У меня есть функция, которая принимает список идентификаторов статей для установки на адаптере. Все работает нормально, пока хотя бы один из запросов не завершится неудачей. Тогда возвращенный список пуст. Как заставить его игнорировать неудачный запрос и перейти к следующему? Например, я запрашиваю 5 статей, 1 из которых не работает, 4 в порядке, поэтому я получаю список из 4.
Я знаю, что мне нужно использовать onErrorResumeNext() здесь, но я не знаю, как.

Интерфейс:

 @GET("articles/{id}") Observablelt;Articlesgt; getArticle1(@Path("id") int id);  

Активность:

 private void getMoreArticles(Listlt;Integergt; l) {    ApiInterface apiInterface = ApiClient.getApiClientRX().create(ApiInterface.class);   Listlt;Observablelt;?gt;gt; requests = new ArrayListlt;gt;();  for (int id : l) {  requests.add(apiInterface.getArticle1(id));  }   Observable.zip(requests, new Functionlt;Object[], Listlt;Articlesgt;gt;() {  @Override  public Listlt;Articlesgt; apply(@NonNull Object[] objects) {  Listlt;Articlesgt; articlesArrayList = new ArrayListlt;gt;();    for (Object response : objects) {  articlesArrayList.add((Articles) response);  }    return articlesArrayList;  }  })  .subscribeOn(Schedulers.io())  .observeOn(AndroidSchedulers.mainThread())  .onErrorResumeNext(Observable.lt;Listlt;Articlesgt;gt;empty())  .subscribe(  new Consumerlt;Listlt;Articlesgt;gt;() {  @Override  public void accept(Listlt;Articlesgt; articlesList) {  adapter = new Adapter(articlesList, MainActivity.this);  if (fav) recyclerView.setAdapter(adapter);  else addRV().setAdapter(adapter);  adapter.notifyDataSetChanged();  initListener();  swipeRefreshLayout.setRefreshing(false);  }  },  new Consumerlt;Throwablegt;() {  @Override  public void accept(Throwable e) throws Exception {   }  }  ).isDisposed();    }  

Ответ №1:

Я попытался немного упростить ваш вариант использования, но, надеюсь, вы поняли мою точку зрения. Вам нужно каким-то образом «просигнализировать», что в вашем вызове API возникла какая-то проблема, и этот конкретный Articles объект должен быть пропущен в .zip() функции молнии вашего оператора. Например, вы можете обернуть возвращаемое значение в Optional . Когда значение задано, это означает, что все прошло нормально. Если нет, вызов API не удался.

 class SO69737581 {   private Observablelt;Articlesgt; getArticle1(int id) {  return Observable.just(new Articles(id))  .map(articles -gt; {  if (articles.id == 2) { // 1.  throw new RuntimeException("Invalid id");  } else {  return articles;  }  });  }   Observablelt;Listlt;Articlesgt;gt; getMoreArticles(Listlt;Integergt; ids) {  Listlt;Observablelt;Optionallt;Articlesgt;gt;gt; requests = new ArrayListlt;gt;();  for (int id : ids) {  Observablelt;Optionallt;Articlesgt;gt; articleRequest = getArticle1(id)  .map(article -gt; Optional.of(article)) // 2.  .onErrorReturnItem(Optional.empty()); // 3.   requests.add(articleRequest);  }   return Observable.zip(requests, objects -gt; {  Listlt;Articlesgt; articlesArrayList = new ArrayListlt;gt;();   for (Object response : objects) {  Optionallt;Articlesgt; optionalArticles = (Optionallt;Articlesgt;) response;  optionalArticles.ifPresent(articlesArrayList::add); // 4.  }   return articlesArrayList;  });  } }  

Объяснение интересных деталей:

  1. Имитация ошибки API с идентификатором = 2
  2. Оберните результат API-интерфейса вызова в необязательный
  3. При возникновении ошибки возвращайте пустой необязательный параметр
  4. Добавьте значение статьи в массив результатов, если это значение присутствует

Проверка:

 public class SO69737581Test {   @Test  public void failedArticleCallsShouldBeSkipped() {  SO69737581 tested = new SO69737581();   TestObserverlt;Listlt;Articlesgt;gt; testSubscriber = tested  .getMoreArticles(Arrays.asList(1, 2, 3, 4))  .test();   Listlt;Articlesgt; result = Arrays.asList(  new Articles(1),  new Articles(3),  new Articles(4)  );   testSubscriber.assertComplete();  testSubscriber.assertValue(result);  } }  

Для полноты картины, вот как я определил Article класс:

 class Articles {   public int id;   public Articles(int id) {  this.id = id;  }   @Override  public boolean equals(Object o) {  if (this == o) return true;  if (o == null || getClass() != o.getClass()) return false;  Articles articles = (Articles) o;  return id == articles.id;  }   @Override  public int hashCode() {  return Objects.hash(id);  } }