Retrofit в связке с Coroutines

Приветствую вас на портале 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. Приложение конечно сыроватое получилось, но как заготовка вполне ничего.

Спасибо за просмотр и до встречи на следующих уроках.

Поделись с друзьями:
Если вам понравилась статья, подписывайтесь на наши социальные сети.

Оставьте комментарий

одиннадцать − 3 =