MySQL-PHP-JSON-Android

Описание приложения

На этом уроки мы создадим гибридное Android приложение на языке Kotlin, позволяющее считывать данные из базы данных MySql расположенной на хостинге, и передавать эти данные в наше Android приложение. Как оно будет выглядеть вы можете увидеть на ниже приведенном изображении.

Итак, давайте немного расскажу о приложении и потом о том как оно устроено.

Данные будут хранится в базе данных MySql на сервере. Также на сервере разместим сервисный код PHP, который будет получать, записывать, обновлять и удалять данные из MySql. Также PHP код будет выводить JSON. Из Android Studio мы будем обращаться к JSON по средствам GET и POST запросов. В Android Studio для упрощения работы с запросами мы используем библиотеку Volly.

Ну что, звучит страшно? На деле всё логически понятно. Знания PHP почти не понадобятся, все можно логически осмыслить. Знания Kotlin и Android Studio конечно нужны. Ну приступим.

Создание базы данных MySql

На сервере хранится база данных MySql. В моем случае это сервер хостинга, в вашем случае это может быть виртуальный сервер на основе сборки Denver или Open Server. Как установить виртуальный сервер, если у вас нет хостинга можете найти в Google, там очень много материалов как их ставить и настраивать. Наш же урок об Android приложении.

Итак, мы должны создать базу данных MySql, задав ей название, логин и пароль. Далее создадим в ней таблицу “artists” и добавим 3 поля:
id типа Int (AUTO_INCREMENT)
name типа varchar
genre типа varchar

Создание веб-сервисов PHP для формирования своих API и JSON

Создайте домен или виртуальный домен.

Создайте папку mysql_artist внутри домена (можно создать папку с любым именем, но потом нужно не забыть в коде подкорректировать пути). В эту папку мы будем копировать наши PHP файлы и ссылаться из Android Studio позже будем тоже на эту деректорию.

Constants.php

Теперь напишем наш первый файл Constants.php

<?php 
  define('DB_HOST','localhost');
 define('DB_USERNAME','ИМЯ ПОЛЬЗОВАТЕЛЯ БД');
 define('DB_PASSWORD','ПАРОЛЬ ОТ БД');
 define('DB_NAME', 'ИМЯ БАЗЫ ДАННЫХ');

С именем пользователя, паролем и именем базы данных, я думаю вопросов возникнуть не должно. А вот с HOST скорее всего есть вопросы. Смотрите в моем случае адрес моего хоста localhost потому, что PHP файлы обращающиеся к БД лежат на этом же хосте. Если у вас например файлы лежат на виртуальном сервере а БД находится на хостинге в сети, то нужно указать IP адреcc сервера на котором находится БД. Т.е. если БД и PHP на одном сервере с одним IP то можно указать localhost иначе IP. Если вы указываете IP то учтите, что по умолчанию указывается порт :3306. Если ваша MySql имеет другой порт, то его нужно указать.
Например так:

define('DB_HOST','192.168.1.1:3307');

DbConnect.php

Файл DbConnect.php отвечает за соединение с базой данных MySql и в конечном итоге выводит ссылку на подключение к БД.

<?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;
    }
 
}

DbOperation.php

Файл DbOperation.php содержит функции с запросами к БД:
createArtist() – отвечает за добавление позиции
delArtist() – отвечает за удаление позиции
updArtist() – отвечает за изменение позиции
getArtists() – отвечает за выдачу всей таблицы

Если вы захотите в будущем сделать выборку данных то вы будите дописывать новые функции здесь.

<?php
 
class DbOperation
{
    private $con;
 
    function __construct()
    {
        require_once dirname(__FILE__) . '/DbConnect.php';
        $db = new DbConnect();
        $this->con = $db->connect();
    }
 
 //Добавление записи в базу данных
 public function createArtist($name, $genre){
 $stmt = $this->con->prepare("INSERT INTO artists (name, genre) VALUES (?, ?)");
 $stmt->bind_param("ss", $name, $genre);
 if($stmt->execute())
 return true; 
 return false; 
 }
 
  //Удалить запись из базы данных
 public function delArtist($id){
 $stmt = $this->con->prepare("DELETE FROM artists WHERE id='$id'");
 $stmt->bind_param("ss", $id);
 if($stmt->execute())
 return true; 
 return false; 
 }
 
   // Обновить запись в базе данных
 public function updArtist($id, $name, $genre){
 $stmt = $this->con->prepare("UPDATE artists SET name = '$name', genre = '$genre' WHERE id = '$id'");
 $stmt->bind_param("ss", $name, $genre);
 if($stmt->execute())
 return true; 
 return false; 
 }
 
 //Извлечение всех записей из базы данных
 public function getArtists(){
 $stmt = $this->con->prepare("SELECT id, name, genre FROM artists");
 $stmt->bind_result($id, $name, $genre);
 $stmt->execute();
 $artists = array();
 
 while($stmt->fetch()){
 $temp = array(); 
 $temp['id'] = $id; 
 $temp['name'] = $name; 
 $temp['genre'] = $genre; 
 array_push($artists, $temp);
 }
 return $artists;
 }
}

index.php

Файл index.php отвечает за прием и формирование запросов JSON. Так же JSON фильтрует приходящие запросы и в соответствии с условиями запускает нужные функции, на добавление удаление или выборку.

<?php 
 
 //Добавление операционного файла БД
 require_once '../mysql_artist/DbOperation.php';
 
 //Массив ответов
 $response = array(); 
 
 //Вызываем API если запрос GET с именем "op"
 if(isset($_GET['op'])){
 
     //Переключатиль значения операции
     switch($_GET['op']){
     
         //Если в запросе получен "addartist"
         //мы добавим исполнителя
         case 'addartist':
         if(isset($_POST['name']) && $_POST['name'] != "" && isset($_POST['genre'])){
             $db = new DbOperation(); 
             if($db->createArtist($_POST['name'], $_POST['genre'])){
                 $response['error'] = false;
                 $response['message'] = 'Артист добавлен удачно.';
             }else{
                 $response['error'] = true;
                 $response['message'] = 'Не получилось добавить артиста!!!';
             }
         }else{
             $response['error'] = true; 
             $response['message'] = 'Необходимые параметры отсутствуют!!!';
         }
         break; 
         
         
         // Если в запросе получен "delartist" то мы удаляем
         case 'delartist':
         if(isset($_POST['id']) && $_POST['id'] != ""){
             $db = new DbOperation();
             if($db->delArtist($_POST['id'])){
                 $response['error'] = false;
                 $response['message'] = 'Артист удален.';
             }else{
                 $response['error'] = true;
                 $response['message'] = 'Не получилось удалить артиста!!!';
             }
         }else{
             $response['error'] = true; 
             $response['message'] = 'Необходимые параметры отсутствуют!!!';
         }
         break;
         
         // Если в запросе получен "updartist" то мы удаляем
         case 'updartist':
         if(isset($_POST['id']) && $_POST['id'] != "" && isset($_POST['name']) && $_POST['name'] != "" && isset($_POST['genre'])){
             $db = new DbOperation();
             if($db->updArtist($_POST['id'], $_POST['name'], $_POST['genre'])){
                 $response['error'] = false;
                 $response['message'] = 'Артист обновлен.';
             }else{
                 $response['error'] = true;
                 $response['message'] = 'Не получилось обновить артиста!!!';
             }
         }else{
             $response['error'] = true; 
             $response['message'] = 'Необходимые параметры отсутствуют!!!';
         }
         break;
         
         //Если в запросе получен "getartist" то мы извлекаем все данные
         case 'getartists':
             $db = new DbOperation();
             $artists = $db->getArtists();
             if(count($artists)<=0){
                 $response['error'] = true; 
                 $response['message'] = 'В базе данных ничего не найдено';
             }else{
                 $response['error'] = false; 
                 $response['artists'] = $artists;
             }
             break; 
             
         default:
         $response['error'] = true;
         $response['message'] = 'Нет операции для выполнения';
         
     }
 
 }else{
     $response['error'] = false; 
     $response['message'] = 'Неверный запрос';
 }
 
 //Отобразить данные в JSON
 echo json_encode($response);
 

Тестируем с Postman

С серверной частью мы закончили.
Чтобы протестировать, что наши запросы к MySql работают предлагаю воспользоваться приложением Postman, которое способно выполнять запросы API к любому API HTTP.

Выбираем GET -> вводим запрос в строку ->нажимаем SEND
Внизу должен появится результат и код результата 200OK. Значит все впорядке и можно приступать к написанию приложения на Android Studio.

Приложение в Android Studio

Подготовим наш проект к работе

  • Создадим новый проект -> Empty Activity -> Придумаем Name название проекту -> Выберем папку хранения нашего проекта -> язык Kotlin ->Finish
  • Добавим в файл build.gradle (Module) следующие зависимости
plugins {
    ...
    // Добавляем Synthetic это чтобы не писать findViewById для View диалогового окна
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}
    ...
buildTypes {
    ...
    }
    // Это ViewBinding если кто не знает что это почитайте в Google
    buildFeatures{
        viewBinding true
    }
    ...
dependencies {
    ...
    // Это библиотека Volly аналог Retrofit для отправки и получения запросов API JSON
    implementation 'com.android.volley:volley:1.2.1'
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}
  • Еще вам понадобится создать иконки: ic_add.xml (это значок +), ic_close.xml (это значок X), ic_delete.xml (это значок КОРЗИНЫ).
  • Векторный файл для фона Alert Dialog fon_corner.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient android:angle="45"
        android:startColor="@color/color_Blue_Light"
        android:endColor="@color/colorGrayText"/>
    <corners android:radius="12dp"/>
</shape>
  • Векторный файл для подложки крестика Alert Dialog view_corner.xml
<?xml version="1.0" encoding="utf-8"?>
<vector android:autoMirrored="true" android:height="105dp"
    android:viewportHeight="105" android:viewportWidth="81"
    android:width="81dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#fff" android:pathData="M0,0H81V105Z"/>
</vector>
  • добавьте в файл colors.xml следующие цвета, если еще каких либо не хватает добавьте свои
    <color name="color_Blue_Light">#20EFFE</color>
    <color name="colorGrayText">#C3C4C8</color>
    <color name="colorBlacky">#3A3A3A</color>
    <color name="colorAccent">#04CE9B</color>
    <color name="colorRed">#F44336</color>

Разметки экранов

activity_main.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/recyclerViewArtists"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        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/buttonAdd"
        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_add" />

</androidx.constraintlayout.widget.ConstraintLayout>

artist_item.xml

Это у нас разметка для RecyclerView. В нем есть два TextView для вывода Name и Genre, и еще ID. И есть кнопка удалить, нажав по которой мы сможем удалить выбранную запись прямо из 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="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="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        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/textViewId"
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:background="@color/purple_500"
                android:textColor="@color/white"
                android:gravity="center"
                android:text="Id"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/textViewName"
                android:layout_width="0dp"
                android:layout_height="wrap_content"

                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                android:text="Oleg"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
                app:layout_constraintBottom_toBottomOf="@+id/textViewId"
                app:layout_constraintEnd_toStartOf="@+id/buttonDelete"
                app:layout_constraintStart_toEndOf="@+id/textViewId"
                app:layout_constraintTop_toTopOf="@+id/textViewId" />

            <TextView
                android:id="@+id/textViewGenre"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="16dp"
                android:layout_marginBottom="16dp"
                android:text="Rock"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/buttonDelete"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textViewName" />

            <ImageButton
                android:id="@+id/buttonDelete"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:backgroundTint="@color/white"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_delete" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

custom_alert_dialog.xml

Это экран Custom Alert Dialog. Диалоговое окно нам нужно будет для того, чтобы добавлять новую позицию, а так же мы это окно будем использовать, чтобы вводить изменения в позиции, т.е. править.

<?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:layout_margin="10dp"
    android:background="@android:color/transparent">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="32dp"
        app:circularflow_radiusInDP="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/fon_corner" />

    <Space
        android:id="@+id/space"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/view"
        android:layout_width="40dp"
        android:layout_height="60dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:background="@drawable/ic_corner"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageButton
        android:id="@+id/btnClose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="@android:color/transparent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_close" />

    <Button
        android:id="@+id/btn_AddUpdate"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        android:backgroundTint="@color/colorAccent"
        android:text="Добавить запись"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:cornerRadius="10dp"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Добавить запись"
        android:textColor="@color/colorBlacky"
        android:textSize="20dp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:layout_height="1dp"
        android:layout_marginStart="32dp"
        android:layout_marginTop="3dp"
        android:layout_marginEnd="32dp"
        android:background="@color/colorBlacky"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="32dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view2">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/etName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white"
            android:hint="Введите имя" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toTopOf="@+id/btn_AddUpdate"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textInputLayout"
        app:layout_constraintVertical_bias="0.0">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/etGenre"
            android:layout_width="match_parent"
            android:layout_height="161dp"
            android:background="@color/white"
            android:gravity="top"
            android:hint="Введите характеристику" />
    </com.google.android.material.textfield.TextInputLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Программная часть приложения

Пришло время написать код нашего приложения.

Внимание!

Когда будете копировать код Kotlin не заменяйте свой пакет на мой
package ru.jandroid.mysql_kotlin
Оставляйте свой пакет, иначе ничего работать не будет.

Artist.kt

Класс, содержащий три переменные: id, name, genre. Это похоже на пользовательскую переменную, которая содержит не цифры не буквы, а три переменных типа String.

package ru.jandroid.mysql_kotlin

// Класс данных
class Artist(var id: String, var name: String, var genre: String)

ArtistAdapter.kt

Адаптер RecyclerView, в него же прописан и интерфейс. Интерфейс нужен, чтобы связать адаптер с Activity, чтобы прослушивать нажатия на элементы и на кнопку удалить. В адаптере все override методы переопределены, то есть это принятая форма создания адаптера. А вот под комментарием “ОБРАБОТКА НАЖАТИЙ” функции свои, в зависимости от задач приложения. Запись notifyDataSetChanged() говорит, что адаптер надо обновить, поскольку список в нем изменился. ArtistHolder тоже встроен в адаптер и в нем прописываются действия с нашими View элементами экрана. Обычно ArtistHolder и Intrface выносятся в отдельные файлы, но в нашем случае приложение простое и чтобы все было компактнее я уложил все в адаптер.

package ru.jandroid.mysql_kotlin

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import ru.jandroid.mysql_kotlin.databinding.ArtistItemBinding

class ArtistAdapter(val listener:Listener):RecyclerView.Adapter<ArtistAdapter.ArtistHolder>() {
    var artistList=ArrayList<Artist>()

    //ViewHolder Class
    class ArtistHolder(item: View):RecyclerView.ViewHolder(item) {
        val binding= ArtistItemBinding.bind(item)
        fun bind(artist:Artist, listener:Listener) = with(binding){
            textViewId.text=artist.id
            textViewName.text=artist.name
            textViewGenre.text=artist.genre
            itemView.setOnClickListener {
                listener.oClick(artist, position)
            }
            buttonDelete.setOnClickListener {
                listener.oClickDelete(artist)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtistHolder {
        val view= LayoutInflater.from(parent.context).inflate(R.layout.artist_item, parent, false)
        return ArtistHolder(view)
    }

    override fun onBindViewHolder(holder: ArtistHolder, position: Int) {
        holder.bind(artistList[position], listener)
    }

    override fun getItemCount(): Int {
        return artistList.size
    }

// ----------------------------- ОБРАБОТКА НАЖАТИЙ
    //Добавляем элемент поштучно
    fun addArtist(artist:Artist){
        artistList.add(artist)
        notifyDataSetChanged()
    }

    //Удаляем элемент
    fun deleteArtist(artist:Artist){
        artistList.remove(artist)
        notifyDataSetChanged()
    }

    //Обновляет элемент
    fun updateArtist(artist:Artist, index : Int){
        artistList[index].name = artist.name
        artistList[index].genre = artist.genre
        notifyDataSetChanged()
    }

    //наполняем элементами при загрузке
    fun fillArtist(artistListFromMain:ArrayList<Artist>){
        artistList = artistListFromMain
        notifyDataSetChanged()
    }

//-------------- ИНТЕРФЕЙС ДЛЯ СЛУШАТЕЛЕЙ
    interface Listener{
        fun oClick(artist:Artist, index: Int)
        fun oClickDelete(artist:Artist)
    }
}

EndPoints.kt

Это хранилище наших ссылок на JSON объект сервера, создаваемый ранее написанным кодом PHP. URL_ROOT – основная ссылка, а остальные будут дописываться к основной. Например если мы захотим добавить новый элемент в БД MySql то наша ссылка будет сложена так:
https://a.jandroid.ru/mysql_artist/?op=addartist
У вас, конечно же URL_ROOT будет своим, если вы используете хостинг то, конечно же знаете свое доменное имя. Если используете виртуальный сервер, то как правило доменное имя соответствует имени корневой папке домена.

package ru.jandroid.mysql_kotlin

// Точки входа JSON - для формирования запросов к серверу
object EndPoints {
    private val URL_ROOT = "https://a.jandroid.ru/mysql_artist/?op="
    val URL_ADD_ARTIST = URL_ROOT + "addartist"
    val URL_GET_ARTIST = URL_ROOT + "getartists"
    val URL_DEL_ARTIST = URL_ROOT + "delartist"
    val URL_UPD_ARTIST = URL_ROOT + "updartist"
}

VolleySingleton.kt

Этот класс нужен для отправки POST запросов через библиотеку Volly. Просто копируйте и трогать в нем ничего не надо. Нужно только будет в файле Манифеста прописать android:name=”.VolleySingleton”, файл манифеста выложу позже.

package ru.jandroid.mysql_kotlin

import android.app.Application
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.toolbox.Volley

//Для POST запросов
class VolleySingleton : Application() {
    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    val requestQueue: RequestQueue? = null
        get() {
            if (field == null) {
                return Volley.newRequestQueue(applicationContext)
            }
            return field
        }

    fun <T> addToRequestQueue(request: Request<T>) {
        request.tag = TAG
        requestQueue?.add(request)
    }

    companion object {
        private val TAG = VolleySingleton::class.java.simpleName
        @get:Synchronized var instance: VolleySingleton? = null
            private set
    }
}

AndroidManifest.xml

В манифесте не забывайте, что package=”ru.jandroid.mysql_kotlin” оставляйте свой.
Вам нужно добавить только: <uses-permission android:name=”android.permission.INTERNET” />
и android:name=”.VolleySingleton”.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ru.jandroid.mysql_kotlin">
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".VolleySingleton"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MySql_Kotlin">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

MainActivity.kt

Это основной файл нашего приложения, в котором прописана вся логика.

package ru.jandroid.mysql_kotlin

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.GridLayoutManager
import com.android.volley.AuthFailureError
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.VolleyError
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import kotlinx.android.synthetic.main.custom_alert_dialog.view.*
import org.json.JSONException
import org.json.JSONObject
import ru.jandroid.mysql_kotlin.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), ArtistAdapter.Listener {
    lateinit var binding: ActivityMainBinding
    private val adapter = ArtistAdapter(this)
    private var artistList = ArrayList<Artist>()
    private var index = 0
    private var artist = Artist("", "", "")


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        recyclerInit()
        loadArtists()

        // Обработка кнопки +
        binding.buttonAdd.setOnClickListener { Dialog_Add_Update(this, "add") }
    }

    //Обработка КЛИКА по элементу
    override fun oClick(artist: Artist, index: Int) {
        this.artist = artist
        this.index = index
        Dialog_Add_Update(this, "upd", artist)
    }

    //Обработка кнопки УДАЛИТЬ с подтверждением AlertDialog
    override fun oClickDelete(artist: Artist) {
        AlertDialog.Builder(this)
            .setTitle("Удаление растения")
            .setMessage("Вы действительно хотите удалить растение?")
            .setPositiveButton("Да"){dialog, which ->
                adapter.deleteArtist(artist)
                add_del_upd_Artist(EndPoints.URL_DEL_ARTIST, artist)
            }.setNegativeButton("Нет"){dialog, which ->

            }
            .show()
    }

    // Включаем RECYCLER ADAPTER
    private  fun recyclerInit(){
        binding.apply {
            recyclerViewArtists.layoutManager = GridLayoutManager(this@MainActivity,1)
            recyclerViewArtists.adapter=adapter
            //rcView.setHasFixedSize(true) //фиксируем размер Item
        }
    }

    // Диалоговое окно ДОБАВИТЬ ОБНОВИТЬ
    private fun Dialog_Add_Update(context: Context, add_upd: String, artist:Artist = Artist("","","")){
        val view = View.inflate(context, R.layout.custom_alert_dialog, null)
        view.apply {
            val customDialog = AlertDialog.Builder(context).setView(view)
            val dialog = customDialog.create()
            dialog.show()
            dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
            //dialog.setCancelable(false)

            etName.setText(artist.name)
            etGenre.setText(artist.genre)

            // Меняем подпись кнопки
            if (add_upd == "add") btn_AddUpdate.setText("Добавить запись")
            if (add_upd == "upd") btn_AddUpdate.setText("Обновить запись")

            // Обработка кнопки крестик (закрыть диалоговое окно)
            btnClose.setOnClickListener { dialog.dismiss() }

            btn_AddUpdate.setOnClickListener {
                artist.name = etName.text.toString().trim()
                artist.genre = etGenre.text.toString().trim()
                if (artist.name.isNotEmpty() && artist.genre.isNotEmpty()) {
                    // Добавляем запись
                    if (add_upd == "add") {
                        add_del_upd_Artist(EndPoints.URL_ADD_ARTIST, artist)
                        /* Это задержка на 0,5 сек для того чтобы успела выполнится
                        функция add_del_upd_Artist, иначе loadArtists() срабатывает быстрее чем 
                         add_del_upd_Artist и обновленный список не учитывает добавленную запись */
                        Thread.sleep(500)
                        loadArtists()
                        adapter.addArtist(artist)
                    // Обновляем запись
                    } else if (add_upd == "upd") {
                        add_del_upd_Artist(EndPoints.URL_UPD_ARTIST, artist)
                        adapter.updateArtist(artist, index)
                    }
                    dialog.dismiss()
                } else showToast("Пожалуйста, заполните все поля!")
            }
        }
    }

/*  JSON загрузить из сети
 Находим в JSON нужные нам поля и считываем с них значения в список artistList
 и загружаем их в адаптер по средствам функции fillArtist */
    private fun loadArtists() {
        val stringRequest = StringRequest(Request.Method.GET, EndPoints.URL_GET_ARTIST,
            { response ->
                val obj = JSONObject(response)
                val objArray = obj.getJSONArray("artists")

                artistList.clear()
                for (i in 0 until objArray.length()) {
                    val objectArtist = objArray.getJSONObject(i)
                    val artist = Artist(
                        objectArtist.getString("id"),
                        objectArtist.getString("name"),
                        objectArtist.getString("genre")
                    )
                    artistList.add(artist)
                    adapter.fillArtist(artistList)
                }
            },
            {
                Log.d("MyLog", "Ошибка Volly: ${it.toString()}")
            })

        // Создаём очередь
        val queue = Volley.newRequestQueue(this)
        // Добавляем в очередь
        queue.add(stringRequest)
    }

/* БД ----- добавить, удалить, обновить
 Отправляем POST запрос на добавить, удалить, обновить
 по средствам JSON с помощью библиотеки Volly и ранее 
  созданного нами класса VolleySingleton.kt */
    private fun add_del_upd_Artist(endPoint: String, artist: Artist) {

        val id = artist.id
        val name = artist.name
        val genre = artist.genre

        //Создаем запрос с помощью Volley
        val stringRequest = object : StringRequest(
            Request.Method.POST, endPoint,
            Response.Listener<String> { response ->
                try {
                    val obj = JSONObject(response)
                    Toast.makeText(applicationContext, obj.getString("message"), Toast.LENGTH_LONG).show()
                } catch (e: JSONException) {
                    e.printStackTrace()
                }
            },
            object : Response.ErrorListener {
                override fun onErrorResponse(volleyError: VolleyError) {
                    Toast.makeText(applicationContext, volleyError.message, Toast.LENGTH_LONG).show()
                }
            })
        {
            @Throws(AuthFailureError::class)
            override fun getParams(): Map<String, String> {
                val params = HashMap<String, String>()
                params.put("id", id)
                params.put("name", name)
                params.put("genre", genre)
                return params
            }
        }

        // Добавляем запрос в очередь
        VolleySingleton.instance?.addToRequestQueue(stringRequest)
    }
}

Заключение

Вот и все, теперь можно запустить наше приложение и все должно заработать. Конечно же приложение далеко не идеально, но может дать представление создания связки внешней базы данных MySql и приложения Android.

Если статья вам помогла, то оставьте комментарий под ней, это будет хорошей благодарностью. Спасибо за внимание.

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

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

двадцать − 1 =