El acceso a base de datos de forma local mediante SQLite queda limitado al ámbito del dispositivo, por lo que en la mayoría de las veces queda insuficiente para la funcionalidad de la aplicación. Para poder utilizar una base de datos de forma colaborativa se necesita acceder a ella de forma externa mediante un servicio adicional. Las posibilidades son numerosas: conector MySQL, peticiones SOAP, ejecución PHP + análisis de JSON. En este caso se explicará la ejecución de un WebService con la ejecución de PHP y MySQL del ladeo del servidor y análisis de objetos JSON del lado del cliente para lo que se utilizará la librería Volley.

compile 'com.android.volley:volley:1.0.0'

Además de esta librería se necesita tener acceso a Internet desde el dispositivo por lo que se agrega el permiso correspondiente en el manifest de la aplicación

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

Para la explicación se utilizará un XML con opciones de agregar, borrar, actualizar y listar objetos.

sql

La primero que tenemos que hay que tener en cuenta es la existencia de una base de datos en el servidor que cuente con la estructura necesaria para soportar el modelo de datos de la aplicación. En este caso se realizará una base de datos con nombre develop y una tabla usuarios con campos id, nombre, apellido, dirección

CREATE TABLE `develop`.`usuario` ( `id` INT NOT NULL AUTO_INCREMENT , `nombre` VARCHAR(50) NOT NULL , `apellidos` VARCHAR(200) NOT NULL , `direccion` VARCHAR(300) NOT NULL , `telefono` INT(9) NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;

Del mismo modo se crea una clase que contenga un constructor y getter asociados a dichos parámetros

public class Alumno {

    String nombre, apellido, direccion;
    int id, telefono;

    public Alumno( int id, String nombre, String apellido, String direccion, int telefono) {
        this.nombre = nombre;
        this.apellido = apellido;
        this.direccion = direccion;
        this.id = id;
        this.telefono = telefono;
    }

    public String getNombre() {
        return nombre;
    }

    public String getApellido() {
        return apellido;
    }

    public String getDireccion() {
        return direccion;
    }

    public int getId() {
        return id;
    }

    public int getTelefono() {
        return telefono;
    }
}

Una vez echo esto tan solo falta crear los php asociados al servicio

php

Dentro del servidor se necesitan aquellos archivos php que serán ejecutados desde la aplicación móvil los cuales devolverán un JSON que será analizado y partido para poder utilizarlo. Para ello se crearán los siguientes archivos:

Archivo de configuración Config.php

Este archivo contendrá las claves y direcciones del servidor SQL así como el nombre de la base de datos a utilizar

<?php
	define("HOSTNAME", "localhost");
	define("USERNAME", "root");
	define("PASSWORD", "");
	define("DATABASE", "develop");
?>

Archivo de instancia de base de datos Database.php

Este archivo generará una instancia de la base de datos a utilizar. Dependiendo de como se utilice la base de datos el contenido del mismo podrá cambiar, en este caso se utiliza PDO

<?php

require_once 'Config.php';

class Database
{

    
    private static $db = null;

    private static $pdo;

    final private function __construct()
    {
        try {
            // Crear nueva conexión PDO
            self::getDb();
        } catch (PDOException $e) {
            // Manejo de excepciones
        }
    }


    public static function getInstance()
    {
        if (self::$db === null) {
            self::$db = new self();
        }
        return self::$db;
    }

    public function getDb()
    {
        if (self::$pdo == null) {
            self::$pdo = new PDO(
                'mysql:dbname=' . DATABASE .
                ';host=' . HOSTNAME .
                ';port:63343;',
                USERNAME,
                PASSWORD,
                array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
            );

            // Habilitar excepciones
            self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }

        return self::$pdo;
    }

    final protected function __clone()
    {
    }

    function _destructor()
    {
        self::$pdo = null;
    }
}

?>

Archivo de manejo de objetos OperacionesAlumnos.php

En este archivo se crearán todos los métodos que serán llamados por los archivos php y que se basa en la instancia de un objeto de la clase anterior

<?php
	require 'Database.php';
	class Usuarios
	{
		function __construct()
		{
			
		}
}
?>

En este archivo se irá incluyendo métodos que serán necesarios para el funcionamiento de la aplicación, ejecutados sentencias SQL

Archivos de obtención de datos

Cada uno de los archivos que llamarán a las funciones contenidas en el archivo anterior y que darán como respuesta el objeto JSON que será procesado en la aplicación

De los archivos php los métodos que se deben manejar son:

  • prepare(consulta): prepara una sentencia SQL para ser ejecutada por el método execute.
  • execute(): ejecuta una sentencia SQL.
  • array(): tiene como función la creación de un array, que en los caso del ejemplo o bien es impreso o bien es utilizado para sustituir los ? en las sentencias SQL.
  • fetchAll(): devuelve un array que contiene todas las filas del conjunto de resultados.
  • fetch(): obtiene la siguiente fila de un conjunto de resultados. Si solo es llamado una vez se obtendrá la primera.
  • $_SERVER[‘REQUEST_METHOD’]: devuelve el método por el que ha sido llamada la url.
  • isset(): comprueba si una variable existe.
  • print json_encode(): devuelve el valor introducido en formato JSON.

Volley

El uso de esta librería permite obtener objetos JSON o imágenes desde un servidor para poder tratarlas de forma sencilla en la aplicación. Para ello lo primero que hay que saber es que se basa en una clase Singleton la cual actual de “organizadora” de peticiones de forma que todas formen parte de una cola y esta se vaya autogestionando. Esta clase no es obligatoria pero si muy recomendable, ya que utiliza un método estático para obtener una instancia y utilizarla. La estructura sería la siguiente

public final class VolleySingleton {

    private static VolleySingleton singleton;
    private RequestQueue requestQueue;
    private static Context context;

    private VolleySingleton(Context context) {
        VolleySingleton.context = context;
        requestQueue = getRequestQueue();
    }

    public static synchronized VolleySingleton getInstance(Context context) {
        if (singleton == null) {
            singleton = new VolleySingleton(context.getApplicationContext());
        }
        return singleton;
    }

    public RequestQueue getRequestQueue() {
        if (requestQueue == null) {
            requestQueue = Volley.newRequestQueue(context.getApplicationContext());
        }
        return requestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

}

Una vez creada la instancia se realizará la petición correspondiente, pudiendo ser de tipos preconstruidos:

  • StringRequest: tan solo obtiene un string
  • JsonObjectRequest: obtiene un objeto de tipo JSON. En este ejemplo se utilizará una petición de este tipo
  • ImageRequest: obtiene una imagen de una URL
  • JSonArrayRequest: obtiene un array de JSON

Para poder realizar una petición se realiza mediante la obtención de la instancia y el método addToRequestQueue al cual se le pasan 5 parámetros:

  • método: si la petición se realiza mediante GET o POST
  • urs: ubicación del archivo php que llama a la función a ejecutar
  • jsonRequest: objeto que contiene un HashMap con par clave valor en el caso de utilizar un método POST. La clave será la palabra que se utilizará para recuperar la variable en el archivo php
  • listener:  escuchador que se activa cuando la petición obtiene respuesta. Esto se realiza mediante el método onResponse() el cual contiene la respuesta
  • errorListener: escuchador que se activa si el escuchador anterior no ha podido realizar la conexión y por tanto no obtener respuesta
bAnadir.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                JSONObject jsonObject = new JSONObject();
                VolleySingleton.getInstance(getApplicationContext())
                        .addToRequestQueue(new JsonObjectRequest(Request.Method.POST, 
                                "url", 
                                jsonObject,
                                new Response.Listener<JSONObject>() {
                                    @Override
                                    public void onResponse(JSONObject response) {
                
                                    }
                                }, 
                                new Response.ErrorListener() {
                                @Override
                                public void onErrorResponse(VolleyError error) {
                                    
                                }
                }));
            }
        });

Dentro de el método onResponse mediante el parámetro response se puede evaluar la respuesta y obtener los datos correspondientes

Añadir datos

Tal y como se ha contado antes, lo primero que se necesita es crear la función dentro del archivo de OperacionesAlumno.php. Para ello se obtendrá por parámetros necesarios a utilizar en la query, la cual será ejecutada y complementada mediante el método execute(array()). La sentencia SQL que se utilizará será la de INSERT INTO

INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);
public static function insertarAlumno($nombre,
            $apellidos,
            $direccion,
            $telefono)
        {

            $comando = 'INSERT INTO usuario (nombre, apellidos, direccion, telefono) VALUES (?,?,?,?)';


            $sentencia = Database::getInstance()->getDb()->prepare($comando);
            return $sentencia->execute(array(
                    $nombre,
                    $apellidos,
                    $direccion,
                    $telefono));
        }

Adicionalmente se creará un fichero php (el que será llamado desde la aplicación móvil) donde se llama a la función creada así como la evaluación de la misma

<?php

require 'AlumnosOperaciones.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {

    $body = json_decode(file_get_contents("php://input"), true);
   
    $retorno = AlumnosOperaciones::insertarAlumno(
        $body['nombre'],
        $body['apellidos'],
        $body['direccion'],
        $body['telefono']);

    if ($retorno) {
        print json_encode(
            array(
                'estado' => '1',
                'mensaje' => 'El usuario creado con exito'));
    } 
    else {
        print json_encode(
            array(
                'estado' => '2',
                'mensaje' => 'El usuario no se ha podido crear')
        );  
    }
}
?>

Una vez creada toda la estructura de ficheros tan solo queda realizar la petición mediante la clase de Volley. En este caso al ser método POST se crea un objeto HashMap donde la clave de cada una de las variables se recuperarán en el fichero php por lo que los nombres deben ser los mismos

bAnadir.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                HashMap mapaDatos = new HashMap();
                mapaDatos.put("nombre", "NombreCapturado");
                mapaDatos.put("apellidos", "ApellidoCapturado");
                mapaDatos.put("direccion", "DireccionCapturado");
                mapaDatos.put("telefono", 1111);
                JSONObject jsonObject = new JSONObject(mapaDatos);

                String url = "http://X.X.X.X/develop/insertar_alumno.php";


                VolleySingleton.getInstance(getApplicationContext()).
                        addToRequestQueue(
                                new JsonObjectRequest(Request.Method.POST,
                                        url,
                                        jsonObject,
                                        new Response.Listener<JSONObject>() {
                                            @Override
                                            public void onResponse(JSONObject response) {
                                                // Procesar la respuesta Json
                                                try {
                                                    Log.v("test", response.getString("mensaje"));
                                                } catch (JSONException e) {
                                                    e.printStackTrace();
                                                }
                                            }
                                        },
                                        new Response.ErrorListener() {
                                            @Override
                                            public void onErrorResponse(VolleyError error) {
                                                Log.d("Volley", "Error Volley: " + error.getMessage());
                                            }
                                        }) {
                                    @Override
                                    public Map<String, String> getHeaders() {
                                        Map<String, String> headers = new HashMap<String, String>();
                                        headers.put("Content-Type", "application/json; charset=utf-8");
                                        headers.put("Accept", "application/json");
                                        return headers;
                                    }

                                    @Override
                                    public String getBodyContentType() {
                                        return "application/json; charset=utf-8" + getParamsEncoding();
                                    }
                                }
                        );


            }
        });

De esta forma se agregaría el usuario y se devolvería el mensaje cuyo código es 1 al llevarse a cabo la ejecución de la query. Adicionalmente también se podría realizar la operación mediante un método GET, con el inconveniente que la información sería vista en la url

String urlGet = "http://X.X.X.X/develop/insertar_alumno.php?nombre=Ejemplo&apellido=apellido&direccion=direccion&telefono=1111";

En este caso se evalúa en el archivo php mediante método GET

require 'OperacionesAlumno.php';

if ($_SERVER['REQUEST_METHOD'] == 'GET') {

    if (isset($_GET['nombre'])) {

        // Obtener parámetro idMeta
        $parametro1 = $_GET['nombre'];
        $parametro2 = $_GET['apellidos'];
        $parametro3 = $_GET['direccion'];
        $parametro4 = $_GET['telefono'];

        $retorno = OperacionesAlumno::insertarAlumno($parametro1,$parametro2,$parametro3,$parametro4);


        if ($retorno) {

            print json_encode(
                array(
                    'estado' => '1',
                    'mensaje' => 'Usuario agregado con éxito'
                )
            );
        } else {
            // Enviar respuesta de error general
            print json_encode(
                array(
                    'estado' => '2',
                    'mensaje' => 'Usuario no agregado'
                )
            );
        }

    } else {
        // Enviar respuesta de error
        print json_encode(
            array(
                'estado' => '3',
                'mensaje' => 'Se necesita un identificador'
            )
        );
    }
}

?>

Borrar datos

Igual que en el caso anterior, se necesita agregar en el archivo AlumnosOperaciones.php la función de borrarAlumno mediante la sentencia SQL, crear el fichero php que llame a dicha función pasándole los parámetros necesarios. Para poder ejecutar el borrado se utiliza la sentencia DELETE FROM

DELETE FROM table_name WHERE condition;
public static function borrarAlumno($nombre)
        {

            $comando = 'DELETE FROM usuario WHERE nombre=?';

            $sentencia = Database::getInstance()->getDb()->prepare($comando);
            return $sentencia->execute(array(
                    $nombre));
        }
<?php
	
	require 'AlumnosOperaciones.php';

	if ($_SERVER['REQUEST_METHOD']=='GET') {
		$parametro1 = $_GET['nombre'];

		$retorno = AlumnosOperaciones::borrarAlumno($parametro1); 
		if ($retorno) {
			# code..
			print json_encode(
                array(
                    'estado' => '1',
                    'mensaje' => 'Alumno borrado con exito'
                )
            );
		} else {

			print json_encode(
                array(
                    'estado' => '2',
                    'mensaje' => 'Alumno no borrado'
                )
            );
		}
		

	} else {
		# code...
		print json_encode(
                array(
                    'estado' => '3',
                    'mensaje' => 'No se obtuvo el parametro'
                )
            );
	}
	
?>

Del mismo modo también se podría realizar mediante el método POST igual que se explicó en el ejemplo anterior

Además de poder hacer este tipo de operaciones también existe la posibilidad de hacer animaciones. Por ejemplo si antes de borrar se quiere comprobar si el usuario existe habría que incluir la función de devolver el usuario, esta vez retornando la fila obtenida si hay datos

public static function getAlumno($nombre){
            $consulta = "SELECT * FROM usuario WHERE nombre = ?";

            try {
            $comando = Database::getInstance()->getDb()->prepare($consulta);
            $comando->execute(array($nombre));
            $row = $comando->fetch(PDO::FETCH_ASSOC);
            return $row;

            } catch (PDOException $e) {
             return -1;
            }
        }

Una vez se tiene esta función se puede llamar de otra forma desde el archivo php que borra el alumno

<?php
	
	require 'AlumnosOperaciones.php';

	if ($_SERVER['REQUEST_METHOD'] == 'GET') {
		if(isset($_GET['nombre'])) {
			# code...
			$parametro = $_GET['nombre'];
			$consulta = AlumnosOperaciones::getAlumno($parametro);
				if ($consulta) {

					$consulta2 = AlumnosOperaciones::borrarAlumno($parametro);
					$consulta2;
					print json_encode(
	            	array(
	                'estado' => '1',
	                'mensaje' => 'El usuario existía y ha sido borrado'));
	    		} 
	    		else{

	    			print json_encode(
            		array(
	                'estado' => '2',
	                'mensaje' => 'El usuario no existe'));
    				} 
			}		
		}
	 else {
		print json_encode(
            		array(
	                'estado' => '3',
	                'mensaje' => 'falta get'));
    				} 
?>

Del mismo modo también se podrían encadenar diversas sentencias. Por ejemplo borrar aquel alumno cuya id es la de un nombre introducido

public static function borrarAlumnoCompleto($nombre)
        {

            $comando = 'DELETE FROM usuario WHERE id = (SELECT id WHERE nombre =?)';

            $sentencia = Database::getInstance()->getDb()->prepare($comando);
            return $sentencia->execute(array(
                    $nombre));
        }

Actualizar datos

Para actualizar datos se utiliza la sentaría SQL UPDATE cuya sintaxis es la siguiente

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

Tal y como se ha realizado antes, se agregará la función que actualice los datos en el fichero AlumnosOperaciones.php, se agregará el fichero que ejecute dicha función y se llamará al fichero mediante la librería volley

public static function actualizarAlumno($nombre,$nombreN)
        {

            $comando = 'UPDATE usuario SET nombre = ? WHERE nombre=?';

            $sentencia = Database::getInstance()->getDb()->prepare($comando);
            return $sentencia->execute(array(
                    $nombreN,$nombre));
        }

Esta función es llamada desde el fichero actualizar_alumo mediante método GET, pero también puede ser llamado mediante método POST modificando el contenido de la url y la llamada de la clase volley

<?php
	
	require 'AlumnosOperaciones.php';

	if ($_SERVER['REQUEST_METHOD'] == 'GET') {
		if(isset($_GET['nombre'])) {

			# code...
			$parametro = $_GET['nombre'];
			$parametro2 = $_GET['nombreNuevo'];

			$consulta = AlumnosOperaciones::getAlumno($parametro);
				if ($consulta) {

					$consulta2 = AlumnosOperaciones::actualizarAlumno($parametro,$parametro2);
					$consulta2;
					print json_encode(
	            	array(
	                'estado' => '1',
	                'mensaje' => 'El usuario ya existe'));
	    		} 
	    		else{

	    			print json_encode(
            		array(
	                'estado' => '2',
	                'mensaje' => 'El usuario no existe'));
    				} 
			}
			
		}
	 else {
		print json_encode(
            		array(
	                'estado' => '3',
	                'mensaje' => 'falta get'));
    				} 
?>

Por último se realiza la ejecución del php desde la aplicación

bActualizar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String urlGet = "http://192.168.0.161/develop/actualizar_alumno.php?nombre=NombreCapturado&nombreNuevo=NombreNuevo";

                VolleySingleton.getInstance(getApplicationContext()).
                        addToRequestQueue(
                                new JsonObjectRequest(Request.Method.GET,
                                        urlGet,
                                        null,
                                        new Response.Listener<JSONObject>() {
                                            @Override
                                            public void onResponse(JSONObject response) {
                                                try {
                                                    Log.v("test", response.getString("mensaje"));
                                                } catch (JSONException e) {
                                                    e.printStackTrace();
                                                }
                                            }
                                        },
                                        new Response.ErrorListener() {
                                            @Override
                                            public void onErrorResponse(VolleyError error) {
                                                Log.d("Volley", "Error Volley: " + error.getMessage());
                                            }
                                        }) {
                                    @Override
                                    public Map<String, String> getHeaders() {
                                        Map<String, String> headers = new HashMap<String, String>();
                                        headers.put("Content-Type", "application/json; charset=utf-8");
                                        headers.put("Accept", "application/json");
                                        return headers;
                                    }

                                    @Override
                                    public String getBodyContentType() {
                                        return "application/json; charset=utf-8" + getParamsEncoding();
                                    }
                                }
                        );
            }
        });

Obtener datos

Para obtener datos se utiliza la sentencia SQL SELECT cuya sintaxis es:

SELECT column1, column2, ...
FROM table_name;

Tal y como se ha realizado antes, se agregará la función que actualice los datos en el fichero AlumnosOperaciones.php, se agregará el fichero que ejecute dicha función y se llamará al fichero mediante la librería volley.

public static function getAlumnos(){
            $comando = 'SELECT * FROM usuario';
            $sentencia = Database::getInstance()->getDb()->prepare($comando);
            $sentencia ->execute();
            return $sentencia->fetchAll(PDO::FETCH_ASSOC);
        }

 

<?php

	require 'AlumnosOperaciones.php';

	if ($_SERVER['REQUEST_METHOD'] == 'GET'){

		$consulta = AlumnosOperaciones::getAlumnos();
		if($consulta){
			$datos['estado']=1;
			$datos['alumnos']=$consulta;

			print json_encode($datos);
		}
		else{
			# code...
			print json_encode(array(
                "estado" => 2,
                "mensaje" => "Ha ocurrido un error"
            ));
		}

	}	

?>

En este caso la respuesta es una variable donde se ha puesto un estado y la consulta con todos los alumnos. Esta consulta podrá ser retornada mediante un JSONArray y convertirlo en el objeto correspondiente. Para ello se utiliza un objeto de tipo GSON por lo que hay que importar la librería lo primero

compile 'com.google.code.gson:gson:2.2.4'
bConsultar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String urlGet = "http://192.168.0.161/develop/obtener_alumnos.php";

                VolleySingleton.getInstance(getApplicationContext()).
                        addToRequestQueue(
                                new JsonObjectRequest(Request.Method.GET,
                                        urlGet,
                                        null,
                                        new Response.Listener<JSONObject>() {
                                            @Override
                                            public void onResponse(JSONObject response) {
                                                try {
                                                    JSONArray jsonArray = response.getJSONArray("alumnos");
                                                    Gson gson = new Gson();
                                                    Alumno[] users = gson.fromJson(jsonArray.toString(), Alumno[].class);
                                                    Log.v("test",users[0].getNombre());

                                                } catch (JSONException e) {
                                                    e.printStackTrace();
                                                }
                                            }
                                        },
                                        new Response.ErrorListener() {
                                            @Override
                                            public void onErrorResponse(VolleyError error) {
                                                Log.d("Volley", "Error Volley: " + error.getMessage());
                                            }
                                        }) {
                                    @Override
                                    public Map<String, String> getHeaders() {
                                        Map<String, String> headers = new HashMap<String, String>();
                                        headers.put("Content-Type", "application/json; charset=utf-8");
                                        headers.put("Accept", "application/json");
                                        return headers;
                                    }

                                    @Override
                                    public String getBodyContentType() {
                                        return "application/json; charset=utf-8" + getParamsEncoding();
                                    }
                                }
                        );
            }
        });

Tras recuperar el JSONArray y utilizando un objeto de tipo Gson se convierte el resultado a un array del tipo concreto, utilizando la clase creada (donde los campos tienen que estar en el mismo orden que el JSON) y el método toString del JSONArray

JSONArray jsonArray = response.getJSONArray("alumnos");
Gson gson = new Gson();
Alumno[] users = gson.fromJson(jsonArray.toString(), Alumno[].class);