Используем Retrofit 2 в Android-приложении


Retrofit — это известная среди Android-разработчиков библиотека для сетевого взаимодействия, некоторые даже считают её в каком-то роде стандартом. Причин для такой популярности масса: библиотека отлично поддерживает REST API, легко тестируется и настраивается, а запросы по сети с её помощью выполняются совсем просто. В этой статье я покажу вам, как настроить и использовать Retrofit, чтобы реализовать работу с сетью в вашем приложении.


Настройка Retrofit


Добавьте следующую зависимость в файл build.gradle:


compile 'com.squareup.retrofit2:retrofit:2.4.0'

Мы будем использовать Gson для преобразования JSON в POJO. Retrofit предоставляет зависимость, которая автоматически конвертирует JSON в POJO. Для этого добавьте ещё одну зависимость в файл build.gradle:


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

Если в вашем приложении ещё не разрешена работа с сетью, то обязательно добавьте соответствующуу строку в файл AndroidManifest:


<uses-permission android:name="android.permission.INTERNET"/>

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


Создайте класс с именем NetworkService:


public class NetworkService {

}

Этот класс должен быть singleton-объектом, поэтому объявите статическую переменную и функцию, которая создаёт и возвращает переменную того же типа, что и класс. Если вы не знаете, как работает данный паттерн, то ознакомьтесь с этой статьёй, в которой есть примеры реализации на языке Java.


public class NetworkService {
    private static NetworkService mInstance;

    public static NetworkService getInstance() {
        if (mInstance == null) {
            mInstance = new NetworkService();
        }
        return mInstance;
    }
}

Для тестирования мы используем JSONPlaceholder, который предоставляет фейковый онлайн REST API для разработчиков:


https://jsonplaceholder.typicode.com

Теперь мы объявим и инициализируем Retrofit в конструкторе NetworkService:


public class NetworkService {
    private static NetworkService mInstance;
    private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
    private Retrofit mRetrofit;

    private NetworkService() {
        mRetrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    public static NetworkService getInstance() {
        if (mInstance == null) {
            mInstance = new NetworkService();
        }
        return mInstance;
    }
}

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


Добавление конечных точек


Создайте интерфейс с именем JSONPlaceHolderApi:


public interface JSONPlaceHolderApi {

}

На сайте JSONPlaceHolder URL /posts/id — это конечная точка, которая возвращает сообщение с соответствующим идентификатором. Данная конечная точка принимает GET-запрос и возвращает данные в формате JSON в следующем виде:


{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

Сначала мы создадим соответствующий POJO для JSON-ответа:


public class Post {
    @SerializedName("userId")
    @Expose
    private int userId;
    @SerializedName("id")
    @Expose
    private int id;
    @SerializedName("title")
    @Expose
    private String title;
    @SerializedName("body")
    @Expose
    private String body;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

Как вы видите, это простой POJO-класс. Переменные мы аннотировали с помощью @SerializedName(), передав туда имя. Эти имена фактически являются ключами в возвращаемых из API JSON-данных, поэтому вы можете как угодно изменять имя переменной, но убедитесь, что имя, переданное в аннотацию @SerializedName(), точно присутствует в JSON.


В интерфейсе, созданном выше, определите конечные точки с требуемыми параметрами:


public interface JSONPlaceHolderApi {
    @GET("/posts/{id}")
    public Call<Post> getPostWithID(@Path("id") int id);
}

Поскольку мы отправляем GET-запрос, нам нужно применить к методу аннотацию @GET, внутри которой находится конечная точка, на которую мы хотим отправить запрос. Как вы видите, мы не добавляем полный URL, т.к. Retrofit автоматически возьмёт BASE_URL, переданный в класс NetworkService, и добавит его к остальной части URL-адреса.


Возвращаемый тип метода называется Call<Post>. Call — это класс, предоставляемый непосредственно самой библиотекой. И все методы в интерфейсе должны возвращать значения этого типа. Это generic-класс, принимающий в себя тип объекта, который мы хотим конвертировать в JSON. Мы передали Post, т.к. это именно тот объект, в который хотим преобразовать JSON-ответ. В параметры мы передали целое число и аннотировали его с помощью @Path, внутри которого записали id. Retrofit возьмёт это значение и в конечной точке заменит на него {id}. Таким образом, если мы передадим значение 1 в качестве параметра, то конечная точка будет выглядеть так — /posts/1, если передадим значение 10, то конечная точка получится — /posts/10.


Теперь нам нужно, чтобы Retrofit предоставил реализацию интерфейса JSONPlaceHolderApi. Для этого используем метод create():


public class NetworkService {
    private static NetworkService mInstance;
    private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
    private Retrofit mRetrofit;

    private NetworkService() {
        mRetrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    public static NetworkService getInstance() {
        if (mInstance == null) {
            mInstance = new NetworkService();
        }
        return mInstance;
    }

    public JSONPlaceHolderApi getJSONApi() {
        return mRetrofit.create(JSONPlaceHolderApi.class);
    }
}

Далее нужно получить JSONPlaceHolderApi из NetworkService и отправить запрос:


NetworkService.getInstance()
                .getJSONApi()
                .getPostWithID(1)
                .enqueue(new Callback<Post>() {
                    @Override
                    public void onResponse(@NonNull Call<Post> call, @NonNull Response<Post> response) {
                        Post post = response.body();

                        textView.append(post.getId() + "\n");
                        textView.append(post.getUserId() + "\n");
                        textView.append(post.getTitle() + "\n");
                        textView.append(post.getBody() + "\n");
                    }

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

                        textView.append("Error occurred while getting request!");
                        t.printStackTrace();
                    }
                });

Возвращённый объект Call содержит метод с именем enqueue, который принимает в качестве параметра Callback<T>. В onResponse мы получаем результат Response<Post>, содержащий возвращённый с сервера объект Post. Чтобы получаем сам объект Post, используем метод response.body(). Остальная часть кода понятна без дополнительных пояснений.


Отправка различных типов запросов


В API JSONPlaceHolder есть много разных конечных точек, которые можно использовать.


Получение списка сообщений


@GET("/posts")
public Call<List<Post>> getAllPosts();

Для получения списка всех сообщений мы изменили конечную точку и возвращаемый тип функции.


Отправка запроса с параметром


Если вы хотите отправить запрос с параметром, то нужно всего лишь использовать аннотацию @Query() для соответствующего параметра в методе:


@GET("/posts")
public Call<List<Post>> getPostOfUser(@Query("userId") int id);

Поэтому если мы передадим значение 6 в параметре метода, то конечная точка будет следующей — /posts?userId=6.


Отправка POST запроса


Для отправки POST-запроса нужно просто изменить аннотацию метода.


@POST("/posts")
public Call<Post> postData(@Body Post data);

Чтобы сформировать тело запроса для данного метода, мы используем аннотацию @Body для передаваемого параметра. Retrofit будет использовать Gson для конвертации @Body в JSON.


Существует ещё несколько различных типов запросов, которые вы можете использовать, но это уже тема для отдельной статьи.


Перехват запросов


Retrofit предоставляет способ перехвата запросов и их логирования в Logcat. Давайте настроим перехватчик и посмотрим на эту магию. Добавьте следующую зависимость в файл build.gradle:


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

Обновите класс NetworkService таким образом:


public class NetworkService {
    private static NetworkService mInstance;
    private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
    private Retrofit mRetrofit;

    private NetworkService() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

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

        mRetrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(client.build())
                .build();
    }

    public static NetworkService getInstance() {
        if (mInstance == null) {
            mInstance = new NetworkService();
        }
        return mInstance;
    }

    public JSONPlaceHolderApi getJSONApi() {
        return mRetrofit.create(JSONPlaceHolderApi.class);
    }
}

Теперь, когда вы отправляете или получаете запрос, все его данные, включая URL, заголовки, тело, будут выведены в лог:


D/OkHttp: <-- 200  https://jsonplaceholder.typicode.com/posts/1 (3030ms)
    date: Tue, 24 Apr 2018 15:25:19 GMT
    content-type: application/json; charset=utf-8
    set-cookie: __cfduid=d16d4221ddfba20b5464e6829eed4e3d11524583519; expires=Wed, 24-Apr-19 15:25:19 GMT; path=/; domain=.typicode.com; HttpOnly
    x-powered-by: Express
    vary: Origin, Accept-Encoding
    access-control-allow-credentials: true
    cache-control: public, max-age=14400
    pragma: no-cache
    expires: Tue, 24 Apr 2018 19:25:19 GMT
04-24 15:25:16.204 7023-7056/com.thetehnocafe.gurleensethi.retrofitexample D/OkHttp: x-content-type-options: nosniff
    etag: W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU"
    via: 1.1 vegur
    cf-cache-status: HIT
    expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    server: cloudflare
    cf-ray: 410994f328963066-SIN
04-24 15:25:16.246 7023-7056/com.thetehnocafe.gurleensethi.retrofitexample D/OkHttp: {
04-24 15:25:16.247 7023-7056/com.thetehnocafe.gurleensethi.retrofitexample D/OkHttp:   "userId": 1,
      "id": 1,
      "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
      "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
    }
    <-- END HTTP (292-byte body)

На этом наша статья заканчивается. Код для этого проекта вы можете найти на GitHub.

Комментарии (2)


  1. agent10
    15.11.2018 18:02
    +1

    Ну сколько можно ещё? Ждём перевод какой-нибудь статьи от индуса как создать новый Андроид проект в студии.


  1. shok96
    16.11.2018 10:18

    Мне кажется, для новичков будет полезно узнать как развернуть сетевую часть в своем приложение.