Продолжаем говорить про Retrofit. Посмотрим, что и как мы можем настроить в нем, чтобы достичь своих целей.

 

Основы работы с Retrofit вы можете посмотреть в прошлом посте. Я же вкратце напомню, что две основных части его конфигурирования - это интерфейс и билдер. В интерфейсе мы описываем, какие методы мы будем вызывать, а билдере - задаем базовый URL и можем добавлять различные дополнения и обработчики для запросов и ответов.

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

 

 

 

Разделение ссылки

Предположим, у нас есть API метод:
http://server/api/v1/products/list.

Он возвращает список товаров. Нам нужно разделить эту ссылку на базовый URL и имя метода. Это можно сделать так:
http://server/api/v1/products/ - базовый URL
list - метод

Соответственно, интерфейс будет выглядеть так:

public interface ServerApi {

   @GET("list")
   Call<List<Product>> productList();

}

 

Но внезапно у нас в API добавляется еще один метод, который возвращает список заказов: 
http://server/api/v1/orders/list 

Если его разделить по той же схеме, то получится так:
http://server/api/v1/orders/ - базовый URL
list - метод

В интерфейс мы можем добавить метод orderList:

public interface ServerApi {

   @GET("list")
   Call<List<Product>> productList();

   @GET("list")
   Call<List<Order>> ordersList();

}

Но у нас в билдере использовался базовый URL:
http://server/api/v1/products/

А нам теперь нужно, чтобы для метода ordersList он был таким: 
http://server/api/v1/orders/ 

 

Можно конечно разделить все это на два отдельных интерфейса и билдера. Но есть решение и для текущей конфигурации. Надо просто по-другому выделять базовый URL из ссылок.

Берем две ссылки:
http://server/api/v1/products/list
http://server/api/v1/orders/list.

У них можно выделить общий базовый URL:
http://server/api/v1/

Его и будем использовать в билдере. А каталоги products и orders пойдут в интерфейс.

public interface ServerApi {

   @GET("products/list")
   Call<List<Product>> productList();

   @GET("orders/list")
   Call<List<Order>> orderList();

}

 

 

 

Query

Рассмотрим пример запроса с параметрами. Предположим, что метод http://server/api/v1/products/list поддерживает некоторые параметры.

Например, мы можем указать id категории товара:
http://server/api/v1/products/list?category=100

Или тип сортировки:
http://server/api/v1/products/list?sort=desc

Или оба вместе:
http://server/api/v1/products/list?category=100&sort=desc

 

В интерфейсе это можно сделать так:

@GET("products/list?category=100&sort=desc")
Call<List<Product>> productList();

И это даже будет работать, но понятно, что решение совсем топорное. Если сортировку еще можно так оставить, то категорию уж точно хотелось бы вынести в параметры. Используем для этого аннотацию Query:

@GET("products/list?sort=desc")
Call<List<Product>> productList(@Query("category") int categoryId);

Сортировку мы так и оставили там же, где указываем имя API метода. А категорию мы вынесли в параметры метода productList и добавили к ней аннотацию Query. В аннотации мы указали под каким именем этот параметр должен появится в URL запроса.

 

Вызов этого метода в коде будет выглядеть так:

Call<List<Product>> productList = serverApi.productList(100);

И в результате будет выполнен следующий запрос
http://server/api/v1/products/list?sort=desc&category=100

 

В одном методе можно указывать несколько Query параметров.

 

 

 

Path

Иногда API может быть такого вида

http://server/api/v1/products/123
или
http://server/api/v1/products/123/delete

Т.е. здесь id товара передается не параметром, а частью пути. В этом случае нам поможет аннотация Path.

 

Используем ее в методах интерфейса:

@GET("products/{id}")
Call<Product> product(@Path("id") int productId);

@GET("products/{id}/delete")
Call<Status> productDelete(@Path("id") int productId);

В строку с именем метода добавляем плэйсхолдер {id} в фигурных скобках. В параметры методов добавляем productId с аннотацией Path. В этой аннотации необходимо указать, в какой плэйсхолдер надо будет подставлять значение, пришедшее в productId. Указываем "id". 

При вызове, Retrofit возьмет значение productId и подставит его в строку запроса вместо {id}.

 

В одном методе можно указывать несколько Path параметров.

 

 

 

Логирование

Чтобы видеть, какие запросы уходят и какие ответы на них приходят, мы можем добавить к Retrofit логирование.

Для этого надо в build.gradle в секцию dependecies добавить зависимость:

compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'

 

Код конфигурации билдера теперь будет выглядеть так:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE);

OkHttpClient client = new OkHttpClient.Builder()
       .addInterceptor(interceptor)
       .build();

Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("http://server/api/v1/")
       .addConverterFactory(GsonConverterFactory.create())
       .client(client)
       .build();

Сначала создаем HttpLoggingInterceptor. В нем настраиваем уровень логирования. Если у нас Debug билд, то выставляем максимальный уровень (BODY), иначе - ничего не логируем, чтобы не палить в логах релизные запросы.

HttpLoggingInterceptor мы не можем напрямую передать в Retrofit билдер. Поэтому сначала создаем OkHttpClient, ему передаем HttpLoggingInterceptor, и уже этот OkHttpClient используем в Retrofit билдере.

В результате, в логе по тегу OkHttp вы увидите все ваши запросы со всеми параметрами, заголовками и содержимым.

 

 

 

RxJava

Чтобы использовать в проекте RxJava, необходимо добавить зависимости:

compile 'io.reactivex.rxjava2:rxjava:2.1.5'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

 

А для Retrofit нужен RxJava-адаптер:

compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

 

В качестве примера используем файл с json данными - https://rawgit.com/startandroid/data/master/messages/messages1.json

 

Описываем интерфейс:

public interface MessagesApi {

   @GET("messages1.json")
   Single<List<Message>> messages();

}

Вместо обертки Call мы используем Single из RxJava. Он вернет нам либо результат (onNext), либо ошибку (onError). А метод завершения (onCompleted) он вызывать не будет.

 

В билдере нам необходимо добавить RxJava2CallAdapterFactory:

Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("https://rawgit.com/startandroid/data/master/messages/")
       .addConverterFactory(GsonConverterFactory.create())
       .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
       .build();

 

Далее, создаем реализацию MessageApi и вызываем метод messages.

MessagesApi messagesApi = retrofit.create(MessagesApi.class);

messagesApi.messages()
       .subscribeOn(Schedulers.io())
       .observeOn(AndroidSchedulers.mainThread())
       .subscribe(new DisposableSingleObserver<List<Message>>() {
           @Override
           public void onSuccess(@NonNull List<Message> messages) {
               log("onSuccess " + messages.size());
           }

           @Override
           public void onError(@NonNull Throwable e) {
               log("onError " + e);
           }
       });

Метод messages вернет нам Single<List<Messages>>, на который мы подписываемся и настраиваем, чтобы запрос был выполнен в IO потоке, а результат вернулся в UI потоке.,

 

В случае использования RxJava, у нас уже нет объекта Response. И если сервер вернет какую-то ошибку, то мы получим ее в onError. Например, ошибка 404 будет выглядеть так:

onError retrofit2.adapter.rxjava2.HttpException: HTTP 404

 

 

 

Как получить чистый текст

Бывает необходимость получить от сервера данные в виде текста (plain text). Т.е. вам не надо, чтобы Retrofit распарсил текстовые данные в объекты. По каким-то причинам вы хотите получить String.

В интерфейсе указываем тип String

import retrofit2.Call;
import retrofit2.http.GET;

public interface MessagesApi {

   @GET("messages1.json")
   Call<String> messages();

}

 

Получить строку нам поможет конвертер ScalarsConverterFactory. Чтобы использовать его, пропишите зависимость:

compile 'com.squareup.retrofit2:converter-scalars:2.3.0'

 

Используем ScalarsConverterFactory в билдере

Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("https://rawgit.com/startandroid/data/master/messages/")
       .addConverterFactory(ScalarsConverterFactory.create())
       .build();

MessagesApi messagesApi = retrofit.create(MessagesApi.class);

 

Далее, все как обычно, получаем Call объект и асинхронно выполняем запрос:

Call<String> messages = messagesApi.messages();

messages.enqueue(new Callback<String>() {
   @Override
   public void onResponse(Call<String> call, Response<String> response) {
       log("response " + response.body());
   }

   @Override
   public void onFailure(Call<String> call, Throwable t) {

   }
});

Ответ придет в response.body()

 

В логах это будет выглядеть так:

response [{"id":1,"time":1454166946000,"text":"rhoncus dui vel...

Мы получили нераспарсенные json данные в виде текста.

 

 

 

Как сделать синхронный вызов

Если вы собираетесь выполнять запрос не в UI потоке, то его можно выполнить синхронно без всяких колбэков.

Это будет выглядеть так:

Call<String> messages = messagesApi.messages();
try {
   Response<List<Message>> response = messages.execute();
} catch (IOException e) {
   e.printStackTrace();
}

Этот код блокирует текущий поток. Ответ от сервера вы получите в Response объект. А если случится ошибка, то она придет в catch


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование



Похожие статьи


Последние статьи



Language

Система Orphus

Социальные сети

 

Telegram канал



Android чат в Telegram



Группа ВКонтакте



Страница в Facebook

Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal

Яндекс.Метрика