RxJava и Retrofit2: связывание зависимых наблюдаемых результатов для возврата комбинированного pojo «за один раз»

(Профессионал Android/Java, новичок RxJava/Lambda) Я пытаюсь создать следующее pojo:

public class ProductCombinedPojo {
    private ProductPojo product;
    private List<TemplatePojo> templates;

    // builder pattern...
}

где ProductPojo это:

public class ProductPojo {
    private List<String> templateUrls; // each url returns a TemplatePojo

    // lots of other stuff...
}

У меня есть следующие реализации Retrofit2:

@GET
Observable<ProductPojo> getProduct(@Url String url);

@GET
Observable<TemplatePojo> getTemplate(@Url String url);

Таким образом, первый Observable возвращает ProductPojo, результирующий список URL-адресов внутри повторяется и вводится во второй Observable для получения списка TemplatePojo, наконец, все результаты объединяются в новый ProductCombinePojo с использованием шаблона построителя. Еще больше усложняет ситуацию то, что из-за природы MVP-фреймворка все это должно быть сделано в одной Func0<<Observable<ProductCombinedPojo>> связанной реализации RxJava.

У меня возникли трудности в конце цепочки, чтобы чисто получить исходный ProductPojo для ввода в билдер. Вот мое рабочее, но уродливое решение (предположим, что mUrl, mProductApi и mTemplateApi все введены и определены, как указано выше):

@Override
public Observable<ProductCombinedPojo> call() {
    final ProductPojo[] aProductPojo = new ProductPojo[1]; // <------------ Ugly!
    return mProductApi
        .getProduct(mUrl)
        .flatMapIterable(new Func1<ProductPojo, List<String>>() {
            @Override
            public List<String> call(ProductPojo productPojo) {
                aProductPojo[0] = productPojo;             // <------------ Ugly!
                return productPojo.getTemplateUrls();
            }
        })
        .flatMap(new Func1<String, Observable<TemplatePojo>>(){
            @Override
            public Observable<TemplatePojo> call(String templateUrl) {
                return mTemplateApi.getTemplate(templateUrl);
            }
        })
        .toList()
        .map(new Func1<List<TemplatePojo>, ProductCombinedPojo>() {
            @Override
            public ProductCombinedPojo call(List<TemplatePojo> templatePojos) {
                return ProductCombinedPojo.Builder.aProductCombinedPojo()
                        .product(aProductPojo[0])          // <------------ Ugly!
                        .templates(templatePojos)
                        .build();
            }
        });
}

Как мне переписать это, чтобы мне не нужен был уродливый final ProductPojo[]? После исчерпывающего поиска и рассмотрения многих подобных вопросов на этом форуме, я думаю,

Func2<ProductPojo, List<TemplatePojo>, ProductCombinedPojo>

должен быть подключен куда-то, но я не могу понять, где именно. Хотя мне интересно, как будет выглядеть решение Lambda, правильный ответ будет присужден любому решению, использующему формат, указанный выше.


person Myles Bennett    schedule 12.11.2016    source источник
comment
Как правило, вы можете комбинировать два (или более) элемента без создания явного комбинированного pojo, используя кортежи, подобные этому: javatuples.org   -  person EpicPandaForce    schedule 12.11.2016
comment
Вам все равно нужно каким-то образом пропустить ProductPojo вниз по цепочке операторов, независимо от того, вернули ли вы в конечном итоге явное pojo или кортеж. Наарман упоминает Кортеж Pair ниже. Я думал о создании комбинации Tuple в первом flatMap. Проблема в том, что последующий вывод flatMapIterable или from должен быть итерируемым, а готовые кортежи не являются итерируемыми (поправьте меня, если я ошибаюсь).   -  person Myles Bennett    schedule 15.11.2016


Ответы (1)


Проблема в том, что с каждой операцией, такой как map или flatMap, вы преобразуете ввод в новый вывод. Если вы не включите ввод в вывод, вы не сможете получить доступ к этому вводу позже. Вот с чем вы сталкиваетесь здесь: вы хотите иметь доступ к ProductPojo дальше по течению.

Вы можете обойти это, возвращая Pair<ProductPojo, List<String>> в свою функцию flatMapIterable, но это тоже не становится лучше.

Вместо этого вы можете создать новый Observable в области вашего ProductPojo:

public Observable<ProductCombinedPojo> call() {
    return mProductApi.getProduct(mUrl)
          .flatMap(new Func1<ProductPojo, Observable<ProductCombinedPojo>>() {
              @Override
              public Observable<ProductCombinedPojo> call(ProductPojo productPojo) {
                  return combinedPojoFor(productPojo);
              }
          });
}

private Observable<ProductCombinedPojo> combinedPojoFor(final ProductPojo productPojo) {
    return Observable.from(productPojo.getTemplateUrls())
          .flatMap(new Func1<String, Observable<TemplatePojo>>() {
              @Override
              public Observable<TemplatePojo> call(String templateUrl) {
                  return mTemplateApi.getTemplate(templateUrl);
              }
          })
          .toList()
          .map(new Func1<List<TemplatePojo>, ProductCombinedPojo>() {
              @Override
              public ProductCombinedPojo call(List<TemplatePojo> templatePojos) {
                  return ProductCombinedPojo.Builder.aProductCombinedPojo()
                        .product(productPojo)
                        .templates(templatePojos)
                        .build();
              }
          });
}

Использование лямбд:

public Observable<ProductCombinedPojo> call() {
    return mProductApi.getProduct(mUrl)
        .flatMap((productPojo) -> combinedPojoFor(productPojo));
}

private Observable<ProductCombinedPojo> combinedPojoFor(final ProductPojo productPojo) {
    return Observable.from(productPojo.getTemplateUrls())
        .flatMap((templateUrl) -> mTemplateApi.getTemplate(templateUrl))
        .toList()
        .map((templatePojos ->
            ProductCombinedPojo.Builder.aProductCombinedPojo()
                .product(productPojo)
                .templates(templatePojos)
                .build()
        ));
}
person nhaarman    schedule 12.11.2016
comment
Это намного аккуратнее, чем мое решение, хотя по сути это одно и то же (ProductPojo хранится в финале). Я очень надеялся, что в огромном арсенале наблюдаемых операторов найдется что-то, что подойдет для этого сценария. Спасибо, что нашли время и усилия, чтобы дать такой четкий ответ. Я почти наверняка отмечу его как принятый. - person Myles Bennett; 15.11.2016