Продолжаем говорить про 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, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня