Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Retrofit

Многие сайты имеют собственные API для удобного доступа к своим данным. На данный момент самый распространённый вариант - это JSON. Также могут встречаться данные в виде XML и других форматов.

Библиотека Retrofit упрощает взаимодействие с REST API сайта, беря на себя часть рутинной работы.

Авторами библиотеки Retrofit являются разработчики из компании "Square", которые написали множество полезных библиотек, например, Picasso, Okhttp, Otto.

Домашняя страница - http://square.github.io/retrofit/

Библиотекой удобно пользоваться для запроса к различным веб-сервисам с командами GET, POST, PUT, DELETE. Может работать в асинхронном режиме, что избавляет от лишнего кода.

В основном вам придётся работать с методами GET и POST. Если вы будет создавать собственный API, то будете использовать и другие команды.

Подключается стандартно.


implementation 'com.squareup.retrofit2:retrofit:2.9.0'

В Retrofit 2.x автоматически подключается библиотека OkHttp и её не нужно прописывать отдельно.

Библиотека может работать с GSON и XML, используя специальные конвертеры, которые следует указать отдельно.


implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

Затем в коде конвертер добавляется с помощью метода addConverterFactory().


addConverterFactory(GsonConverterFactory.create()

Список готовых конвертеров:

  • 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.

Можно подключить несколько конвертеров (порядок важен).


Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/v2/");
    .addConverterFactory(ProtoConverterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .build();

Если вы хотите изменить формат какого-нибудь JSON-объекта, то это можно сделать с помощью GsonConverterFactory.create():


Gson gson = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
        .create();
 
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.example.com/base/")
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();
 
service = retrofit.create(APIService.class);

Базовый URL всегда заканчивается слешем /. Задаётся в методе baseUrl().

Можно указать полный URL в запросе, тогда базовый URL будет проигнорирован:


public interface APIService {
    @GET("https://api.sample.com/users/user/list")
    Call<Users> getUsers();
}

Для работы с Retrofit понадобятся три класса.

  1. POJO (Plain Old Java Object) или Model Class - json-ответ от сервера нужно реализовать как модель
  2. Retrofit - класс для обработки результатов. Ему нужно указать базовый адрес в методе baseUrl()
  3. Interface - интерфейс для управления адресом, используя команды GET, POST и т.д.

Работу с Retrofit можно разбить на отдельные задачи.

Задача первая. POJO

Задача первая - посмотреть на структуру ответа сайта в виде JSON (или других форматов) и создать на его основе Java-класс в виде POJO.

POJO удобнее создавать с помощью готовых веб-сервисов в автоматическом режиме. Либо можете самостоятельно создать класс, если структура не слишком сложная.

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

Задача вторая. Интерфейс

Задача вторая - создать интерфейс и указать имя метода. Добавить необходимые параметры, если они требуются.

В интерфейсе задаются команды-запросы для сервера. Команда комбинируется с базовым адресом сайта (baseUrl()) и получается полный путь к странице. Код может быть простым и сложным. Можно посмотреть примеры в документации.

Запросы размещаются в обобщённом классе Call с указанием желаемого типа.


import retrofit2.Call;
 
public interface APIService {
    @POST("list")
    Call<Repo> loadRepo();
}

В большинстве случаев вы будете возвращать объект Call<T> с нужным типом, например, Call<User>. Если вас не интересует тип ответа, то можете указать Call<Response>.

Здесь также используются аннотации, но уже от самой библиотеки.

С помощью аннотации указываются веб-команды, а затем Java-метод. Для динамических параметров используются фигурные скобки (users/{user}/repos), в которые подставляются нужные значения.

В самой аннотации используется метод, используемый на сервере, а ниже вы можете указать свой вариант (полезно для соответствия стилю вашего кода.


@GET("get_all_cats")  // команда на сервере
List<Cat> getAllCats();  // ваш код

Аннотации

АннотацияОписание
@GET()GET-запрос для базового адреса. Также можно указать параметры в скобках
@POST()POST-запрос для базового адреса. Также можно указать параметры в скобках
@PathПеременная для замещения конечной точки, например, username подставится в {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. Это несложный пример и мы можем поместить запрос с параметром в интерфейс без изменений.


@GET("products/cats?category=5&sort=desc")
Call<Cats> getAllCats(); 

Если не требуется управлять сортировкой, то её можно оставить в коде и она будет применяться по умолчанию. Но в нашем запросе есть ещё один параметр, который отвечает за категорию котов (домашние, уличные, породистые), которая может меняться в зависимости от логики приложения. Этот параметр можно снабдить аннотацией и программно управлять в коде.


@GET("products/cats?sort=desc")
Call<Cats> getAllCats(@Query("category") int categoryId);

Сортировку мы оставляем как есть, а категорию перенесли в параметры метода под именем categoryId, снабдив аннотацией, с которой параметр будет обращаться на сервер в составе запроса.


Call<Cats> getAllCats() = catAPIService.getAllCats(5);

Запрос получится в виде http://example.com/api/v1/products/cats?sort=desc&category=5.

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

@Path

Запрос может иметь изменяемые части пути. Посмотрите на один из примеров запроса для GitHub: /users/:username. Вместо :username следует подставлять конкретные имена пользователей (https://api.github.com/users/alexanderklimov). В таких случаях используют фигурные скобки в запросе, в самоме методе через аннотацию @Path указывается имя, которое будет подставляться в путь.


@GET("/users/{username}")
Call getUser(
    @Path("username") String userName
);

@Headers

Пример аннотации @Headers, которая позволяет указать все заголовки вместе.


@Headers({"Cache-Control: max-age=640000", "User-Agent: My-App-Name"})
@GET("some/endpoint")

@Multipart

Пример аннотации @Multipart при загрузке файлов или картинок:


@Multipart
@POST("some/endpoint")
Call<Response> uploadImage(@Part("description") String description, @Part("image") RequestBody image)

@FormUrlEncoded

Пример использования аннотации @FormUrlEncoded:


@FormUrlEncoded
@POST("/some/endpoint")
Call<SomeResponse> someEndpoint(@FieldMap Map<String, String> names);

@Url

Пример аннотации @Url:


public interface UserService {  
    @GET
    public Call<File> getZipFile(@Url String url);
}

Задача третья. Retrofit

Для синхронного запроса используйте метод Call.execute(), для асинхронного - метод Call.enqueue().

Объект для запроса к серверу создаётся в простейшем случае следующим образом


public static final String BASE_URL = "http://api.example.com/";

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

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

Далее в его методе create() указываем наш класс интерфейса с запросами к сайту.


UserService userService = retrofit.create(UserService.class);

После этого мы получаем объект 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-ответ

Можно написать такую конструкцию.


// код 200
if (response.isSuccessful()) {
    ... // код для успешного случая
} else {

    switch(response.code()) {
        case 404:
            // страница не найдена. можно использовать ResponseBody, см. ниже
            break;
        case 500:
            // ошибка на сервере. можно использовать ResponseBody, см. ниже
            break;
    }

    // или
    // Также можете использовать ResponseBody для получения текста ошибки
    ResponseBody errorBody = response.errorBody();
    try {
        mTextView.setText(errorBody.string());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Для отмены запроса используется метод Call.cancel().

Перехватчики (Interceptors)

В библиотеку можно внедрить перехватчики для изменения заголовков при помощи класса Interceptor из OkHttp. Сначала следует создать объект перехватчика и передать его в OkHttp, который в свою очередь следует явно подключить в Retrofit.Builder через метод client().

Поддержка перехватчиков/interceptors для обработки заголовков запросов, например, для работы с токенами авторизации в заголовке Authorization.


OkHttpClient client = new OkHttpClient();  
client.interceptors().add(new Interceptor() {  
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();

        // Настраиваем запросы
        Request request = original.newBuilder()
                .header("Accept", "application/json")
                .header("Authorization", "auth-token")
                .method(original.method(), original.body())
                .build();

        Response response = chain.proceed(request);

        return response;
    }
});

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/v2/");
    .client(client)
    .build();

HttpLoggingInterceptor

Библиотека HttpLoggingInterceptor является частью OkHttp, но поставляется отдельно от неё. Перехватчик следует использовать в том случае, когда вам действительно нужно изучать логи ответов сервера. По сути библиотека является сетевым аналогом привычного LogCat.

Подключаем библиотеку.


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

Подключаем перехватчик к веб-клиенту. Добавляйте его после других перехватчиков, чтобы ловить все сообщения. Существует несколько уровней перехвата данных: NONE, BASIC, HEADERS, BODY. Последний вариант самый информативный, пользуйтесь им осторожно. При больших потоках данных информация забьёт весь экран. Используйте промежуточные варианты.


HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();

// Только в режиме отладки
if(BuildConfig.DEBUG){
    loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY );
}

OkHttpClient okClient = new OkHttpClient.Builder()
        .addInterceptor(new ResponseInterceptor())
        .addInterceptor(loggingInterceptor)
        .build();

RxJava

Сами разработчики библиотеки очень любят реактивное программирование и приложили многие усилия для интеграции с библиотекой RxJava.


// build.gradle
implementation 'com.squareup.retrofit2:adapter-rxjava:2.5.0'

Retrofit retrofit = new Retrofit.Builder()
   .baseUrl(baseUrl);
   .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
   .addConverterFactory(GsonConverterFactory.create())
   .build();

Дополнительное чтение

Retrofit 2.x. Примеры для GitHub

Retrofit 2.x. Другие примеры

Retrofit 2.x. POST, PUT, DELETE

Retrofit 2.x. Конвертер XML

Retrofit 2.x. Конвертер ScalarsConverterFactory и метод POST

Курс валют Центрального Банка России. Retrofit, XML

Retrofit на Kotlin с применением корутины. JSONPlaceholder

Retrofit, Reddit (Kotlin)

TheCatAPI - Cats as a Service, Everyday is Caturday (Retrofit, Kotlin)

Реклама