• No se han encontrado resultados

Crear Un WebService Para Android Con Mysql, Php y Json

N/A
N/A
Protected

Academic year: 2021

Share "Crear Un WebService Para Android Con Mysql, Php y Json"

Copied!
46
0
0

Texto completo

(1)

Crear Un Web Service Para Android Con Mysql, Php y Json

¿Deseas conectar una aplicación Android a Mysql?

¿Has intentado crear un web service con Php para la comunicación de datos de tu aplicativo web con tu aplicativo móvil android, pero aún no

comprendes bien cómo hacerlo?

Pues bien, en este artículo te mostraré algunas ideas sobre la creación de una aplicación android que consuma los datos de un servidor

externo a través de Php, Mysql y Json.

Para ello he creado una aplicación llamada “I Wish”, la cual permite a nuestros usuarios guardar una lista de deseos y metas que tienen en su

vida. Con este ejemplo podrás ver cómo implementar la inserción, edición, eliminación y consulta de datos a través de un Web Service.

El código de la aplicación puedes obtenerlo presionando el siguiente botón:

Descargar Código

1. ¿Qué Es Un Web Service?

Un Web Service o Servicio Web es un aplicativo que facilita la interoperabilidad entre varios sistemas independientemente del del lenguaje de

programación o plataforma en que fueron desarrollados. Este debe tener una interfaz basada en un formato estándar entendible por las

maquinas como lo es XML o JSON.

Por ejemplo…

Facebook es un aplicativo web construido con una determinada arquitectura y lenguajes de programación basados en el protocolo HTTP. Sin

embargo podemos usar esta red social en nuestro dispositivo Android.

¿Cómo es posible esto, si la aplicación Android está construida con lenguaje Java?

A través de un Web Service construido para gestionar todas aquellas operaciones sobre una base de datos alojada en los servidores de

Facebook. Quiere decir que ambos aplicativos usan como puente la web para acceder a un solo repositorio de datos.

Como ves, un Web Service se crea con funcionalidades que permitan obtener datos actualizados en tiempo real. El hecho de que sea dinámico

incorpora el uso de un lenguaje web para la gestión HTTP que en este caso será Php.

2. Requerimientos De La Aplicación

Como leíste al inicio, la aplicación I Wish gestiona las metas y sueños de los usuarios permitiéndoles tener un registro completo. Básicamente el

alcance del proyecto se resumen en:

Como usuario de I Wish, deseo mantener los datos de todas mis metas y sueños (se refiere al CRUD).

Como usuario de I Wish, deseo ver el detalle de cada meta.

(2)

Las categorías posibles son: Salud, Finanzas, Profesional y Espiritual.

Estos requerimientos no son nada del otro mundo. Básicamente estas ante una situación de listas y detalles. Algo que ya has visto en artículos

pasados con gran frecuencia.

El meollo del asunto se encuentra en el Web Service que debes crear con Php y Mysql para el mantenimiento de los datos. Esta vez no

usaremos caching para el soporte de los datos locales como lo hicimos al crear el lector Rss. Nos enfocaremos en como usar Volley para

realizar las peticiones en el localhost.

3. Wireframing De La Aplicación

A primera vista I Wish es una aplicación que se basa en la funcionalidad básica de un crud. Tendremos una lista de los elementos que existen,

podremos ver el detalle de cada uno, modificar su contenido e incluso borrarlos.

Teniendo en cuenta este razonamiento, puedes imaginar la aplicación en primera instancia de la siguiente forma:

Basado en el boceto que acabas de crear ya puedes identificar que la cantidad de actividades, fragmentos, diálogos y formularios que

necesitas. Así que veamos la siguiente lista de materiales a crear:

Actividad principal con un fragmento de lista.

Layout personalizado para items.

Actividad con fragmento de detalle.

Actividad con fragmento de formulario para inserción.

Actividad con fragmento de formulario para edición.

En este tutorial usaremos actividades basadas en fragmentos, ya que muchos lectores han preguntado cómo hacer para comunicar fragmentos

con actividades y viceversa.

4. Crear UI Para La Aplicación Android

4.1 Diseñar Actividad Principal Con Fragmento Tipo Lista

Después de haber creado tú proyecto en Android Studio vas a crear una actividad principal que contengan un fragmento con una lista. Debido

a que vamos a añadir los fragmentos programáticamente no es necesario enfocarnos tanto en los layouts de las actividades. Incluso puedes

usar un solo layout para todas las actividades.

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"

(3)

    android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical"     tools:context=".MainActivity" />

El fragmento pudiese heredar de

ListFragment

pero debido a que vamos a usar un

RecyclerView

, el diseño es diferente. La idea es añadir el

recycler para recubrir toda la actividad y además añadir un Floating Action Button en la parte inferior derecha con el fin de que el usuario

añada nuevas metas.

Para añadir el FAB (Floating Action Button) podemos hacer uso de una de las siguientes librerías que existen en la web:

Floating Action Button Library For Android

FloatingActionButton de makovkastar

Future Simple

Incluso podrías basarte en el ejemplo del sitio de android devepers llamado FloatingActionButtonBasic. Todo depende de ti. Cada librería trae

la explicación de su implementación, así que no hay excusas.

Por mi parte, en este ejemplo usaré la librería de makovkastar, ya que necesitamos fabs muy simples. Para ello incluimos la siguiente

dependencia de Gradle:

compile 'com.melnykov:floatingactionbutton:1.3.0'

Veamos como queda el layout del fragmento principal:

fragment_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:fab="http://schemas.android.com/apk/res‐auto"     android:id="@+id/fragment_main"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">     <android.support.v7.widget.RecyclerView         android:id="@+id/reciclador"         android:layout_width="match_parent"         android:layout_height="match_parent"         android:padding="3dp"         android:scrollbars="vertical" />     <com.melnykov.fab.FloatingActionButton         android:id="@+id/fab"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_alignParentBottom="true"         android:layout_alignParentRight="true"         android:layout_gravity="bottom|right"         android:layout_margin="16dp"         android:src="@mipmap/ic_add"         fab:fab_colorNormal="@color/accent"         fab:fab_colorPressed="@color/primary"         fab:fab_colorRipple="@color/ripple" /> </RelativeLayout>

Se usa una etiqueta

<com.melnykov.fab.FloatingActionButton>

para implementar el FAB. Simplemente se ubica en la parte inferior derecha y le

añadimos los colores correspondientes a su interacción.

Donde

colorNormal

es el color que tiene en estado natural;

colorPressed

es aquel que se proyecta cuando lo presionamos rapidamente y

colorRipple

se evidencia cuando mantienes un click largo sobre él.

Otro aspecto a tener en cuenta es que los mipmaps o drawables que uses para el icono de un FAB debe tener dimensiones de 24dp, para una

buena experiencia de usuario:

(4)

El patrón anterior muestra un FAB grande para representar la inserción con unas dimensiones reglamentarias de 56dpx56dp. El icono que lleva

debe mantenerse en 24dpx24dp.

También podemos tener un FAB mini con dimensiones de 40dpx40dp, donde el icono se mantiene sobre 24dpx24dp.

4.2 Diseñar Actividad De Detalle Con Fragmento Personalizado

Acudiendo a los estilos de layouts en Material Design, dividiremos el fragmento de detalle en dos pasos. El primero será una ImageView alusivo

a la categoría de la meta y el segundo será una hoja para sus datos completos. Adicionalmente añadiremos un Float Button Action para la

edición de la meta.

fragment_detail.xml

<?xml version="1.0" encoding="utf‐8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:fab="http://schemas.android.com/apk/res‐auto"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">          <!‐‐ Parte superior ‐‐>     <RelativeLayout         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="50">         <ImageView       android:id="@+id/cabecera"       android:layout_width="match_parent"       android:layout_height="match_parent"       android:layout_weight="30"       android:layout_marginBottom="28dp" />

(5)

        <com.melnykov.fab.FloatingActionButton       android:id="@+id/fab"       android:layout_width="40dp"       android:layout_height="40dp"       android:layout_alignParentBottom="true"       android:layout_alignParentLeft="true"       android:layout_gravity="bottom|right"       android:src="@mipmap/ic_edit"       fab:fab_colorNormal="@color/colorNormalMini"       fab:fab_colorPressed="@color/colorPressedMini"       fab:fab_colorRipple="@color/colorRippleMini"       android:layout_marginLeft="16dp"       fab:fab_type="mini"       android:layout_marginBottom="8dp"/>         <TextView       android:id="@+id/titulo"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:text="Titulo"       android:textAppearance="?android:attr/textAppearanceLarge"       android:layout_marginBottom="48dp"       android:layout_toRightOf="@+id/fab"       android:layout_alignParentBottom="true"       android:layout_marginLeft="16dp"       android:textColor="@android:color/white" />     </RelativeLayout>     <!‐‐ Datos de la meta ‐‐>     <RelativeLayout         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="70"         android:paddingBottom="@dimen/activity_vertical_margin"         android:paddingLeft="@dimen/activity_horizontal_margin"         android:paddingRight="@dimen/activity_horizontal_margin"         android:paddingTop="@dimen/activity_vertical_margin">         <TextView       android:id="@+id/categoria"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_below="@+id/categoria_label"       android:text="Categoría"       android:textAppearance="?android:attr/textAppearanceSmall"       android:layout_marginBottom="16dp" />         <TextView       android:id="@+id/fecha"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_alignParentLeft="true"       android:layout_alignParentStart="true"       android:layout_below="@+id/fecha_label"       android:text="Fecha"       android:textAppearance="?android:attr/textAppearanceSmall"       android:layout_marginBottom="16dp" />         <TextView       android:id="@+id/prioridad"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_below="@+id/prioridad_label"       android:text="Prioridad"       android:textAppearance="?android:attr/textAppearanceSmall" />         <TextView       android:id="@+id/descripcion"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_below="@+id/descripcion_label"       android:text="Descripción"       android:textAppearance="?android:attr/textAppearanceSmall"       android:layout_marginBottom="16dp" />

(6)

        <TextView       android:id="@+id/descripcion_label"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:text="Descripción"       android:textAppearance="?android:attr/textAppearanceSmall"       android:textColor="@android:color/black" />         <TextView       android:id="@+id/fecha_label"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_below="@+id/descripcion"       android:text="Fecha Límite"       android:textAppearance="?android:attr/textAppearanceSmall"       android:textColor="@android:color/black" />         <TextView       android:id="@+id/categoria_label"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_below="@+id/fecha"       android:text="Categoría"       android:textAppearance="?android:attr/textAppearanceSmall"       android:textColor="@android:color/black" />         <TextView       android:id="@+id/prioridad_label"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_below="@+id/categoria"       android:text="Prioridad"       android:textAppearance="?android:attr/textAppearanceSmall"       android:textColor="@android:color/black" />     </RelativeLayout> </LinearLayout>

El FAB debe usar el atributo fab:fab_type=”mini” para usar el botón mini.

En este caso se usó como nodo un LinearLayout con dos RelativeLayout dentro. Esto nos permite dividir por pesos (weight) la ocupación de

espacio entre ambos layouts y así mantener una proporción.

(7)

4.3 Diseñar Actividad Con Formulario

La inserción y edición requiere de la proyección de un formulario que contenga los controles necesarios para que el usuario especifique la

información personalizada que desea almacenar en la base de datos. Para ello debes crear un layout con los datos que viste en los

requerimientos de la aplicación con las respectivos views para obtener la información.

Por ejemplo…

El titulo de cada meta recibe texto escrito desde el input del dispositivo, por lo que sabemos que el

EditText

es la solución para este caso. La

descripción es igual, necesita un campo de texto. La fecha limite puede ser obtenida a través de un

DatePicker

y para la categoría que tiene un

dominio de varias opciones, puedes usar un

Spinner

.

Veamos:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     android:paddingBottom="@dimen/activity_vertical_margin"     tools:context="com.herprogramacion.iwish.ui.fragmentos.UpdateFragment">     <!‐‐ Titulo‐‐>     <EditText         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:id="@+id/titulo_input"         android:layout_alignParentTop="false"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true"         android:hint="Título"         android:minLines="1"         android:maxLines="1"         android:maxLength="55"         android:phoneNumber="false"         android:singleLine="true"         android:paddingTop="16dp"         android:paddingBottom="16dp" />     <!‐‐ Descripción ‐‐>     <EditText         android:layout_width="match_parent"

(8)

        android:layout_height="wrap_content"         android:id="@+id/descripcion_input"         android:layout_below="@+id/titulo_input"         android:layout_centerHorizontal="true"         android:hint="Descripción"         android:maxLength="128"         android:nestedScrollingEnabled="true"         android:paddingTop="16dp"         android:paddingBottom="16dp" />     <!‐‐ Etiqueta Fecha ‐‐>     <TextView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:textAppearance="?android:attr/textAppearanceSmall"         android:text="Fecha"         android:id="@+id/fecha_text"         android:layout_below="@+id/descripcion_input"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true"         android:paddingTop="16dp"         android:textColor="@android:color/black" />     <!‐‐ Fecha ‐‐>     <TextView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:textAppearance="?android:attr/textAppearanceSmall"         android:text="2015/05/17"         android:id="@+id/fecha_ejemplo_text"         android:layout_below="@+id/fecha_text" />     <!‐‐ Categoría ‐‐>     <Spinner         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:id="@+id/categoria_spinner"         android:entries="@array/entradas_categoria"         android:layout_below="@+id/categoria_texto" />     <!‐‐ Etiqueta Categoría ‐‐>     <TextView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:textAppearance="?android:attr/textAppearanceSmall"         android:text="Categoría"         android:id="@+id/categoria_texto"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true"         android:layout_below="@+id/fecha_ejemplo_text"         android:paddingTop="16dp"         android:textColor="@android:color/black" />     <!‐‐ Etiqueta Prioridad ‐‐>     <TextView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:textAppearance="?android:attr/textAppearanceSmall"         android:text="Prioridad"         android:id="@+id/prioridad_text"         android:layout_below="@+id/categoria_spinner"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true"         android:textColor="@android:color/black"         android:paddingTop="16dp" />     <!‐‐ Prioridad ‐‐>     <Spinner         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:id="@+id/prioridad_spinner"         android:layout_below="@+id/prioridad_text"         android:entries="@array/entradas_prioridad" /> </RelativeLayout>

(9)

4.4 Diseñar Layout Personalizado De Los Items

La organización de los atributos de cada meta dentro de los ítems de la lista debe ser un resumen de sus características principales. Puedes

dejar la descripción solo para la actividad del detalle y eliminarlo de la presentación en la lista.

item_list.xml

<?xml version="1.0" encoding="utf‐8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     android:paddingBottom="@dimen/activity_vertical_margin">     <!‐‐ Titulo ‐‐>     <TextView         android:id="@+id/titulo"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="Titulo"         android:textAppearance="?android:attr/textAppearanceMedium"         android:layout_below="@+id/fecha"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true"         android:layout_marginTop="16dp" />     <!‐‐ Categoría ‐‐>     <TextView         android:id="@+id/categoria"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_alignParentEnd="true"         android:layout_alignParentRight="true"         android:layout_alignParentTop="true"         android:text="Categoría"         android:textAppearance="?android:attr/textAppearanceMedium"         android:textColor="@color/accent" />     <!‐‐ Fecha ‐‐>     <TextView         android:id="@+id/fecha"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="Fecha"         android:textAppearance="?android:attr/textAppearanceMedium"         android:layout_alignParentTop="true"         android:layout_alignParentLeft="false"         android:layout_alignParentStart="false"         android:textColor="@android:color/black"         android:layout_toRightOf="@+id/imageView" />     <!‐‐ Prioridad ‐‐>     <TextView         android:id="@+id/prioridad"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="Prioridad"         android:textAppearance="?android:attr/textAppearanceSmall"         android:layout_marginTop="8dp"         android:layout_below="@+id/titulo"         android:textStyle="italic" />     <!‐‐ Icono para la fecha ‐‐>     <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:id="@+id/imageView"         android:layout_alignParentTop="true"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true"         android:src="@mipmap/ic_calendar"         android:layout_marginRight="3dp" /> </RelativeLayout>

(10)

El anterior diseño se vería de la siguiente forma:

5. Codificación Del Web Service En Php

Antes de crear la aplicación Android debes desarrollar primero tu Web Service con cualquiera de los estándares que te interesen. El alcance de

este tutorial no abarca el uso de restricciones REST, SOAP, RPC o sus parecidos. Simplemente verás cómo crear las implementaciones Php

necesarias para realizar operaciones sobre una base de datos en Mysql a través de peticiones GET y POST.

Si deseas aprender a crear Web Services con diseño REST, entonces te recomiendo este excelente curso online con Laravel.

Para desarrollar este aplicativo usaré el entorno de desarrollo XAMPP, el cual provee automáticamente una configuración de un servidor

Apache local, el intérprete de Php y el gestor Mysql.

Sin embargo tu puedes usar las herramientas que desees para gestionar pruebas locales. Lo importante es que puedas correr Mysql e

interpretar scripts de Php.

5.1 Diseño E Implementación De La Base De Datos

Diseñar base de datos: Si ya lo has notado, la base de datos de la aplicación I Wish solo tiene una entidad que representa a los registros de las

metas. Esto reduce ampliamente el diseño de bases de datos en el problema. No obstante, si tu proyecto es mas complicado, asegúrate de

tener una buena metodología de diseño de bases de datos antes de crear el web service,

Meta debe tener los atributos que hemos venido viendo más una llave primaria que mantenga la integridad de los datos. Observa el siguiente

minidiagrama entidad-relación:

Crear base de datos: Para implementar la base de datos lo primero que debes hacer es crear una nueva base de datos en la aplicación

(11)

Ahora crea la tabla meta para que contenga seis columnas en su estructura y además usa el formato

UTF‐8

para soportar acentos. Puedes

hacerlo a través del editor de phpMyAdmin o con el siguiente comando

CREATE

:

CREATE TABLE IF NOT EXISTS meta(

    idMeta int(3) PRIMARY KEY AUTO_INCREMENT,   titulo varchar(56) NOT NULL,

  descripcion varchar(128) NOT NULL,

  prioridad enum('Alta','Media','Baja','') NULL DEFAULT 'Alta',   fechaLim date NOT NULL,

  categoria enum('Salud','Finanzas','Espiritual','Profesional','Material') NOT NULL DEFAULT 'Finanzas'

)

Luego añade 5 registros de ejemplo en la tabla que permitan probar el funcionamiento en la aplicación android más adelante.

INSERT INTO `i_wish`.`meta` (`idMeta`, `titulo`, `descripcion`, `prioridad`, `fechaLim`, `categoria`)

VALUES (NULL, 'Comprar Mazda 6', 'Deseo adquirir un auto mazda 6 para mi desplazamiento en la ciudad. Debo investigar cómo conseguir mas fuentes de ingresos', 'Media', '2015‐11‐20', 'Material'), (NULL, 'Obtener mi título de ingeniería de sistemas', 'Ya solo faltan 2 semestres para terminar mi carrera de ingeniería. Debo prepararme al máximo para desarrollar mi tesis de grado', 'Alta', '2016‐06‐17', 'Profesional'), (NULL, 'Conquistar a Natasha', 'Natasha es la mujer de vida. Tengo que decírselo antes de que acabe el semestre', 'Alta', '2015‐05‐25', 'Espiritual'), (NULL, 'Tener un peso de 70kg', 'Actualmente peso 92kg y estoy en sobrepeso. Sin embargo voy a seguir una rutina de ejercicios y un régimen alimenticio', 'Baja', '2016‐05‐13', 'Salud'), (NULL, 'Incrementar un 30% mis ingresos', 'Conseguiré una fuente de ingresos alternativa que representen un 30% de los ingresos que recibo actualmente.', 'Media', '2015‐10‐13', 'Finanzas');

5.2 Crear Código Php Para Consumir Datos

En primera instancia crea una conexión a la base de datos Mysql con la interfaz que mas se acomode a tus necesidades. En mi caso voy a crear

una conexión con PDO, la cual me permite proteger los datos de inyecciones sql.

Luego de eso crea una clase que mapee la estructura de la tabla

meta

. El objetivo de ello es proveerla de comportamientos de inserción,

actualización, eliminación y consulta a través de la conexión a la base de datos.

Finalmente implementaré scripts Php para gestionar las peticiones que lanzan los clientes. La idea es parsear los datos en formato Json para

que nuestra aplicación Android interprete los resultados de forma legible.

Paso #1: Crear conexión a la base de datos con PDO

El uso de PDO depende del enfoque que tengan tus proyectos, puedes crear una clase que represente la conexión hacia la base de datos o

simplemente crear una nueva conexión en cada script de Php que tengas.

Para este caso te compartiré un patrón singleton de PDO para limitar el número de aperturas a la base de datos en una sola. Con ello podremos

disponer de un solo objeto a través de todo el proyecto.

No obstante hay patrones de diseño muy interesantes que puedes consultar en la web. Por ejemplo el repositorio del usuario indieteq en

github. Él se enfoca en la implementación del CRUD de una forma muy sencilla y orientada a objetos.

Veamos el resultado del patrón singleton:

(12)

<?php /**  * Clase que envuelve una instancia de la clase PDO  * para el manejo de la base de datos  */ require_once 'mysql_login.php'; class Database {     /**      * Única instancia de la clase      */

    private static $db = null;     /**

     * Instancia de PDO      */

    private static $pdo;

    final private function __construct()     {

        try {

      // Crear nueva conexión PDO

      self::getDb();

        } catch (PDOException $e) {       // Manejo de excepciones         }     }     /**      * Retorna en la única instancia de la clase      * @return Database|null      */

    public static function getInstance()     {

        if (self::$db === null) {       self::$db = new self();         }         return self::$db;     }     /**      * Crear una nueva conexión PDO basada      * en los datos de conexión      * @return PDO Objeto PDO      */

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

    /**

     * Evita la clonación del objeto      */

    final protected function __clone()     {

(13)

    function _destructor()     {

        self::$pdo = null;     }

} ?>

Ten en cuenta que la conexión se abre con 4 cadenas descriptivas del entorno que estás usando declaradas en el archivo mysql_login.php. Con

ello me refiero al nombre del host, el nombre de la base de datos, el usuario con que deseas ingresar y su respectiva contraseña.

Por el momento usaremos el localhost debido a las pruebas que estamos haciendo. El usuario ya depende de ti, en mi caso uso el usuario por

defecto

"root"

y sin contraseña alguna.

mysql_login.php

<?php /**  * Provee las constantes para conectarse a la base de datos  * Mysql.  */

define("HOSTNAME", "localhost");// Nombre del host

define("DATABASE", "i_wish"); // Nombre de la base de datos

define("USERNAME", "root"); // Nombre del usuario

define("PASSWORD", ""); // Nombre de la constraseña

?>

Adicionalmente debes añadir al cuarto parámetro del constructor de PDO la indicación

SET NAMES UTF‐8

para el servidor. Esto permite que los

datos de la base de datos vengan codificados en este formato para evitar problemas de compatibilidad.

Paso #4: Crear clase para las metas

En este paso vas a modelar en una clase a la tabla

"meta"

de tal forma que aplique el CRUD sobre los datos a través de la clase

Database

. En

esencia necesitas un método para obtener todos los registros, uno para la inserción, otro para eliminación, también para la actualización y un

método que permita obtener del detalle de un solo registro.

Meta.php

<?php /**  * Representa el la estructura de las metas  * almacenadas en la base de datos  */ require 'Database.php'; class Meta {     function __construct()     {     }     /**      * Retorna en la fila especificada de la tabla 'meta'      *      * @param $idMeta Identificador del registro      * @return array Datos del registro      */

    public static function getAll()     {

        $consulta = "SELECT * FROM meta";         try {

      // Preparar sentencia

      $comando = Database::getInstance()‐>getDb()‐>prepare($consulta);       // Ejecutar sentencia preparada

      $comando‐>execute();

      return $comando‐>fetchAll(PDO::FETCH_ASSOC);         } catch (PDOException $e) {

(14)

      return false;         }     }     /**      * Obtiene los campos de una meta con un identificador      * determinado      *      * @param $idMeta Identificador de la meta      * @return mixed      */

    public static function getById($idMeta)     {         // Consulta de la meta         $consulta = "SELECT idMeta,       titulo,        descripcion,        prioridad,        fechaLim,        categoria        FROM meta        WHERE idMeta = ?";         try {       // Preparar sentencia

      $comando = Database::getInstance()‐>getDb()‐>prepare($consulta);       // Ejecutar sentencia preparada

      $comando‐>execute(array($idMeta));       // Capturar primera fila del resultado

      $row = $comando‐>fetch(PDO::FETCH_ASSOC);       return $row;

        } catch (PDOException $e) {

      // Aquí puedes clasificar el error dependiendo de la excepción       // para presentarlo en la respuesta Json       return ‐1;         }     }     /**      * Actualiza un registro de la bases de datos basado      * en los nuevos valores relacionados con un identificador      *      * @param $idMeta      identificador      * @param $titulo      nuevo titulo      * @param $descripcion nueva descripcion      * @param $fechaLim    nueva fecha limite de cumplimiento      * @param $categoria   nueva categoria      * @param $prioridad   nueva prioridad      */

    public static function update(         $idMeta,         $titulo,         $descripcion,         $fechaLim,         $categoria,         $prioridad     )     {         // Creando consulta UPDATE         $consulta = "UPDATE meta" .       " SET titulo=?, descripcion=?, fechaLim=?, categoria=?, prioridad=? " .       "WHERE idMeta=?";         // Preparar la sentencia

        $cmd = Database::getInstance()‐>getDb()‐>prepare($consulta);         // Relacionar y ejecutar la sentencia

        $cmd‐>execute(array($titulo, $descripcion, $fechaLim, $categoria, $prioridad, $idMeta));         return $cmd;     }     /**      * Insertar una nueva meta      *      * @param $titulo      titulo del nuevo registro

(15)

     * @param $descripcion descripción del nuevo registro      * @param $fechaLim    fecha limite del nuevo registro      * @param $categoria   categoria del nuevo registro      * @param $prioridad   prioridad del nuevo registro      * @return PDOStatement      */

    public static function insert(         $titulo,         $descripcion,         $fechaLim,         $categoria,         $prioridad     )     {         // Sentencia INSERT         $comando = "INSERT INTO meta ( " .       "titulo," .       " descripcion," .       " fechaLim," .       " categoria," .       " prioridad)" .       " VALUES( ?,?,?,?,?)";         // Preparar la sentencia

        $sentencia = Database::getInstance()‐>getDb()‐>prepare($comando);         return $sentencia‐>execute(

      array(       $titulo,       $descripcion,       $fechaLim,       $categoria,       $prioridad       )         );     }     /**      * Eliminar el registro con el identificador especificado      *      * @param $idMeta identificador de la meta      * @return bool Respuesta de la eliminación      */

    public static function delete($idMeta)     {

        // Sentencia DELETE

        $comando = "DELETE FROM meta WHERE idMeta=?";         // Preparar la sentencia

        $sentencia = Database::getInstance()‐>getDb()‐>prepare($comando);         return $sentencia‐>execute(array($idMeta));

    } } ?>

Recuerda que el método

prepare()

permite reemplazar los placeholders (

'?'

) a través de

execute()

. Esto protege la operación de

inyecciones que puedan atentar contra la seguridad de los datos.

Paso #5: Crear un script para obtener todas las metas

Para retornar todos los registros que existen en la tabla

"meta"

usaremos el método

getAll()

de la clase

Meta

. La trata de la petición seguiría

la siguiente lógica:

1. Comprobar que la petición se realizó con el método GET.

2. Obtener todos los registros.

3. ¿La obtención tuvo éxito?

A. SI -> Retornar objeto Json con los datos

B. NO -> Retornar objeto Json con mensaje de error

(16)

aquellos posibles caminos que puedan generarse como una petición fallida, la falla de autenticación, la no existencia del recurso, la no

disponibilidad del servidor, etc. En resumen, contempla todas las fallas tanto del lado del servidor (códigos 5xx) como las del cliente (códigos

4xx).

No obstante este ejemplo se basa en el comportamiento ideal de nuestro servidor local. Donde solo reportaremos aquellas anomalías que

sucedan en la base de datos, asumiendo que la respuesta siempre tendrá un código de estado 200. Esto permitirá trackear si nuestro web

service está operando bien la base de datos.

Además de ello PDO puede retornar en excepciones por distintas causas que puedes estandarizar para el envío de mensajes. Pero este trabajo

te queda a tí

Ahora…¿Cómo envío una respuesta de vuelta a la aplicación Android?

Es justo donde entra Json para actuar como formato de comunicación. En cada respuesta enviaremos una seria de elementos Json que puedan

ser interpretados del lado del cliente. Esto te será posible usando las funciones

json_encode()

y

json_decode()

. La primera parsea un tipo de

dato a un string en formato json y la segunda es el procedimiento contrario.

Veamos nuestro servicio de obtención:

obtener_metas.php

<?php /**  * Obtiene todas las metas de la base de datos  */ require 'Meta.php';

if ($_SERVER['REQUEST_METHOD'] == 'GET') {     // Manejar petición GET

    $metas = Meta::getAll();     if ($metas) {

        $datos["estado"] = 1;         $datos["metas"] = $metas;         print json_encode($datos);     } else {

        print json_decode(array(       "estado" => 2,

      "mensaje" => "Ha ocurrido un error"         ));

    } }

El objeto Json que retornaremos tiene un atributo llamado

"estado"

el cual representa un código para indicar la calidad del resultado. Si es

entonces añadiremos otro atributo llamado

"metas"

el cual es un array de objetos con los datos de las metas. Si es

2

, entonces usaremos un

atributo

"mensaje"

para avisar a la aplicación cliente que ocurrió un error en la operación a la base de datos.

Una respuesta de éxito tendría el siguiente aspecto:

{     "estado":1,    "metas":[         {       "idMeta":"2",          "titulo":"Obtener mi t\u00edtulo de ingenier\u00eda de sistemas",          "descripcion":"Ya solo faltan 2 semestres para terminar mi carrera de ingenier\u00eda. Debo prepararme al m\u00e1ximo para desarrollar mi tesis de grado"          "prioridad":"Media",          "fechaLim":"2015‐05‐29",          "categoria":"Profesional"       },       {       "idMeta":"3",          "titulo":"Conquistar a Natasha",          "descripcion":"Natasha es la mujer de vida. Tengo que dec\u00edrselo antes de que acabe el semestre",

(17)

         "prioridad":"Alta",          "fechaLim":"2015‐05‐25",          "categoria":"Espiritual"       }    ] }

Por el otro lado, la respuesta de error simplemente sería:

{

    "estado":"2",

   "mensaje":"Ha ocurrido un error"

}

Cambiando de tema…¿Qué pasa si quieres filtrar los registros?

Por ejemplo…

Puede que requieras en orden ascendente o descendente de los registros con respecto a un campo. O simplemente obtener las metas que van

de una fecha a otra.

Para tener en cuenta estos casos, puedes consultar los datos de acuerdo a una serie de parámetros establecidos en la API. Esto quiere decir

que podrías incluir en el cuerpo de la petición variables que actúen como filtros en la selección. Sin embargo dicho tema está fuera del alcance

de nuestro artículo.

El diseño RESTful para Web Services provee reglas supremamente estilizadas para filtrar y consultar datos de forma más sencilla que

estableciendo filtros manuales.

Paso #6: Crear un script php para consultar el detalle de una meta

El segundo caso requiere que la petición traiga consigo el identificador de la meta que se desea ver en detalle. Con este dato es posible usar el

método

getById()

de Meta para conseguir el array necesario.

Veamos:

<?php /**  * Obtiene el detalle de una meta especificada por  * su identificador "idMeta"  */ require 'Meta.php';

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

        // Obtener parámetro idMeta

        $parametro = $_GET['idMeta'];         // Tratar retorno

        $retorno = Meta::getById($parametro);

        if ($retorno) {

      $meta["estado"] = "1";       $meta["meta"] = $retorno;

      // Enviar objeto json de la meta

      print json_encode($meta);         } else {       // Enviar respuesta de error general       print json_encode(       array(       'estado' => '2',       'mensaje' => 'No se obtuvo el registro'       )

(18)

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

Para retornar el detalle obviamente primero debes comprobar que el parámetro vino con la petición GET y si vino bien definido. Recuerda que

la función

isset()

es quién realiza este trabajo.

Esta vez tenemos tres casos generales posibles. Que la consulta sea un éxito y el registro con el identificador enviado existe. Lo que retorna en

un objeto Json con un objeto interno que tiene los datos de la meta.

{     "estado":"1",    "meta":{     "idMeta":"2",       "titulo":"Obtener mi t\u00edtulo de ingenier\u00eda de sistemas",       "descripcion":"Ya solo faltan 2 semestres para terminar mi carrera de ingenier\u00eda. Debo prepararme al m\u00e1ximo para desarrollar mi tesis de grado"       "prioridad":"Media",       "fechaLim":"2015‐05‐29",       "categoria":"Profesional"    } }

O también puede que PDO haya arrojado una excepción por algún motivo. Por ejemplo un error de sintaxis, la inexistencia del registro, etc. Con

ello envías tu objeto representativo del estado

2

.

{

    "estado":"2",

   "mensaje":"No se obtuvo el registro"

}

Ahora bien, puede que por alguna razón el parámetro no haya venido en la petición, o que pueda que haya venido pero con otro nombre. Para

este caso envías tu código 3 indicando este mensaje.

{

    "estado":"3",

   "mensaje":"Se necesita un identificador"

}

Paso #7: Crear un script php para la inserción de metas

La inserción requiere el uso del  método POST para la recepción de los datos de la meta. Por lo que debemos leer los datos en formato Json:

<?php

/**

 * Insertar una nueva meta en la base de datos  */

require 'Meta.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {     // Decodificando formato Json

    $body = json_decode(file_get_contents("php://input"), true);     // Insertar meta

    $retorno = Meta::insert(         $body['titulo'],         $body['descripcion'],

(19)

        $body['fechaLim'],         $body['categoria'],         $body['prioridad']);     if ($retorno) {         // Código de éxito         print json_encode(       array(       'estado' => '1',       'mensaje' => 'Creación exitosa')         );     } else {         // Código de falla         print json_encode(       array(       'estado' => '2',       'mensaje' => 'Creación fallida')         );     } }

La primera instrucción es comprobar la petición POST obtenida. Luego de ello conviertes el cuerpo de la petición a un arreglo de strings. Esto es

posible consultando el flujo con

file_get_contents()

, que convierte un archivo a string. Obviamente es necesario que uses la convención

“php://input” para acceder al cuerpo de la petición POST.

Ahora, el resultado que obtengas con

file_get_contents()

debe estar en formato Json, por lo que convertiremos esos datos a un arreglo

asociativo que nos permita acceder a la información. Para ello usa la función

json_decode()

y pasa como segundo parámetro el valor de

true

Luego usa el método

insert()

de Meta y comprueba el resultado. Esta vez no retornas en filas de la base de datos, así que el estado 1

contiene un mensaje de éxito.

{

    "estado":"1",

   "mensaje":"Creación éxitosa"

}

De lo contrario usa un mensaje general de error.

{

    "estado":"2",

   "mensaje":"Creación fallida"

}

Paso #8: Crear un scritp Php para la actualización de metas

La actualización es casi idéntica a la inserción, solo que esa vez debemos obtener el identificador de la meta para saber que registro actualizar.

De resto procedemos con el método

update()

de

Meta

sin problemas:

<?php

/**

 * Actualiza una meta especificada por su identificador  */

require 'Meta.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {     // Decodificando formato Json

    $body = json_decode(file_get_contents("php://input"), true);     // Actualizar meta

    $retorno = Meta::update(         $body['idMeta'],         $body['titulo'],         $body['descripcion'],         $body['fechaLim'],         $body['categoria'],         $body['prioridad']);     if ($retorno) {

(20)

        // Código de éxito         print json_encode(       array(       'estado' => '1',       'mensaje' => 'Actualización exitosa')         );     } else {         // Código de falla         print json_encode(       array(       'estado' => '2',       'mensaje' => 'Actualización fallida')         );     } }

Es resultado de éxito es similar y repetitivo para la actualización:

{

    "estado":"1",

   "mensaje":"Actualización éxitosa"

}

Al igual que el objeto Json de error:

{

    "estado":"2",

   "mensaje":"Actualización fallida"

}

Paso #9: Crear un script Php para la eliminación de metas

La eliminación se basa en el método POST para enviar el identificador de la meta que se necesita eliminar de la base de datos. Esta vez

usaremos el método

delete()

de

Meta

.

<?php /**  * Elimina una meta de la base de datos  * distinguida por su identificador  */ require 'Meta.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {     // Decodificando formato Json

    $body = json_decode(file_get_contents("php://input"), true);     $retorno = Meta::delete($body['idMeta']);

    if ($retorno) {         print json_encode(       array(       'estado' => '1',       'mensaje' => 'Eliminación exitosa')         );     } else {         print json_encode(       array(       'estado' => '2',       'mensaje' => 'Eliminación fallida')         );     } }

Como ves este servicio no es nada complicado. Su respuesta de éxito ser vería de la siguiente forma:

{

    "estado":"1",

   "mensaje":"Eliminación éxitosa"

(21)

Y los errores se mostrarían así:

{

    "estado":"2",

   "mensaje":"Eliminación fallida"

}

6. Codificación De La Aplicación Android

Una vez creado el Web Service, es hora de construir nuestra aplicación gestora de metas. Recuerda que es necesario que crees los siguientes

elementos e interacciones de la arquitectura:

Un patrón singleton Volley para las peticiones (o un cliente HttpURLConnection si lo deseas).

Crear la petición personalizada para tratar respuestas Json (el código ya fue tratado en el artículo de Volley).

Crear un adaptador que procese los elementos del recycler view.

Tratar los eventos para la comunicación de datos a través de los controles.

La idea es enfocarnos en el uso del servicio web y aprovechar al máximo las peticiones Json que nos provee Volley.

Paso #1: Crear Patrón Singleton Volley

Este paso ya hace parte de nuestra rutina para gestionar peticiones HTTP. Así que reutilizarás el singleton de artículos pasados para simplificar

procesos. La única diferencia que tendrás será la ausencia del

ImageLoader

como atributo. En esta ocasión no usaremos caching de imágenes,

así que es justo dejarlo descansar.

Recuerda incluir la librería Volley en tu proyecto de la forma que más te parezca.

VolleySingleton.java

import android.content.Context; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley;

/**

 * Creado por Hermosa Programación.  *

 * Clase que representa un cliente HTTP Volley  */

public final class VolleySingleton {     // Atributos

    private static VolleySingleton singleton;     private RequestQueue requestQueue;

    private static Context context;

    private VolleySingleton(Context context) {         VolleySingleton.context = context;         requestQueue = getRequestQueue();     }     /**      * Retorna la instancia unica del singleton      * @param context contexto donde se ejecutarán las peticiones      * @return Instancia      */

    public static synchronized VolleySingleton getInstance(Context context) {         if (singleton == null) {

      singleton = new VolleySingleton(context.getApplicationContext());         }         return singleton;     }     /**      * Obtiene la instancia de la cola de peticiones      * @return cola de peticiones

(22)

     */

    public RequestQueue getRequestQueue() {         if (requestQueue == null) {

      requestQueue = Volley.newRequestQueue(context.getApplicationContext());         }         return requestQueue;     }     /**      * Añade la petición a la cola      * @param req petición      * @param <T> Resultado final de tipo T      */

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

    }   }

Para acceder a las URLs del web service con aislamiento, crea una clase para referenciar constantes de la aplicación. Allí añadirás todas las

direcciones para evitar múltiples declaraciones:

/**

 * Clase que contiene los códigos usados en "I Wish" para

 * mantener la integridad en las interacciones entre actividades  * y fragmentos

 */

public class Constantes {     /**

     * Transición Home ‐> Detalle      */

    public static final int CODIGO_DETALLE = 100;     /**

     * Transición Detalle ‐> Actualización      */

    public static final int CODIGO_ACTUALIZACION = 101;     /**

     * URLs del Web Service      */

    public static final String GET = "http://10.0.3.2:63343/I%20Wish/obtener_metas.php";

    public static final String GET_BY_ID = "http://10.0.3.2:63343/I%20Wish/obtener_meta_por_id.php";     public static final String UPDATE = "http://10.0.3.2:63343/I%20Wish/actualizar_meta.php";

    public static final String DELETE = "http://10.0.3.2:63343/I%20Wish/borrar_meta.php";     public static final String INSERT = "http://10.0.3.2:63343/I%20Wish/insertar_meta.php";     /**

     * Clave para el valor extra que representa al identificador de una meta      */

    public static final String EXTRA_ID = "IDEXTRA"; }

Como ves, yo uso para el localhost la dirección

10.0.3.2

debido a que Genymotion (emulador alternativo) estableció este valor. Si vas a usar el

emulador de android usa la dirección

10.0.2.2

. Aquí el sitio oficial te habla un poco mas sobre estás convenciones de direcciones para

operaciones en la web.

Paso #2: Crear fuente de datos para las metas

Nuestro adaptador necesita alimentarse de una lista de elementos que le proporcionen la información necesaria para proyectar el layout. Es

por eso que tienes que crear una clase que represente la existencia de una meta en la aplicación Android.

Crea una nueva clase en Android Studio y llámala 

Meta

. Pon todos aquellos atributos puestos en la base de datos:

/** 

 * Reflejo de la tabla 'meta' en la base de datos  */

public class Meta {     /*

(23)

     */

    private String idMeta;     private String titulo;     private String descripcion;     private String prioridad;     private String fechaLim;     private String categoria;

    public Meta(String idMeta, String titulo, String descripcion, String prioridad, String fechaLim, String categoria) {         this.idMeta = idMeta;

        this.titulo = titulo;

        this.descripcion = descripcion;         this.prioridad = prioridad;         this.fechaLim = fechaLim;         this.categoria = categoria;     }

    public String getIdMeta() {         return idMeta;

    }

    public String getTitulo() {         return titulo;

    }

    public String getDescripcion() {         return descripcion;

    }

    public String getPrioridad() {         return prioridad;

    }

    public String getFechaLim() {         return fechaLim;

    }

    public String getCategoria() {         return categoria;     }     /**      * Compara los atributos de dos metas      * @param meta Meta externa      * @return true si son iguales, false si hay cambios      */

    public boolean compararCon(Meta meta) {

        return this.titulo.compareTo(meta.titulo) == 0 &&

      this.descripcion.compareTo(meta.descripcion) == 0 &&       this.fechaLim.compareTo(meta.fechaLim) == 0 &&       this.categoria.compareTo(meta.categoria) == 0 &&       this.prioridad.compareTo(meta.prioridad) == 0;     }

}

Si te fijas, tenemos un método para comparar una meta con otra para determinar si son iguales o no. Este método será de gran ayuda al

momento de validar si hay cambios en los datos de los formularios cuando el usuario interactúa con ellos. Lo que permitirá determinar si hay

que lanzar diálogos de confirmación antes de aplicar acciones.

Paso #3: Crear adaptador personalizado para el Recycler View

En este paso debes relacionar el layout item_list.xml con los datos que tenga cada objeto Meta de la fuente de datos.

No olvides usar le patrón ViewHolder para reducir la cantidad de llamadas del método

findViewById().

Además de ello tenemos que implementar sobre cada view holder una escucha

OnClickListener

para recibir los eventos del usuario en la lista.

Para ello se creará una interfaz intermediaria entre el

ViewHolder

y el adaptador, de tal forma que cuando se active el evento

onClick()

este

inicie la actividad de detalle.

import android.app.Activity; import android.content.Context;

import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater;

(24)

import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.herprogramacion.iwish.R;

import com.herprogramacion.iwish.modelo.Meta;

import com.herprogramacion.iwish.ui.actividades.DetailActivity; import java.util.List;

/**

 * Adaptador del recycler view  */

public class MetaAdapter extends RecyclerView.Adapter<MetaAdapter.MetaViewHolder>         implements ItemClickListener {

    /**

     * Lista de objetos {@link Meta} que representan la fuente de datos      * de inflado

     */

    private List<Meta> items;     /*

    Contexto donde actua el recycler view      */

    private Context context;

    public MetaAdapter(List<Meta> items, Context context) {         this.context = context;

        this.items = items;     }

    @Override

    public int getItemCount() {         return items.size();     }

    @Override

    public MetaViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {         View v = LayoutInflater.from(viewGroup.getContext())

      .inflate(R.layout.item_list, viewGroup, false);         return new MetaViewHolder(v, this);

    }

    @Override

    public void onBindViewHolder(MetaViewHolder viewHolder, int i) {         viewHolder.titulo.setText(items.get(i).getTitulo());

        viewHolder.prioridad.setText(items.get(i).getPrioridad());         viewHolder.fechaLim.setText(items.get(i).getFechaLim());         viewHolder.categoria.setText(items.get(i).getCategoria());     }     /**      * Sobrescritura del método de la interfaz {@link ItemClickListener}      *      * @param view     item actual      * @param position posición del item actual      */     @Override

    public void onItemClick(View view, int position) {         DetailActivity.launch(

      (Activity) context, items.get(position).getIdMeta());     }

    public static class MetaViewHolder extends RecyclerView.ViewHolder

      implements View.OnClickListener {         // Campos respectivos de un item

        public TextView titulo;         public TextView prioridad;         public TextView fechaLim;         public TextView categoria;

        public ItemClickListener listener;

        public MetaViewHolder(View v, ItemClickListener listener) {       super(v);

(25)

      titulo = (TextView) v.findViewById(R.id.titulo);       prioridad = (TextView) v.findViewById(R.id.prioridad);       fechaLim = (TextView) v.findViewById(R.id.fecha);       categoria = (TextView) v.findViewById(R.id.categoria);       this.listener = listener;

      v.setOnClickListener(this);         }

        @Override

        public void onClick(View v) {

      listener.onItemClick(v, getAdapterPosition());         }

    } }

interface ItemClickListener {

    void onItemClick(View view, int position); }

ItemClickListener

es la interfaz de comunicación que nos ayudará a relacionar lo posición del view con el evento

onClick()

. Como ves se

implementa en la clase

MetaAdapter

para iniciar la actividad detalle a través de su método de fabricación

launch()

.

Es necesario que enviemos el identificador de la meta para tener una referencia de la meta que debemos detallar.

Esto significa que se debe realizar otra petición para obtener los datos de la meta seleccionada. Lo que podría evitarse a través de caching con

SQLite o enviando todos los datos de la meta. Sin embargo el fin de este tutorial es el uso al máximo de nuestro Web Service para que puedas

interiorizar el conocimiento y practicar esta metodología. Por ahora no te preocupes en la arquitectura u optimizaciones.

Paso #4: Realizar Petición Para Poblar La Lista

Ya has construido un Web Service en Php con todas las características necesarias y has desarrollado los componentes de software para que la

aplicación Android comience a funcionar.

El fragmento de la lista lo iniciaremos dinámicamente a través del método

onCreate()

de

MainActivity

:

import android.content.Intent;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity; import com.herprogramacion.iwish.R;

import com.herprogramacion.iwish.ui.fragmentos.MainFragment;

/**  * Actividad principal que contiene un fragmento con una lista.  * Recuerda que la nueva librería de soporte reemplazó la clase  * {@link android.support.v7.app.ActionBarActivity} por  * {@link AppCompatActivity} para el uso de la action bar  * en versiones antiguas.  */

public class MainActivity extends AppCompatActivity {     @Override

    protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);         // Creación del fragmento principal

        if (savedInstanceState == null) {

      getSupportFragmentManager().beginTransaction()

      .add(R.id.container, new MainFragment(),"MainFragment")       .commit();

        }     } }

La comunicación inicial con el servidor es la lectura de todas las metas que se han guardado hasta el momento. Con ellas poblaremos la lista a

penas inicie la aplicación. Por lo que debemos dirigirnos al fragmento principal y generar una petición GET hacia el servidor en

onCreateView()

(26)

import android.content.Intent; import android.os.Bundle;

import android.support.v4.app.Fragment;

import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView;

import android.util.Log;

import android.view.LayoutInflater; import android.view.View;

import android.view.ViewGroup; import android.widget.Toast; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError;

import com.android.volley.toolbox.JsonObjectRequest; import com.google.gson.Gson;

import com.herprogramacion.iwish.R;

import com.herprogramacion.iwish.modelo.Meta; import com.herprogramacion.iwish.tools.Constantes; import com.herprogramacion.iwish.ui.MetaAdapter;

import com.herprogramacion.iwish.ui.actividades.InsertActivity; import com.herprogramacion.iwish.web.VolleySingleton;

import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.Arrays;

/**

 * Fragmento principal que contiene la lista de las metas  */

public class MainFragment extends Fragment {     /*

    Etiqueta de depuracion      */

    private static final String TAG = MainFragment.class.getSimpleName();     /*

    Adaptador del recycler view      */

    private MetaAdapter adapter;     /*

    Instancia global del recycler view      */

    private RecyclerView lista;     /*

    instancia global del administrador      */

    private RecyclerView.LayoutManager lManager;     /*

    Instancia global del FAB      */

    com.melnykov.fab.FloatingActionButton fab;     private Gson gson = new Gson();

    public MainFragment() {     }

    @Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container,        Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_main, container, false);         lista = (RecyclerView) v.findViewById(R.id.reciclador);

        lista.setHasFixedSize(true);

        // Usar un administrador para LinearLayout

        lManager = new LinearLayoutManager(getActivity());         lista.setLayoutManager(lManager);

Referencias

Documento similar