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

 

Предположим, что на каком-то сервере есть данные. И сервер готов нам выдать эти данные. Чаще всего для этого используется формат json. Вот пример таких данных: https://rawgit.com/startandroid/data/master/messages/messages1.json

Давайте используем Retrofit, чтобы получить в приложении данные из этого файла.  

 

Для начала немного теории. 

Конфигурирование Retrofit можно разделить на две части: API интерфейс и билдер. Давайте рассмотрим подробно, кто из них за что отвечает.

Представим себе работу по обмену данными приложения с сервером. Как это происходит? Обычно сервер предоставляет нам какое-то API, т.е. набор методов (он же REST). Если, например, это сервер интернет-магазина, то его API может содержать следующие методы:
getProducts - получить список товаров
getProdut - получить детальные данные о продукте
getOrders - получить список заказов
getOrder - получить детальные данные о заказе
createOrder - создать заказ
и т.д.

Это серверные методы и, чтобы их вызывать, нам необходимо выполнять запросы, которые могут выглядеть, например, так:
http://server/api/v1/getProducts
http://server/api/v1/getProduct
http://server/api/v1/getOrders
http://server/api/v1/getOrder
http://server/api/v1/createOrder
и т.д.

Т.е. у сервера есть какой-то базовый URL - http://server/api/v1/. И к нему просто добавляются имена методов (getProducts, getOrders, и т.д.), чтобы получать ссылки для работы с API методами.

 

Возвращаемся в приложение. Было бы очень удобно, если бы у нас в приложении был класс ServerApi с методами getProducts, getOrders и т.д. И при вызове этих методов происходил бы вызов соответствующих серверных методов. И Retrofit как раз может создать для нас такой класс. От нас потребуется интерфейс, в котором мы распишем необходимые методы.

Вот пример такого интерфейса:

public interface ServerApi {

   @GET("getProducts")
   List<Product> getProducts();

   @GET("getProduct")
   Product getProduct(long productId);

   @GET("getOrders")
   List<Order> getOrders();

   @GET("getOrder")
   Order getOrder(long orderId);

   @POST("createOrder")
   void createOrder(Order order);

}

Этот код немного упрощен. В реальном примере будут дополнительные аннотации и обертки. Но общий смысл он вполне передает. Retrofit создаст класс, который реализует этот интерфейс. Внутри этого класса и будут сгенерированы вызовы к серверу.

А мы в этом интерфейсе можем настроить названия методов и некоторые HTTP штуки, такие как передаваемые параметры (Query), заголовки (Header) и т.п.

 

Рассмотрим описание метода getProducts подробнее:

@GET("getProducts")
List<Product> getProducts();

Аннотация GET означает, что HTTP-запрос для getProducts должен быть типа GET. А в качестве параметра для этой аннотации нам необходимо указать имя метода на сервере. Обратите внимание, что здесь не фигурирует базовый URL (http://server/api/v1/). Его мы будем указывать позже, в билдере.

List<Product> - это тип возвращаемых данных. Retrofit сам сможет сконвертировать json данные в список объектов Product. Подробнее об этом тоже поговорим во время обсуждения билдера.

Ну и имя метода - getProducts(). В этом примере оно совпадает с именем метода на сервере. Но это необязательно. Т.е. вы вполне можете сделать так

@GET("getProducts")
List<Product> getProductList();

Теперь имена методов различаются. Вы в коде будете вызывать метод getProductList, а на сервере будет вызываться getProducts.

 

Переходим к практике. Чтобы использовать в вашем проекте Retrofit, в build.gradle файле, в секции dependencies добавляйте зависимости:

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

Первая строка - это непосредственно Retrofit, а вторая - это его Gson-конвертер.

 

Напомню, что у нас есть ссылка: https://rawgit.com/startandroid/data/master/messages/messages1.json.

Она вернет нам json данные, которые представляют из себя список сообщений. Каждое сообщение содержит поля id, time, text и иногда image. Эта ссылка, конечно, не API, а просто текстовый файл, но смысл остается тем же, просто вместо имени метода у нас будет имя файла.

Мы делим ссылку на две части:
"https://rawgit.com/startandroid/data/master/messages/" - базовый URL (будет указан в билдере)
"messages1.json" - имя файла (будет указано в интерфейсе, в GET аннотации)

Мы можем создать интерфейс для работы с этой ссылкой. Там будет только один метод, и он будет возвращать List<Message>.

 

Давайте сначала создадим класс Message.

public class Message {

   private long id;
   private long time;
   private String text;
   private String image;

   // getters and setters
   ...
  
}

Его поля соответствуют полям в данных в messages1.json.

 

Теперь создаем интерфейс:

import java.util.List;

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

public interface MessagesApi {

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

}

В аннотации GET указываем, что к базовому URL надо будет добавить "messages1.json", чтобы получилась нужная нам ссылка.

Обратите внимание на Call. Эта обертка нужна для работы Retrofit. В ней мы указываем, какой тип данных ожидаем получить из messages1.json - т.е. List<Message>.

 

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

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

В билдере мы указываем базовый URL и добавляем Gson конвертер, чтобы Retrofit сам смог сконвертировать json данные в объекты Message. Это минимум настроек, который от нас требуется для нашего простого примера. Для сложных случаев количество параметров может быть больше.

 

В итоге у нас есть объект Retrofit, который содержит базовый URL и способность преобразовывать json данные с помощью Gson. Мы передаем ему в метод create класс интерфейса, в котором описывали методы.

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

И получаем реализацию MessagesApi от Retrofit. В этой реализации соединены настройки билдера (базовый URL и Gson конвертер) и описания методов из интерфейса (метод messages для получения файла messages1.json). В итоге Retrofit будет брать базовый URL из билдера, присоединять к нему остаток пути, который мы указываем в GET в интерфейсе, и тем самым получит полную ссылку.

 

Возможно, возникает вопрос, зачем было разделять функционал на билдер и интерфейс. Я могу привести вам пару примеров из своей практики.

1) У интернет-магазина есть два сервера: рабочий (с реальными данными) и тестовый. На обоих развернуто одинаковое API. И есть приложение, которое общается с сервером. Debug-версия приложения должна работать с тестовым сервером и выводить в лог всю информацию об этом. Release-версия работает с рабочим сервером и ничего не выводит в лог.

Эту разницу между Debug и Release версиями можно реализовать двумя разными билдерами. Просто указываем для них разные базовые URL и разные настройки логирования. А интерфейс в обоих случаях будет использоваться один и тот же.

2) Есть сервер с API. И есть приложение, которое вызывает эти методы. На сервере реализована авторизация. Т.е. приложение должно вызвать метод авторизации, получить токен, и затем использовать этот токен при вызове некоторых API методов, которые связаны с пользователем.

В билдере есть возможность использовать специальный обработчик, который будет добавлять токен ко всем вызовам. Но нам надо добавлять токен не ко всем, а только к некоторым вызовам.

В этом случае можно создать два билдера. Первый не будет использовать токен. Реализацию, полученную от этого билдера используем для работы с методами, не требующими токена. А второй билдер будет добавлять токен ко всем вызовам. Реализацию, полученную от этого билдера используем для всех методов, где нужен токен.

 

Итак, связка интерфейса и билдера дала нам реализацию MessagesApi. Используем ее для получения данных. Вызываем метод messages()

Call<List<Message>> messages = messagesApi.messages();

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

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

   }
});

Метод log - это просто вывод в лог текста

Вызов метода messages вернет нам Call объект. При выполнении этого кода запрос к серверу еще не был отправлен. Call дает нам возможность отправить запрос синхронно (блокируя текущий поток) или асинхронно (с колбэком). Мы выбираем асинхронный вариант: вызываем метод enqueue и создаем для него Callback. Запрос будет выполнен в отдельном потоке, а результат придет в Callback в main потоке.

Если взаимодействие с сервером пройдет успешно, то мы получим результат в метод onResponse в объекте Response. Чтобы добраться до данных, необходимо вызвать метод body() у Response объекта. Это даст нам список сообщений - List<Message>. Выводим в лог количество записей в этом списке.

 

Запустив этот код, мы получим список из 50 записей. Лог покажет следующее:

response 50

 

Если по каким-то причинам не удалось достучаться до сервера, то вместо onResponse, будет вызван метод onFailure. Добавим вывод в лог ошибки, которую вернет нам Retrofit

@Override
public void onFailure(Call<List<Message>> call, Throwable t) {
   log("failure " + t);
}

 

Чтобы эмулировать ошибку, мы можем просто поломать имя сервера в базовом URL, который задается в билдере. Например, добавим букву r к rawgit:

https://rrawgit.com/startandroid/data/master/messages/

Запускаем и видим в логе ошибку.

failure java.net.UnknownHostException: Unable to resolve host "rrawgit.com": No address associated with hostname

Retrofit сообщает, что не нашел сервер rrawgit.com

 

Если случится какая-то ошибка в процессе работы Retrofit, то она попадет также в onFailure. Например, если сервер вернет некорректные данные, то GsonConverterFactory не сможет их распарсить и выкинет ошибку. А Retrofit отправит ее нам в onFailure.

 

Есть тип ошибок, который Retrofit умеет обрабатывать без вызова onFailure. Это HTTP-ошибки сервера. Самый простой случай - ошибка 404. Запрос дойдет до сервера, но сервер вместо данных вернет HTTP 404 Not found. Казалось бы - это ошибка и на должна прийти в onFailure. Но нет. onFailure вызывается в случае, когда не удалось достучаться до сервера. А здесь с сервером все ок, он просто вместо данных вернул нам информацию о том, что мы странные и хотим непонятного.

В итоге, ответ сервера мы получим в методе onResponse. И нам надо как-то различать, вернул сервер данные или ошибку. Для этого у объекта Response есть метод isSuccessful.

Перепишем метод onResponse

 

@Override
public void onResponse(Call<List<Message>> call, Response<List<Message>> response) {
   if (response.isSuccessful()) {
       log("response " + response.body().size());
   } else {
       log("response code " + response.code());
   }
}

Если запрос был выполнен успешно (isSuccessfull), то выводим в лог количество полученных записей. Иначе выводим в лог код ошибки.

 

Снова поменяем базовый URL, чтобы спровоцировать ошибку 404. Добавим лишнюю букву s в слово messages:

https://rawgit.com/startandroid/data/master/messagess/

Запускаем и в логе видим

response code 404

 

Чтобы получить полный текст ошибки сервера, используйте

response.errorBody().string()

 

Приведу весь код работы с Retrofit:

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

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

Call<List<Message>> messages = messagesApi.messages();

messages.enqueue(new Callback<List<Message>>() {
    @Override
    public void onResponse(Call<List<Message>> call, Response<List<Message>> response) {
        if (response.isSuccessful()) {
            log("response " + response.body().size());
        } else {
            log("response code " + response.code());
        }
    }

    @Override
    public void onFailure(Call<List<Message>> call, Throwable t) {
        log("failure " + t);
    }
});

Разумеется, вам не нужно при каждом запросе к серверу использовать билдер и создавать новый объект MessagesApi. Это делается один раз за время работы приложения и далее объект MessagesApi используется везде, где необходима работа с сервером. Для реализации Singleton подойдет Dagger или любая другая реализация этого паттерна.

 

На этом пока все, чтобы не перегружать вас информацией. Продолжение в следующем посте.


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

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

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

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

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

 

В канале я публикую ссылки на интересные и полезные статьи по Android

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



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



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

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal