Содержание
Описание приложения
На этом уроки мы создадим гибридное 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.
Если статья вам помогла, то оставьте комментарий под ней, это будет хорошей благодарностью. Спасибо за внимание.