Содержание
Приветствую вас на портале jandroid.ru.
– Сегодня мы познакомимся с библиотекой Retrofit.
– Напишем простенький пример приложения использования Retrofit в связке с Coroutines (корутинами).
– Создадим базу данных на сервере и напишем так называемый Backend – интерфейс считывающий информацию из базы данных и преобразующий ее в JSON по запросу клиента. Клиентом в нашем случае буде Android приложение.
– Напишем этот же код запроса с использованием библиотеки Volley. Я не буду тут рассказывать про эту библиотеку, она не лучше и не хуже, просто другой аналог. Вы сравните сами, что лучше и удобнее.
Статья будет большая, поэтому пользуйтесь содержанием.
Знакомство с Retrofit
Что такое Retrofit
Retrofit — это безопасный HTTP-клиент для Kotlin и Java. Retrofit упрощает подключение к веб-службе REST за счет преобразования API в интерфейсы Java. В этом руководстве я покажу вам, как использовать одну из самых популярных и часто рекомендуемых библиотек HTTP, доступных для Android.
Эта мощная библиотека упрощает использование данных JSON или XML, которые затем анализируются в обычные старые объекты Java или Kotlin (POJO). С Retrofit могут быть выполнены следующие запросы: GET
, POST
, PUT
, PATCH
DELETE
.
Как и большинство программ с открытым исходным кодом, Retrofit был создан на основе некоторых других мощных библиотек и инструментов. За кулисами Retrofit использует OkHttp (от того же разработчика) для обработки сетевых запросов. Кроме того, в Retrofit нет встроенного преобразователя JSON для синтаксического анализа объектов JSON в объекты Kotlin и Java. Вместо этого он поддерживает следующие библиотеки преобразователей JSON для обработки этого:
- Gson:
com.squareup.retrofit:converter-gson
- Jackson:
com.squareup.retrofit:converter-jackson
- Moshi:
com.squareup.retrofit:converter-moshi
Для Protocol Buffers (протокольных буферов) Retrofit поддерживает:
- Protobuf:
com.squareup.retrofit2:converter-protobuf
- Wire:
com.squareup.retrofit2:converter-wire
Для XML Retrofit поддерживает:
Simple Framework: com.squareup.retrofit2:converter-simpleframework
Зачем использовать Retrofit
Разработка собственной безопасной HTTP-библиотеки для взаимодействия с REST API может стать настоящей головной болью: вам придется выполнять множество функций, таких как установление соединений, кэширование, повторение неудачных запросов, многопоточность, анализ ответов, обработка ошибок и многое другое. Чтобы не тратить время на изобретение велосипеда можно использовать готовую библиотеку Retrofit. Retrofit очень хорошо спланирован, задокументирован и протестирован — проверенная библиотека, которая сэкономит вам много драгоценного времени и избавит от головной боли.
В этом руководстве я объясню, как использовать Retrofit 2 для обработки сетевых запросов, создав простое приложение для запроса ответов из API. Мы будем выполнять GET
запросы, добавляя параметры к базовому URL-адресу https://a.jandroid.ru/mysql_cities/ , а затем получим результаты и отобразим их в RecyclerView. Я также покажу вам, как это сделать с помощью Coroutines (корутин), чтобы упростить управление потоком состояний и данных.
Как использовать Retrofit
1. Добавим библиотеки
Первым делом следует добавить зависимости в build.gradle (Module).
dependencies { ... // Retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' }
2. Создадим data class
Теперь создадим data class отвечающий структуре нашего JSON. Если data class сложный с большим количеством вложений, то можно воспользоваться плагином Android Studio “JSON to KOTLIN CLASS“. В этот плагин вы вставляете свой JSON, полученный по вашей ссылке, а плагин автоматически создаёт вам все классы со всеми вложенными классами. Простые модели проще создать вручную.
@SerializedName(“country_id”)
val id: Int,
Эта запись означает что из JSON мы хотим получить переменную country_id, но в приложении мы хотим эту переменную называть id. Это для удобства, можно сделать как и на остальных переменных убрать эту запись и имя переменной и в JSON и в приложении будет называться одинаково.
data class CountryRequest( val error: Boolean, val countries: List<Country> ) data class Country( @SerializedName("country_id") val id: Int, val title_ru: String )
3. Создаём интерфейс
Создаем интерфейс с именем RestCountriesApi. Создаём запрос GET – для получения JSON данных с сервера. Внутри GET запроса помещаем параметр к основной ссылки. Для информации, полностью ссылка с параметром выглядит так: https://a.jandroid.ru/mysql_cities/?op=getcountries. Попробуйте перейти по ней через браузер, вы увидите JSON который мы хотим получить и обработать в приложении.
https://a.jandroid.ru/mysql_cities/ – это основная ссылка
?op=getcountries – это параметр
В GET мы записываем параметр.
Далее создаем абстрактную suspend функцию выводящую объект типа CountryRequest – это заглавный элемент нашей data модели.
suspend говорит о том, что мы планируем эту функцию запускать в корутине.
interface RestCountriesApi { @GET("?op=getcountries") suspend fun getAllCountry(): CountryRequest }
4. Подключаем Retrofit
Retrofit.Builder() – говорим что будем создавать Retrofit
baseUrl – указываем базовую ссылку
addConverterFactory(GsonConverterFactory.create()) – конвертируем JSON в GSON
var restCountriesApi = Retrofit.Builder() .baseUrl("https://a.jandroid.ru/mysql_cities/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(RestCountriesApi::class.java)
5. Вызываем Retrofit в Coroutines
Теперь в нужном месте кода мы можем вызвать созданный Retrofit, и так как функция интерфейса была помечена как suspend (прерываемая), то вызывать мы должны Retrofit в корутине.
lifecycleScope.launch { } – это корутина, в ее теле мы запустим созданный Retrofit.
val response = restCountriesApi.getAllCountry() – запускаем Retrofit и присваиваем результат переменной response. Тип данных переменной response – CountryRequest.
lifecycleScope.launch { val response = restCountriesApi.getAllCountry() }
Варианты запросов Retrofit
/** Запрос как мы используем в приложении Запрос на получение данных только с параметром "users" */ @GET("users") fun getUsers(): Call<List<User>> /** В этом запросе мы в переменную {name} подставляем входное значение функции getUsers String name */ @GET("name/{name}") fun getUsers(@Path("name") name: String): Call<List<User>> /** Параметры запроса добавляются с помощью аннотации @Query к параметру метода. Они автоматически добавляются в конце URL-адреса */ @GET("users") fun getUserById(@Query("id") id: Integer): Call<User> /** Аннотация @ Body к параметру метода говорит Retrofit использовать объект в качестве тела запроса для вызова */ @POST("users") fun postUser(@Body user: User): Call<User>
Retrofit аутентификация
Retrofit поддерживает вызовы API, требующие аутентификации. Аутентификацию можно выполнить, используя имя пользователя и пароль (аутентификация Http Basic) или API токен.
Существует два способа управления аутентификацией. Первый метод — управлять заголовком запроса с помощью аннотаций. Другой способ — использовать для этого OkHttp перехватчик.
Аутентификация с аннотациями
Предположим, что вы хотите запросить информацию о пользователе, для которой требуется аутентификация. Вы можете сделать это, добавив новый параметр в определение API, например:
@GET("user") fun getUserDetails(@Header("Authorization") credentials: String): Call<UserDetails>
С помощью аннотации @ Header(«Authorization») вы говорите Retrofit добавить заголовок Authorization в запрос со значением, которое вы передаете.
Чтобы генерировать учетные данные для Basic authentication, вы можете использовать класс OkHttps Credentials с его базовым (String, String) методом. Метод принимает имя пользователя и пароль и возвращает учетные данные аутентификации для Http Basic схемы.
Credentials.basic("username","password")
Если вы хотите использовать API токен и не использовать Basic схему, просто вызовите метод getUserDetails(String) с вашим токеном.
Аутентификация с помощью OkHttp перехватчиков
Метод выше добавляет учетные данные, только если вы запрашиваете данные пользователя. Если у вас больше вызовов, требующих аутентификации, для этого вы можете использовать перехватчик. Перехватчик используется для изменения каждого запроса до его выполнения и устанавливает заголовок запроса. Преимущество состоит в том, что вам не нужно добавлять @Header(«Authorization») к каждому определению метода API.
Чтобы добавить перехватчик, вы должны использовать метод okhttp3.OkHttpClient.Builder.addInterceptor(Interceptor) в OkHttp Builder.
var okHttpClient = OkHttpClient().newBuilder().addInterceptor { chain -> val originalRequest: Request = chain.request() val builder: Request.Builder = originalRequest.newBuilder().header( "Authorization", Credentials.basic("aUsername", "aPassword") ) val newRequest: Request = builder.build() chain.proceed(newRequest) }.build()
Созданный OkHttp клиент должен быть добавлен в ваш Retrofit клиент с помощью метода retrofit2.Retrofit.Builder.client(OkHttpClient).
var retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .build()
Как вы заметили, здесь используется класс Credentials для Basic авторизации.
Опять же, если вы хотите использовать токен API, просто используйте вместо этого токен.
Практическая часть
Создадим приложение Countries в Android Studio
В практической части мы создадим настоящее приложение реализующее Retrofit.
Наше приложение будет обращаться к удаленному серверу с запросом GET для получения JSON. В этом JSON мы будем получать список всех стран нашей планеты. Затем мы выведем этот список стран в RecyclerView и повесим слушатели нажатий.
Затем мы создадим свой Backend для нашего приложения. Создадим базу данных MySQL на сервере, локальном или удаленном. Напишем связующий интерфейс на PHP, который будет получать данные из MySql и выводить их в JSON по определённой ссылке.
1. Интерфейс приложения
Отсутствующие значки и цвета добавьте сами.
activity_main.xml – это основной вид экрана
country_item.xml – это вид элемента для RecyclerView
Обратите внимание на вкладки окна кода. Щелкая по вкладкам вы можете перемещаться по листам кода.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:background="@color/light_gray"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerViewCountries" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/buttonUpdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:clickable="true" app:backgroundTint="@color/purple_200" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:srcCompat="@drawable/ic_update" /> </androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/transparent" android:orientation="vertical"> <androidx.cardview.widget.CardView android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="2dp" android:layout_marginTop="2dp" android:layout_marginEnd="2dp" android:layout_marginBottom="2dp" app:cardCornerRadius="10dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tvIdCountry" android:layout_width="50dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@color/purple_500" android:gravity="center" android:text="Id" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" android:textColor="@color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tvTitleCountry" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:text="Country title" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" app:layout_constraintBottom_toBottomOf="@+id/tvIdCountry" app:layout_constraintEnd_toStartOf="@+id/buttonMore" app:layout_constraintStart_toEndOf="@+id/tvIdCountry" app:layout_constraintTop_toTopOf="@+id/tvIdCountry" /> <ImageButton android:id="@+id/buttonMore" android:layout_width="50dp" android:layout_height="50dp" android:backgroundTint="@color/white" app:layout_constraintBottom_toBottomOf="@+id/tvIdCountry" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/tvIdCountry" app:srcCompat="@drawable/ic_info" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> </androidx.constraintlayout.widget.ConstraintLayout>
2. Добавим библиотеки
Библиотеку Volley добавил для того, чтобы показать как тоже самое написать на ней и сравнить. Но это потом. Сейчас сконцентрируемся на Retrofit.
plugins { ... // Добавляем Synthetic id 'kotlin-android' id 'kotlin-android-extensions' } ... buildTypes { ... } // Это ViewBinding если кто не знает что это почитайте в Google buildFeatures{ viewBinding true } ... dependencies { ... //Volley implementation 'com.android.volley:volley:1.2.1' // Retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' }
3. Код приложения
Помните, название пакета копировать не надо. У вас должен быть свой пакет, который дается при создании приложения. Файлы кода разбиты по вкладкам.
Model.kt – это файл data class содержащий модель данных
EndPoints – тут хранится базовая ссылка и параметры для получения JSON
RemoteDataSource.kt – тут мы создаем наш Retrofit и пишем интерфейс с запросом
CountryAdapter – это адаптер для RecyclerView. описывать как он работает я тут не буду, это тема другого урока.
MainActivity – это главная и единственная активность нашего приложения.
override fun oClick и override fun oClickMore – переписывают интерфейс нажатий RecyclerView
recyclerInit() – создаем RecyclerView
loadCountriesByRetrofit() – запуск созданного в RemoteDataSource.kt Retrofit
Все этапы создания Retrofit описаны выше на этом же примере, так что повторятся не буду.
package ru.jandroid.mysql_cities data class CountryRequest( val error: Boolean, val countries: List<Country> ) data class Country( val country_id: Int, val title_ru: String )
package ru.jandroid.mysql_cities object EndPoints { const val URL_ROOT = "https://a.jandroid.ru/mysql_cities/" const val URL_GET_COUNTRIES= "?op=getcountries" }
package ru.jandroid.mysql_cities import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET interface RestCountriesApi { @GET(EndPoints.URL_GET_COUNTRIES) suspend fun getAllCountry(): CountryRequest } var restCountriesApi = Retrofit.Builder() .baseUrl(EndPoints.URL_ROOT) .addConverterFactory(GsonConverterFactory.create()) .build() .create(RestCountriesApi::class.java)
package ru.jandroid.mysql_cities import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import ru.jandroid.mysql_cities.databinding.CountryItemBinding class CountryAdapter(val listener:Listener):RecyclerView.Adapter<CountryAdapter.CountryHolder>() { var countryList=ArrayList<Country>() //ViewHolder Class class CountryHolder(item: View):RecyclerView.ViewHolder(item) { val binding= CountryItemBinding.bind(item) fun bind(country:Country, listener:Listener) = with(binding){ tvIdCountry.text=country.country_id.toString() tvTitleCountry.text=country.title_ru itemView.setOnClickListener { listener.oClick(country, position) } buttonMore.setOnClickListener { listener.oClickMore(country) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CountryHolder { val view= LayoutInflater.from(parent.context).inflate(R.layout.country_item, parent, false) return CountryHolder(view) } override fun onBindViewHolder(holder: CountryHolder, position: Int) { holder.bind(countryList[position], listener) } override fun getItemCount(): Int { return countryList.size } //Наполняем элементами при загрузке fun fillCountry(countryListFromMain:ArrayList<Country>){ countryList = countryListFromMain notifyDataSetChanged() } //-------------- ИНТЕРФЕЙС ДЛЯ СЛУШАТЕЛЕЙ interface Listener{ fun oClick(country:Country, index: Int) fun oClickMore(country:Country) } }
package ru.jandroid.mysql_cities import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import com.android.volley.Request import com.android.volley.toolbox.StringRequest import com.android.volley.toolbox.Volley import kotlinx.coroutines.* import org.json.JSONObject import ru.jandroid.mysql_cities.databinding.ActivityMainBinding class MainActivity : AppCompatActivity(), CountryAdapter.Listener { lateinit var binding: ActivityMainBinding private val adapter = CountryAdapter(this) private var countrytList = ArrayList<Country>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.buttonUpdate.setOnClickListener { loadCountriesByRetrofit() } recyclerInit() loadCountriesByRetrofit() } override fun oClick(country: Country, index: Int) { Toast.makeText(this, country.title_ru, Toast.LENGTH_SHORT).show() } override fun oClickMore(country: Country) { Toast.makeText(this, country.country_id, Toast.LENGTH_SHORT).show() } // RECYCLER ADAPTER private fun recyclerInit(){ binding.apply { recyclerViewCountries.layoutManager = GridLayoutManager(this@MainActivity,1) recyclerViewCountries.adapter=adapter //rcView.setHasFixedSize(true) //фиксируем размер Item } } // JSON с помощью Retrofit и Coroutines private fun loadCountriesByRetrofit() { countrytList.clear() lifecycleScope.launch { val response = restCountriesApi.getAllCountry() // Основной поток для интерфейса countrytList.addAll(response.countries) countrytList.sortBy { it.country_id } adapter.fillCountry(countrytList) } } }
Создадим свой Backend:
база данных MySql и интерфей на PHP
В общем то на этом можно и остановиться. Ваше приложение уже будет работать, при условии что работает чужой сервер (в нашем случае – мой сервер), который выдаёт JSON карту со списком городов. Но вы можете создать свой Backend.
Создадим базу данных MySql
Для начала скачайте файл с городами в формате CSV, далее мы его импортируем.
База данных MySql хранится на сервере. В моем случае это сервер хостинга, в вашем случае это может быть виртуальный сервер на основе сборки Denver или Open Server. Как установить виртуальный сервер, если у вас нет хостинга можете найти в Google, там очень много материалов как их ставить и настраивать.
Итак, мы должны создать базу данных MySql, задав ей название, логин и пароль.
Далее создадим в ней таблицу “countries” и добавим 2 поля:
country_id типа Int
title_ru типа varchar
Затем нажимаем импортировать и импортируем скаченную базу данных в формате CSV, обратите внимание на тип разделителя, должен быть точка с запятой ( ; )
Создание веб-сервисов PHP для формирования своих API и JSON
Создайте домен или виртуальный домен.
Создайте папку mysql_cities внутри домена (можно создать папку с любым именем, но потом нужно не забыть в коде подкорректировать пути). В эту папку мы будем копировать наши PHP файлы и ссылаться из Android Studio позже будем тоже на эту директорию.
Пишем сервисы на PHP
<?php define('DB_HOST','localhost'); define('DB_USERNAME','ИМЯ ПОЛЬЗОВАТЕЛЯ БД'); define('DB_PASSWORD','ПАРОЛЬ ОТ БД'); define('DB_NAME', 'ИМЯ БАЗЫ ДАННЫХ');
<?php class DbConnect { //Переменная для хранения ссылки на базу данных private $con; //Конструктор класса function __construct(){ } //Метод для подключения к базе данных function connect() { //Подключаем файл constants.php include_once dirname(__FILE__) . '/Constants.php'; //Подключение к базе данных mysql $this->con = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME); //Если произошла ошибка при подключении if (mysqli_connect_errno()) { echo "Failed to connect to MySQL: " . mysqli_connect_error(); return null; } //Наконец, возвращаем ссылку подключения return $this->con; } }
<?php class DbOperation { private $con; function __construct() { require_once dirname(__FILE__) . '/DbConnect.php'; $db = new DbConnect(); $this->con = $db->connect(); } //Извлечение всех записей из базы данных public function getCountries(){ $stmt = $this->con->prepare("SELECT country_id, title_ru FROM countries"); $stmt->bind_result($country_id, $title_ru); $stmt->execute(); $countries = array(); while($stmt->fetch()){ $temp = array(); $temp['country_id'] = $country_id; $temp['title_ru'] = $title_ru; array_push($countries, $temp); } return $countries; } }
<?php //Добавление операционного файла БД require_once '../mysql_cities/DbOperation.php'; //Массив ответов $response = array(); //Вызываем API если запрос GET с именем "op" if(isset($_GET['op'])){ //Переключатель значения операции switch($_GET['op']){ //Если в запросе получен "getcountries" то мы извлекаем все данные case 'getcountries': $db = new DbOperation(); $countries = $db->getCountries(); if(count($countries)<=0){ $response['error'] = true; $response['message'] = 'В базе данных ничего не найдено'; }else{ $response['error'] = false; $response['countries'] = $countries; } break; default: $response['error'] = true; $response['message'] = 'Нет операции для выполнения'; } }else{ $response['error'] = false; $response['message'] = 'Неверный запрос'; } //Отобразить данные в JSON echo json_encode($response);
Подробное описание того, что здесь написано вы можете прочитать в ранее написанной мною статье. Тут всё подробно расписано.
Тоже самое но с библиотекой Volley
Заменим функцию loadCountriesByRetrofit() на loadCountriesByVolley()
// JSON с помощью Volley private fun loadCountriesByVolley() { showToast("Загрузка") val stringRequest = StringRequest(Request.Method.GET, (EndPoints.URL_ROOT+EndPoints.URL_GET_COUNTRIES), { response -> val obj = JSONObject(response) val objArray = obj.getJSONArray("countries") countrytList.clear() for (i in 0 until objArray.length()) { val objectCountries = objArray.getJSONObject(i) val country = Country( objectCountries.getInt("country_id"), objectCountries.getString("title_ru"), ) countrytList.add(country) } countrytList.sortBy { it.country_id } adapter.fillCountry(countrytList) }, { Log.d("MyLog", "Ошибка Volly: ${it.toString()}") }) // Создаём очередь val queue = Volley.newRequestQueue(this) // Добавляем в очередь queue.add(stringRequest) }
В результате вы получите тоже самое, что и с Retrofit.
Зависимости для Volley мы уже добавили ранее.
Файл RemoteDataSource.kt нам для Volley не нужен.
Что лучше или хуже сказать не могу, библиотеки примерно похожи. Кому как удобнее, каждый выбирает сам.
Заключение
Вот мы и создали наше приложение и базу данных с интерфейсом на PHP. Приложение конечно сыроватое получилось, но как заготовка вполне ничего.
Спасибо за просмотр и до встречи на следующих уроках.