Android Retrofit2: как передать параметры запроса запроса в ответ?

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

Я полагаюсь на retrofit2, настроенный на использование RxJava для упаковки ответов и GSon для их анализа.

Я использую дооснащение для службы REST.

@GET("search")
Observable<SearchResult> search(@Query("q") String query);

@GET("search")
Observable<SearchResult> search(@QueryMap Map<String, String> params);

@GET("search")
Observable<SearchResult> searchNext(@Query("q") String query, @Query("startKey") String startKey);

@GET("search")
Observable<SearchResult> searchNext(@QueryMap Map<String, String> params, @Query("startKey") String startKey);

Служба поиска REST возвращает JSON

{
  "count": 2334,
  "nextBatchKey": "SADjdfsahoi023451sadfjlskdfj02134512",
  "results": [ .... ]
}
  • общий счет результата
  • ключ, который будет использоваться для получения следующей партии списка
  • фактический массив результатов

Чтобы получить следующий пакет, необходимо снова вызвать службу с теми же параметрами и вернуть ключ.

Например, предположим, что выполняется поиск q=foo и limit=20: REST API вернет результаты, соответствующие строке foo, сгруппированные по сегментам по 20 результатов в каждом. Чтобы получить следующий пакет, мне нужно было бы создать еще один запрос с теми же двумя параметрами, q=foo и limit=20, добавив startKey=<the next batch key>.

Я хочу / мне нужно скрыть от пользователя библиотеки этот внутренний механизм.

Внутри моей библиотеки я инициализирую модернизацию следующим образом:

Retrofit retrofit = new Retrofit.Builder()
    .client(okHttpClient) // added interceptors
    .baseUrl(baseUrl) // my base url
    .addConverterFactory(GsonConverterFactory.create(gson)) // custom type adapter factory
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();

Он генерирует службу для моего интерфейса и использует Gson для анализа ответа, заключая его в RxJava Observable.

Затем я получаю услугу:

MyService service = retrofit.create(MyService.class)

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

С точки зрения пользователя библиотеки все это скрыто, он просто получит услугу и будет использовать ее так:

// obtain the service
MyService service = MyLibrarySDK.getService(context);
// perform the search operation
Observable<SearchResult> resultObservable =
    service.search(params); 

Я хочу, чтобы разработчик, использующий мою библиотеку, мог получить следующую партию следующим образом:

// then later result == the SearchResult
Observable<SearchResult> nextBatch =
    result.searchNext();

Вызов searchNext() не принимает параметров. Это означает, что объект SearchResult, который я создал в предыдущем вызове, должен внутренне знать, какие параметры использовать при использовании метода searchNext(Map parameters, String key).

Если пользователь выполняет поисковый вызов с q=foo, эта информация не поступает в ответ. И мне это нужно.

Поскольку модификация создает службу, я не знаю, как я могу перехватить параметры метода, переданные с вызовом service.search().

Единственная идея, которую мне пришлось решить, - это использовать OkHttp перехватчик, поместить карту параметров запроса в ThreadLocal, а затем использовать пользовательский Gson TypeAdapter, чтобы вставить эти параметры в объект SearchResult.

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

Есть ли у вас лучшая / креативная / элегантная идея?


person Daniele Segato    schedule 15.03.2016    source источник
comment
Разве вы не получили бы параметры запроса из модели данных? Вы делаете что-то подобное - github.com/codepath/android_guides/wiki /?   -  person Krishnaraj    schedule 15.03.2016
comment
@Krishnaraj, мой вопрос был написан плохо? Потому что то, что вы спрашиваете, заставляет меня думать, что вы этого не поняли. Я могу улучшить вопрос, если ты скажешь, где он запутался   -  person Daniele Segato    schedule 15.03.2016
comment
В любом случае @Krishnaraj, предполагая, что с моделью данных вы имеете в виду json-ответ поиска: он содержит только результаты, общее количество и ключ для получения следующего пакета. Таким образом, если мне нужно получить следующую партию, мне понадобится этот ключ и все параметры, использованные для первого вызова. Я хочу сделать это прозрачным в клиентском API, поэтому мне нужен способ передачи этих параметров.   -  person Daniele Segato    schedule 15.03.2016
comment
@Krishnaraj, ты думаешь, мне следует отредактировать свой вопрос, чтобы он был более ясным?   -  person Daniele Segato    schedule 15.03.2016
comment
Я думаю, будет лучше, если вы добавите еще несколько деталей.   -  person Krishnaraj    schedule 15.03.2016
comment
@Krishnaraj попытался улучшить его, надеюсь, у меня получится, спасибо   -  person Daniele Segato    schedule 16.03.2016
comment
Разве недостаточно хорошо для цепочки запросов и наблюдения?   -  person Mimmo Grottoli    schedule 21.03.2016
comment
@MimmoGrottoli Думаю, я не понимаю, что ты имеешь в виду   -  person Daniele Segato    schedule 21.03.2016
comment
Я бы посоветовал просто создавать вызовы OkHttp вручную вместо использования Retrofit, если вам нужен такой уровень настройки и контроля над тем, что вы возвращаете. Однако, если вы склонны использовать Retrofit, вы можете добиться того, чего хотите, либо создав оболочку вокруг интерфейса MyService, который вводит необходимые данные в модель результатов, либо создайте пользовательский CallAdapter, который анализирует Request из параметров Call. он получает и генерирует желаемые объекты результата на его основе (возможный вариант реализации RxJavaCallAdapterFactory).   -  person corsair992    schedule 27.03.2016
comment
Я, вероятно, буду использовать Java-прокси для своего интерфейса   -  person Daniele Segato    schedule 27.03.2016


Ответы (1)


Не думайте, что у вас должен быть searchNext() на result, а лучше на service.

Попробуйте что-то вроде ниже:

MyService.java

public class MyService {
    RestClient.GitApiInterface service = RestClient.getClient();
    String query;
    Observable<GitResult> resultObservable;

    public Observable<GitResult> getUsersByName(String query) {
        this.query = query;
        resultObservable = service.getUsersByName(query, 0).cache();
        return resultObservable;
    }

    public Observable<GitResult> searchNext() {
        Observable nextResultObservable = resultObservable
                .flatMap(new Func1<GitResult, Observable<?>>() {
                    @Override
                    public Observable<?> call(GitResult gitResult) {
                        return service.getUsersByName(query, gitResult.getNextPage());
                    }
                });

        return nextResultObservable;
    }
} 

Использование

final MyService service = new MyService();
// First load
service.getUsersByName("tom");
// consecutive calls
service.searchNext();

Пример приложения - https://gitlab.com/wizardkrishna/retrofit2-sample < / а>

Ссылки
Использование REST API с помощью Retrofit2
Наблюдаемое кеширование

person Krishnaraj    schedule 24.03.2016
comment
Как вы думаете, почему использование метода SearchResult.next () - это плохо? - person Daniele Segato; 24.03.2016
comment
SearchResult, вероятно, является модельным классом, привязка к нему вызова службы не кажется хорошей идеей. - person Krishnaraj; 24.03.2016
comment
Я думаю, что иметь сервис с состоянием еще хуже :) - person Daniele Segato; 29.03.2016
comment
MyService - это просто оболочка вокруг самого сервиса - RestClient.GitApiInterface service, вы могли бы назвать это как-нибудь иначе :) - person Krishnaraj; 29.03.2016
comment
это не имя. это роль этого компонента. тот факт, что результат для этого метода зависит от того, что вы вызвали ранее. это так неправильно. - person Daniele Segato; 29.03.2016
comment
Я хочу немного уточнить ... Да, наличие next() на модели кажется чем-то необычным, но думайте об этом так: service.searchNext(previousResult) для того, чтобы это работало, мне нужно иметь в предыдущем результате данные, которые мне нужны реконструировать запрос. И мой вопрос касался переноса параметров запроса в результат посредством модификации. Я могу сделать это, создав оболочку для услуги по модернизации (что я и сделал), но было бы неплохо, если бы был способ сделать это с помощью модернизации. - person Daniele Segato; 25.08.2019