- Retrofit
- Задача первая. POJO
- Задача вторая. Интерфейс
- Аннотации
- @Query
- @Headers
- @Multipart
- @FormUrlEncoded
- Задача третья. Retrofit
- Перехватчики (Interceptors)
- HttpLoggingInterceptor
- RxJava
- Retrofit. Query, Path, RxJava, логи
- Разделение ссылки
- Query
- Логирование
- RxJava
- Как получить чистый текст
- Как сделать синхронный вызов
Retrofit
Многие сайты имеют собственные API для удобного доступа к своим данным. На данный момент самый распространённый вариант — это JSON. Также могут встречаться данные в виде XML и других форматов.
Библиотека Retrofit упрощает взаимодействие с REST API сайта, беря на себя часть рутинной работы.
Авторами библиотеки Retrofit являются разработчики из компании «Square», которые написали множество полезных библиотек, например, Picasso, Okhttp, Otto.
Библиотекой удобно пользоваться для запроса к различным веб-сервисам с командами GET, POST, PUT, DELETE. Может работать в асинхронном режиме, что избавляет от лишнего кода.
В основном вам придётся работать с методами GET и POST. Если вы будет создавать собственный API, то будете использовать и другие команды.
В Retrofit 2.x автоматически подключается библиотека OkHttp и её не нужно прописывать отдельно.
Библиотека может работать с GSON и XML, используя специальные конвертеры, которые следует указать отдельно.
Затем в коде конвертер добавляется с помощью метода addConverterFactory().
Список готовых конвертеров:
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
Также вы можете создать свой собственный конвертер, реализовав интерфейс на основе абстрактного класса Converter.Factory.
Можно подключить несколько конвертеров (порядок важен).
Если вы хотите изменить формат какого-нибудь JSON-объекта, то это можно сделать с помощью GsonConverterFactory.create():
Базовый URL всегда заканчивается слешем /. Задаётся в методе baseUrl().
Можно указать полный URL в запросе, тогда базовый URL будет проигнорирован:
Для работы с Retrofit понадобятся три класса.
- POJO (Plain Old Java Object) или Model Class — json-ответ от сервера нужно реализовать как модель
- Retrofit — класс для обработки результатов. Ему нужно указать базовый адрес в методе baseUrl()
- Interface — интерфейс для управления адресом, используя команды GET, POST и т.д.
Работу с Retrofit можно разбить на отдельные задачи.
Задача первая. POJO
Задача первая — посмотреть на структуру ответа сайта в виде JSON (или других форматов) и создать на его основе Java-класс в виде POJO.
POJO удобнее создавать с помощью готовых веб-сервисов в автоматическом режиме. Либо можете самостоятельно создать класс, если структура не слишком сложная.
В классе часто используются аннотации. Иногда они необходимы, иногда их можно пропустить. В некоторых случаях аннотации помогают избежать ошибок. Список аннотаций зависит от типа используемого конвертера, их список можно посмотреть в соответствующей документации.
Задача вторая. Интерфейс
Задача вторая — создать интерфейс и указать имя метода. Добавить необходимые параметры, если они требуются.
В интерфейсе задаются команды-запросы для сервера. Команда комбинируется с базовым адресом сайта (baseUrl()) и получается полный путь к странице. Код может быть простым и сложным. Можно посмотреть примеры в документации.
Запросы размещаются в обобщённом классе Call с указанием желаемого типа.
В большинстве случаев вы будете возвращать объект Call с нужным типом, например, Call . Если вас не интересует тип ответа, то можете указать Call .
Здесь также используются аннотации, но уже от самой библиотеки.
С помощью аннотации указываются веб-команды, а затем Java-метод. Для динамических параметров используются фигурные скобки (users/
В самой аннотации используется метод, используемый на сервере, а ниже вы можете указать свой вариант (полезно для соответствия стилю вашего кода.
Аннотации
Аннотация | Описание |
---|---|
@GET() | GET-запрос для базового адреса. Также можно указать параметры в скобках |
@POST() | POST-запрос для базового адреса. Также можно указать параметры в скобках |
@Path | Переменная для замещения конечной точки, например, username подставится в в адресе конечной точки |
@Query | Задаёт имя ключа запроса со значением параметра |
@Body | Используется в POST-вызовах (из Java-объекта в JSON-строку) |
@Header | Задаёт заголовок со значением параметра |
@Headers | Задаёт все заголовки вместе |
@Multipart | Используется при загрузке файлов или изображений |
@FormUrlEncoded | Используется при использовании пары «имя/значение» в POST-запросах |
@FieldMap | Используется при использовании пары «имя/значение» в POST-запросах |
@Url | Для поддержки динамических адресов |
@Query
Аннотация @Query полезна при запросах с параметрами. Допустим, у сайте есть дополнительный параметр к запросу, который выводит список элементов в отсортированном виде: http://example.com/api/v1/products/cats?sort=desc. Это несложный пример и мы можем поместить запрос с параметром в интерфейс без изменений.
Если не требуется управлять сортировкой, то её можно оставить в коде и она будет применяться по умолчанию. Но в нашем запросе есть ещё один параметр, который отвечает за категорию котов (домашние, уличные, породистые), которая может меняться в зависимости от логики приложения. Этот параметр можно снабдить аннотацией и программно управлять в коде.
Сортировку мы оставляем как есть, а категорию перенесли в параметры метода под именем categoryId, снабдив аннотацией, с которой параметр будет обращаться на сервер в составе запроса.
Запрос получится в виде http://example.com/api/v1/products/cats?sort=desc&category=5.
В одном методе можно указать несколько Query-параметров.
Запрос может иметь изменяемые части пути. Посмотрите на один из примеров запроса для GitHub: /users/:username. Вместо :username следует подставлять конкретные имена пользователей (https://api.github.com/users/alexanderklimov). В таких случаях используют фигурные скобки в запросе, в самоме методе через аннотацию @Path указывается имя, которое будет подставляться в путь.
@Headers
Пример аннотации @Headers, которая позволяет указать все заголовки вместе.
@Multipart
Пример аннотации @Multipart при загрузке файлов или картинок:
@FormUrlEncoded
Пример использования аннотации @FormUrlEncoded:
Пример аннотации @Url:
Задача третья. Retrofit
Для синхронного запроса используйте метод Call.execute(), для асинхронного — метод Call.enqueue().
Объект для запроса к серверу создаётся в простейшем случае следующим образом
В итоге мы получили объект Retrofit, содержащий базовый URL и способность преобразовывать JSON-данные с помощью указанного конвертера Gson.
Далее в его методе create() указываем наш класс интерфейса с запросами к сайту.
После этого мы получаем объект Call и вызываем метод enqueue() (для асинхронного вызова) и создаём для него Callback. Запрос будет выполнен в отдельном потоке, а результат придет в Callback в main-потоке.
В результате библиотека Retrofit сделает запрос, получит ответ и производёт разбор ответа, раскладывая по полочкам данные. Вам остаётся только вызывать нужные методы класса-модели для извлечения данных.
Основная часть работы происходит в onResponse(), ошибки выводятся в onFailure() (неправильный адрес сервера, некорректные формат данных, неправильный формат класса-модели и т.п). HTTP-коды сервера (например, 404) не относятся к ошибкам.
Метод onResponse() вызывается всегда, даже если запрос был неуспешным. Класс Response имеет удобный метод isSuccessful() для успешной обработки запроса (коды 200хх). В ошибочных ситуациях вы можете обработать ошибку в методе errorBody() класса ResponseBody.
Другие полезные методы Response.
- code() — HTTP-код ответа
- body() — сам ответ в виде строки, без сериализации
- headers() — HTTP-заголовки
- message() — HTTP-статус (или null)
- raw() — сырой HTTP-ответ
Можно написать такую конструкцию.
Для отмены запроса используется метод Call.cancel().
Перехватчики (Interceptors)
В библиотеку можно внедрить перехватчики для изменения заголовков при помощи класса Interceptor из OkHttp. Сначала следует создать объект перехватчика и передать его в OkHttp, который в свою очередь следует явно подключить в Retrofit.Builder через метод client().
Поддержка перехватчиков/interceptors для обработки заголовков запросов, например, для работы с токенами авторизации в заголовке Authorization.
HttpLoggingInterceptor
Библиотека HttpLoggingInterceptor является частью OkHttp, но поставляется отдельно от неё. Перехватчик следует использовать в том случае, когда вам действительно нужно изучать логи ответов сервера. По сути библиотека является сетевым аналогом привычного LogCat.
Подключаем перехватчик к веб-клиенту. Добавляйте его после других перехватчиков, чтобы ловить все сообщения. Существует несколько уровней перехвата данных: NONE, BASIC, HEADERS, BODY. Последний вариант самый информативный, пользуйтесь им осторожно. При больших потоках данных информация забьёт весь экран. Используйте промежуточные варианты.
RxJava
Сами разработчики библиотеки очень любят реактивное программирование и приложили многие усилия для интеграции с библиотекой RxJava.
Источник
Retrofit. Query, Path, RxJava, логи
Продолжаем говорить про Retrofit. Посмотрим, что и как мы можем настроить в нем, чтобы достичь своих целей.
Основы работы с Retrofit вы можете посмотреть в прошлом посте. Я же вкратце напомню, что две основных части его конфигурирования — это интерфейс и билдер. В интерфейсе мы описываем, какие методы мы будем вызывать, а в билдере задаем базовый URL и можем добавлять различные дополнения и обработчики для запросов и ответов.
Сначала рассмотрим пару полезных трюков, которые мы можем провернуть в интерфейсе.
Разделение ссылки
Предположим, у нас есть API метод:
http://server/api/v1/products/list.
Он возвращает список товаров. Нам нужно разделить эту ссылку на базовый URL (для билдера) и имя метода (для интерфейса). Это можно сделать так:
http://server/api/v1/products/ — базовый URL
list — метод
Соответственно, интерфейс будет выглядеть так:
Но внезапно у нас в API добавляется еще один метод, который возвращает список заказов:
http://server/api/v1/orders/list
Если его разделить по той же схеме, то получится так:
http://server/api/v1/orders/ — базовый URL
list — метод
В интерфейс мы можем добавить метод orderList:
Но у нас в билдере использовался базовый 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 пойдут в интерфейс.
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
В интерфейсе это можно сделать так:
И это даже будет работать, но понятно, что решение совсем топорное. Если сортировку еще можно так оставить, то категорию уж точно хотелось бы вынести в параметры. Используем для этого аннотацию Query:
Сортировку мы так и оставили там же, где указываем имя API метода. А категорию мы вынесли в параметры метода productList и добавили к ней аннотацию Query. В аннотации мы указали под каким именем этот параметр должен появится в URL запроса.
Вызов этого метода в коде будет выглядеть так:
И в результате будет выполнен следующий запрос
http://server/api/v1/products/list?sort=desc&category=100
В одном методе можно указывать несколько Query параметров.
Иногда API может быть такого вида
Т.е. здесь id товара передается не параметром, а частью пути. В этом случае нам поможет аннотация Path.
Используем ее в методах интерфейса:
В строку с именем метода добавляем плэйсхолдер
При вызове, Retrofit возьмет значение productId и подставит его в строку запроса вместо
В одном методе можно указывать несколько Path параметров.
Логирование
Чтобы видеть, какие запросы уходят и какие ответы на них приходят, мы можем добавить к Retrofit логирование.
Для этого надо в build.gradle в секцию dependecies добавить зависимость:
Код конфигурации билдера теперь будет выглядеть так:
Сначала создаем HttpLoggingInterceptor. В нем настраиваем уровень логирования. Если у нас Debug билд, то выставляем максимальный уровень (BODY), иначе — ничего не логируем, чтобы не палить в логах релизные запросы.
HttpLoggingInterceptor мы не можем напрямую передать в Retrofit билдер. Поэтому сначала создаем OkHttpClient, ему передаем HttpLoggingInterceptor, и уже этот OkHttpClient используем в Retrofit билдере.
В результате, в логе по тегу OkHttp вы увидите все ваши запросы со всеми параметрами, заголовками и содержимым.
RxJava
Чтобы использовать в проекте RxJava, необходимо добавить зависимости:
А для Retrofit нужен RxJava-адаптер:
Вместо обертки Call мы используем Single из RxJava. Он вернет нам либо результат (onNext), либо ошибку (onError). А метод завершения (onCompleted) он вызывать не будет.
В билдере нам необходимо добавить RxJava2CallAdapterFactory:
Далее, создаем реализацию MessageApi и вызываем метод messages.
Метод messages вернет нам Single
>, на который мы подписываемся и настраиваем, чтобы запрос был выполнен в IO потоке, а результат вернулся в UI потоке.,
В случае использования RxJava, у нас уже нет объекта Response. И если сервер вернет какую-то ошибку, то мы получим ее в onError. Например, ошибка 404 будет выглядеть так:
onError retrofit2.adapter.rxjava2.HttpException: HTTP 404
Как получить чистый текст
Бывает необходимость получить от сервера данные в виде текста (plain text). Т.е. вам не надо, чтобы Retrofit распарсил текстовые данные в объекты. По каким-то причинам вы хотите получить String.
В интерфейсе указываем тип String
Получить строку нам поможет конвертер ScalarsConverterFactory. Чтобы использовать его, пропишите зависимость:
Используем ScalarsConverterFactory в билдере
Далее, все как обычно, получаем Call объект и асинхронно выполняем запрос:
Ответ придет в response.body()
В логах это будет выглядеть так:
Мы получили нераспарсенные json данные в виде текста.
Как сделать синхронный вызов
Если вы собираетесь выполнять запрос не в UI потоке, то его можно выполнить синхронно без всяких колбэков.
Это будет выглядеть так:
Этот код блокирует текущий поток. Ответ от сервера вы получите в Response объект. А если случится ошибка, то она придет в catch
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник