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

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

Настройка Retrofit

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

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

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

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

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

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

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

Создайте класс-объект с именем NetworkService (он будет синглтоном):

object NetworkService {
}

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

https://jsonplaceholder.typicode.com

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

object NetworkService { 
    private const val BASE_URL = "https://jsonplaceholder.typicode.com"
    private retrofit: Retrofit

    init { 
        retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

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

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

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

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"
}

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

data class Post(
    var userId: Int,
    var id: Int,
    var title: String,
    var body: String
)

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

interface JSONPlaceHolderApi { 
    @GET("/posts/{id}")
    fun getPostWithID(@Path("id") id: Int): Call<Post> 
}

Поскольку мы отправляем 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():

object NetworkService { 
    private const val BASE_URL = "https://jsonplaceholder.typicode.com"
    private retrofit: Retrofit

    init { 
        retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    fun getJSONApi(): JSONPlaceHolderApi { 
        return mRetrofit.create(JSONPlaceHolderApi.class)
    } 
}

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

NetworkService.getJSONApi()
    .getPostWithID(1)
    .enqueue(object : Callback<Post>() {
        override fun onResponse(call: Call<Post>, response: Response<Post>) { 
            val post = response.body()
            textView.append(post.getId() + "\n")
            textView.append(post.getUserId() + "\n")
            textView.append(post.getTitle() + "\n")
            textView.append(post.getBody() + "\n")
        }

        override fun onFailure(call: Call<Post>, t: Throwable) {
            textView.append("Error occurred while getting request!")
            t.printStackTrace()
        }
    })

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

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

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

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

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

@GET("/posts")
fun getAllPosts(): Call<List<Post>>

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

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

@GET("/posts")
fun getPostOfUser(@Query("userId") id: Int): Call<List<Post>>

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

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

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

@POST("/posts")
fun postData(@Body data: Post): Call<Post>

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

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

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

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

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

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

object NetworkService { 
    private const val BASE_URL = "https://jsonplaceholder.typicode.com"
    private retrofit: Retrofit

    init {
        val interceptor = HttpLoggingInterceptor()
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)

        val client = OkHttpClient.Builder()
            .addInterceptor(interceptor)

        val retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client.build())
            .build()
    }

    fun getJSONApi(): JSONPlaceHolderApi { 
        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: {
        "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.

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