20 - Biblioteca Volley - Consulta de datos de un servidor (PHP y MySQL)

Problema

Implementar una aplicación que permita consultar una tabla de productos por su código.

Tenemos la tabla en un servidor web llamada:

create table articulos (
    codigo int primary key AUTO_INCREMENT,
    descripcion varchar(50),
    precio float
);

Crearemos el proyecto 'Compose22'

Agregamos las dependencias de la biblioteca Volley:

dependencies {
        ...
        implementation 'com.android.volley:volley:1.2.0'
    }

Nuestra aplicación debe acceder a internet por lo que debemos pedir dicho permiso:

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

Debemos llamar a la URL:

   https://scratchya.com.ar/videosandroidjava/volley/consultar.php?codigo=?

En el servidor hay un archivo en PHP llamado 'consultar.php':

<?php
header('Content-Type: application/json');

require("conexion.php");

$conexion = retornarConexion();

$datos = mysqli_query($conexion, "select codigo,descripcion,precio from articulos where codigo=$_GET[codigo]");
$resultado = mysqli_fetch_all($datos, MYSQLI_ASSOC);
echo json_encode($resultado);
?>

Y un segundo archivo 'conexion.php'

<?php
function retornarConexion() {
    $server="localhost";
    $usuario="xxxx";
    $clave="xxxx";
    $base="xxxx";
    $con=mysqli_connect($server,$usuario,$clave,$base) or die("problemas") ;
    mysqli_set_charset($con,'utf8'); 
    return $con;
}
?>

La interfaz visual que debemos implementar con Compose debe ser similar a:

biblioteca Volley Jetpack Compose

El código a implementar en Kotlin para obtener dicha funcionalidad es:

package com.tutorialesprogramacionya.compose22

import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.android.volley.Request

import org.json.JSONException

import org.json.JSONObject

import com.android.volley.toolbox.JsonArrayRequest
import com.android.volley.toolbox.Volley


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ConsultaArticulo()
        }
    }
}

@Composable
fun ConsultaArticulo() {
    val contexto = LocalContext.current
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        var codigo by remember { mutableStateOf("") }
        var descripcion by remember { mutableStateOf("") }
        var precio by remember { mutableStateOf("") }
        var mensaje by remember { mutableStateOf("") }
        OutlinedTextField(
            value = codigo,
            onValueChange = { codigo = it },
            label = {
                Text("Código")
            },
            modifier = Modifier
                .fillMaxWidth()
                .padding(10.dp),
            singleLine = true,
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
        )
        OutlinedTextField(
            value = descripcion,
            onValueChange = { descripcion = it },
            label = {
                Text("Descripción")
            },
            modifier = Modifier
                .fillMaxWidth()
                .padding(10.dp),
            singleLine = true
        )
        OutlinedTextField(
            value = precio,
            onValueChange = { precio = it },
            label = {
                Text("Precio")
            },
            modifier = Modifier
                .fillMaxWidth()
                .padding(10.dp),
            singleLine = true,
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
        )
        Button(
            onClick = {
                ConsultaCodigo(
                    codigo = codigo.toString(),
                    respuesta = {
                        if (it!=null) {
                            descripcion = it.descripcion
                            precio = it.precio.toString()
                            mensaje=""
                        } else {
                            mensaje = "No existe el código de producto ingresado"
                            descripcion=""
                            precio=""
                        }
                    },
                    contexto = contexto
                )
            },
            modifier = Modifier.padding(10.dp)
        ) {
            Text(text = "Consultar por código")
        }
        Text(text = "$mensaje")
    }
}

data class Articulo(val codigo: Int, val descripcion: String, val precio: Float)


fun ConsultaCodigo(codigo: String, respuesta: (Articulo?) -> Unit, contexto: Context) {
    val requestQueue = Volley.newRequestQueue(contexto)
    val url = "https://scratchya.com.ar/videosandroidjava/volley/consultar.php?codigo=$codigo"
    val requerimiento = JsonArrayRequest(
        Request.Method.GET,
        url,
        null,
        { response ->
            if (response.length() == 1) {
                try {
                    val objeto = JSONObject(response[0].toString())
                    val articulo = Articulo(
                        objeto.getString("codigo").toInt(),
                        objeto.getString("descripcion"),
                        objeto.getString("precio").toFloat()
                    )
                    respuesta(articulo)
                } catch (e: JSONException) {
                }
            }
            else
                respuesta(null);
        }
    ) { error ->

    }
    requestQueue.add(requerimiento)
}

Declaramos un data class que representa una fila de la tabla 'articulos' existente en el servidor de Internet:

data class Articulo(val codigo: Int, val descripcion: String, val precio: Float)

Cuando se presiona el botón 'Consultar por código' se procede a llamar a la función 'ConsultaCodigo' y se le pasa el código de producto a consultar y mediante una función lambda se recepta la respuesta de la función con el registro del producto consultado o null en el caso que el código ingresado no exista:

Button(
            onClick = {
                ConsultaCodigo(
                    codigo = codigo.toString(),
                    respuesta = {
                        if (it!=null) {
                            descripcion = it.descripcion
                            precio = it.precio.toString()
                            mensaje=""
                        } else {
                            mensaje = "No existe el código de producto ingresado"
                            descripcion=""
                            precio=""
                        }
                    },
                    contexto = contexto
                )
            },
            modifier = Modifier.padding(10.dp)
        ) {
            Text(text = "Consultar por código")
        }

La función ConsultaCodigo procede a crear un objeto de la clase JsonArrayRequest y cuando obtenemos la respuesta si no está vacío creamos un objeto de la clase Articulo y cargamos sus 3 atributos y procedemos a llamar a la función lambda de la otra función, gracias al parámetro respuesta:

fun ConsultaCodigo(codigo: String, respuesta: (Articulo?) -> Unit, contexto: Context) {
    val requestQueue = Volley.newRequestQueue(contexto)
    val url = "https://scratchya.com.ar/videosandroidjava/volley/consultar.php?codigo=$codigo"
    val requerimiento = JsonArrayRequest(
        Request.Method.GET,
        url,
        null,
        { response ->
            if (response.length() == 1) {
                try {
                    val objeto = JSONObject(response[0].toString())
                    val articulo = Articulo(
                        objeto.getString("codigo").toInt(),
                        objeto.getString("descripcion"),
                        objeto.getString("precio").toFloat()
                    )
                    respuesta(articulo)
                } catch (e: JSONException) {
                }
            }
            else
                respuesta(null);
        }
    ) { error ->

    }
    requestQueue.add(requerimiento)
}

En el caso que el código de artículo no exista llamamos a respuesta pasando el valor null.

Este proyecto lo puede descargar en un zip desde este enlace: Compose22.zip