You are on page 1of 96

Finalmente ejecuta el proyecto Feedky y prueba su funcionamiento:

Conclusiones

Crear Un Web Service Para Android Con


Mysql, Php y Json
mayo 26, 2015 James Revelo

Deseas conectar una aplicacin Android a Mysql?


Has intentado crear un web service con Php para la comunicacin de datos de tu
aplicativo web con tu aplicativo mvil android, pero an no comprendes bien cmo
hacerlo?
Pues bien, en este artculo te mostrar algunas ideas sobre la creacin de una aplicacin
android que consuma los datos de un servidor externo a travs de Php, Mysql yJson.
Para ello he creado una aplicacin llamada I Wish, la cual permite a nuestros usuarios
guardar una lista de deseos y metas que tienen en su vida. Con este ejemplo podrs ver
cmo implementar la insercin, edicin, eliminacin y consulta de datos a travs de un
Web Service.

Si sigues leyendo podrs obtener el siguiente resultado:


El cdigo de la aplicacin puedes obtenerlo presionando el siguiente botn:

Descargar Cdigo
Apyanos con una seal en tu red social favorita y consigue el cdigo completo.

Me gusta

Tweet

+1 Google

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 programacin o plataforma en que
fueron desarrollados. Este debe tener una interfaz basada en un formato estndar 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
programacin basados en el protocolo HTTP. Sin embargo podemos usar esta red social en
nuestro dispositivo Android.
Cmo es posible esto, si la aplicacin Android est construida con lenguaje Java?
A travs 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 dinmico incorpora el uso de un lenguaje
web para la gestin HTTP que en este caso ser Php.

2. Requerimientos De La Aplicacin
Como leste al inicio, la aplicacin I Wish gestiona las metas y sueos de los usuarios
permitindoles tener un registro completo. Bsicamente el alcance del proyecto se resumen
en:
Como usuario de I Wish, deseo mantener los datos de todas mis metas y sueos (se
refiere al CRUD).
Como usuario de I Wish, deseo ver el detalle de cada meta.
Como usuario de I Wish, deseo que cada tem tenga un ttulo, una descripcin, una
fecha lmite de cumplimiento, prioridad y categora. Las categoras posibles son:
Salud, Finanzas, Profesional y Espiritual.
Estos requerimientos no son nada del otro mundo. Bsicamente estas ante una situacin de
listas y detalles. Algo que ya has visto en artculos 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 Aplicacin
A primera vista I Wish es una aplicacin que se basa en la funcionalidad bsica 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 aplicacin en primera instancia
de la siguiente forma:

Basado en el boceto que acabas de crear ya puedes identificar que la cantidad de


actividades, fragmentos, dilogos 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 insercin.

Actividad con fragmento de formulario para edicin.


En este tutorial usaremos actividades basadas en fragmentos, ya que muchos lectores han
preguntado cmo hacer para comunicar fragmentos con actividades y viceversa.

4. Crear UI Para La Aplicacin Android


4.1 Disear Actividad Principal Con Fragmento Tipo Lista

Despus 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 aadir los fragmentos
programticamente 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"
android:id="@+id/container"
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 diseo es diferente. La idea es aadir el recycler para recubrir toda la
actividad y adems aadir un Floating Action Button en la parte inferior derecha con el fin
de que el usuario aada nuevas metas.
Para aadir el FAB (Floating Action Button) podemos hacer uso de una de las siguientes
libreras que existen en la web:

Floating Action Button Library For Android


FloatingActionButton de makovkastar
Future Simple
Incluso podras basarte en el ejemplo del sitio de android devepers llamado
FloatingActionButtonBasic. Todo depende de ti. Cada librera trae la explicacin de su
implementacin, as que no hay excusas.
Por mi parte, en este ejemplo usar la librera 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 aadimos los colores
correspondientes a su interaccin.
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:

El patrn anterior muestra un FAB grande para representar la insercin con unas
dimensiones reglamentarias de 56dpx56dp. El icono que lleva debe mantenerse en
24dpx24dp.

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

4.2 Disear 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 categora de la meta y el segundo
ser una hoja para sus datos completos. Adicionalmente aadiremos un Float Button
Action para la edicin 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" />

<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="Categora"
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="Descripcin"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_marginBottom="16dp" />

<TextView
android:id="@+id/descripcion_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Descripcin"
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 Lmite"
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="Categora"
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 botn mini.

En este caso se us como nodo un LinearLayout con dos RelativeLayout dentro. Esto nos
permite dividir por pesos (weight) la ocupacin de espacio entre ambos layouts y as
mantener una proporcin.

4.3 Disear Actividad Con Formulario


La insercin y edicin requiere de la proyeccin de un formulario que contenga los
controles necesarios para que el usuario especifique la informacin 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 aplicacin con las respectivos views para obtener la informacin.

Por ejemplo
El titulo de cada meta recibe texto escrito desde el input del dispositivo, por lo que sabemos
que elEditText es la solucin para este caso. La descripcin es igual, necesita un campo de
texto. La fecha limite puede ser obtenida a travs de un DatePicker y para la categora
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="Ttulo"
android:minLines="1"
android:maxLines="1"
android:maxLength="55"
android:phoneNumber="false"
android:singleLine="true"
android:paddingTop="16dp"
android:paddingBottom="16dp" />

<!-- Descripcin -->


<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/descripcion_input"
android:layout_below="@+id/titulo_input"
android:layout_centerHorizontal="true"
android:hint="Descripcin"
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" />

<!-- Categora -->


<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 Categora -->


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Categora"

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>

4.4 Disear Layout Personalizado De Los Items


La organizacin de los atributos de cada meta dentro de los tems de la lista debe ser un
resumen de sus caractersticas principales. Puedes dejar la descripcin solo para la
actividad del detalle y eliminarlo de la presentacin 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" />

<!-- Categora -->


<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="Categora"
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>

El anterior diseo se vera de la siguiente forma:

5. Codificacin Del Web Service En Php


Antes de crear la aplicacin Android debes desarrollar primero tu Web Service con
cualquiera de los estndares que te interesen. El alcance de este tutorial no abarca el uso de
restricciones REST, SOAP,RPC o sus parecidos. Simplemente vers cmo crear las
implementaciones Php necesarias para realizar operaciones sobre una base de datos en
Mysql a travs de peticiones GET y POST.
Si deseas aprender a crear Web Services con diseo REST, entonces te recomiendo
este excelente curso online con Laravel.
Para desarrollar este aplicativo usar el entorno de desarrollo XAMPP, el cual provee
automticamente una configuracin de un servidor Apache local, el intrprete 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 Diseo E Implementacin De La Base De Datos


Disear base de datos: Si ya lo has notado, la base de datos de la aplicacin I Wish solo
tiene una entidad que representa a los registros de las metas. Esto reduce ampliamente el
diseo de bases de datos en el problema. No obstante, si tu proyecto es mas complicado,
asegrate de tener una buena metodologa de diseo de bases de datos antes de crear el web
service,
Meta debe tener los atributos que hemos venido viendo ms una llave primaria que
mantenga la integridad de los datos. Observa el siguiente minidiagrama entidad-relacin:

Crear base de datos: Para implementar la base de datos lo primero que debes hacer es
crear una nueva base de datos en la aplicacin phpMyAdmin que te otorga tu
distribucin XAMPP. Donde le asignaremos el nombre de i_wish.

Ahora crea la tabla meta para que contenga seis columnas en su estructura y adems usa el
formato UTF-8 para soportar acentos. Puedes hacerlo a travs del editor de phpMyAdmin o
con el siguiente comandoCREATE:
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 aade 5 registros de ejemplo en la tabla que permitan probar el funcionamiento en la


aplicacin android ms 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 cmo conseguir mas fuentes
de ingresos',
'Media',
'2015-11-20',
'Material'), (NULL,
'Obtener mi ttulo de ingeniera de sistemas',
'Ya solo faltan 2 semestres para terminar mi carrera de ingeniera. Debo prepararme al mximo para desarrollar mi
tesis de grado',
'Alta',
'2016-06-17',
'Profesional'), (NULL,
'Conquistar a Natasha',
'Natasha es la mujer de vida. Tengo que decrselo 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 rgimen
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 Cdigo Php Para Consumir Datos


En primera instancia crea una conexin a la base de datos Mysql con la interfaz que mas se
acomode a tus necesidades. En mi caso voy a crear una conexin 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 insercin, actualizacin, eliminacin y consulta a travs
de la conexin 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 aplicacin Android interprete
los resultados de forma legible.

Paso #1: Crear conexin 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 conexin hacia la base de datos o simplemente crear una nueva conexin en
cada script de Php que tengas.
Para este caso te compartir un patrn singleton de PDO para limitar el nmero de
aperturas a la base de datos en una sola. Con ello podremos disponer de un solo objeto a
travs de todo el proyecto.
No obstante hay patrones de diseo muy interesantes que puedes consultar en la web. Por
ejemplo el repositorio del usuario indieteq en github. l se enfoca en la implementacin del
CRUD de una forma muy sencilla y orientada a objetos.
Veamos el resultado del patrn singleton:
Database.php
<?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 conexin 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 conexin PDO basada
* en los datos de conexin
* @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 clonacin del objeto
*/
final protected function __clone()
{
}

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

?>

Ten en cuenta que la conexin se abre con 4 cadenas descriptivas del entorno que ests
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 contrasea.
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 contrasea
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 constrasea
?>

Adicionalmente debes aadir al cuarto parmetro del constructor de PDO la indicacin SET
NAMES UTF-8para 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 travs de la clase Database. En esencia necesitas un mtodo para
obtener todos los registros, uno para la insercin, otro para eliminacin, tambin para la
actualizacin y un mtodo 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) {


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 excepcin
// 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
* @param $titulo

identificador
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

* @param $descripcion descripcin 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 eliminacin
*/
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 mtodo prepare() permite reemplazar los placeholders ('?') a travs
de execute(). Esto protege la operacin 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
mtodo getAll() de la claseMeta. La trata de la peticin seguira la siguiente lgica:

Comprobar que la peticin se realiz con el mtodo GET.

Obtener todos los registros.


La obtencin tuvo xito?

SI -> Retornar objeto Json con los datos

NO -> Retornar objeto Json con mensaje de error


Tenemos un flujo que se asegura de satisfacer el debido resultado y aquellos resultados
adversos. La trata de errores debe comprender todos aquellos posibles caminos que puedan
generarse como una peticin fallida, la falla de autenticacin, la no existencia del recurso,
la no disponibilidad del servidor, etc. En resumen, contempla todas las fallas tanto del lado
del servidor (cdigos 5xx) como las del cliente (cdigos 4xx).
No obstante este ejemplo se basa en el comportamiento ideal de nuestro servidor local.
Donde solo reportaremos aquellas anomalas que sucedan en la base de datos, asumiendo
que la respuesta siempre tendr un cdigo de estado 200. Esto permitir trackear si nuestro
web service est operando bien la base de datos.
Adems de ello PDO puede retornar en excepciones por distintas causas que puedes

estandarizar para el envo de mensajes. Pero este trabajo te queda a t


AhoraCmo envo una respuesta de vuelta a la aplicacin Android?
Es justo donde entra Json para actuar como formato de comunicacin. 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 obtencin:
obtener_metas.php
<?php
/**
* Obtiene todas las metas de la base de datos
*/

require 'Meta.php';

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

// Manejar peticin 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
cdigo para indicar la calidad del resultado. Si es 1, entonces aadiremos 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 aplicacin cliente que ocurri un error en
la operacin a la base de datos.
Una respuesta de xito tendra 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",
"prioridad":"Alta",
"fechaLim":"2015-05-25",
"categoria":"Espiritual"
}
]
}

Por el otro lado, la respuesta de error simplemente sera:

{
"estado":"2",
"mensaje":"Ha ocurrido un error"
}

Cambiando de temaQu 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
parmetros establecidos en la API. Esto quiere decir que podras incluir en el cuerpo de la
peticin variables que acten como filtros en la seleccin. Sin embargo dicho tema est
fuera del alcance de nuestro artculo.

El diseo RESTful para Web Services provee reglas supremamente estilizadas para filtrar y
consultar datos de forma ms sencilla que estableciendo filtros manuales.

Paso #6: Crear un script php para consultar el detalle de una meta
El segundo caso requiere que la peticin traiga consigo el identificador de la meta que se
desea ver en detalle. Con este dato es posible usar el mtodo 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 parmetro 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'
)
);
}

} 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 parmetro vino con la
peticin GET y si vino bien definido. Recuerda que la funcin isset() es quin 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 tambin puede que PDO haya arrojado una excepcin por algn motivo. Por ejemplo un
error de sintaxis, la inexistencia del registro, etc. Con ello envas tu objeto representativo
del estado 2.
{
"estado":"2",
"mensaje":"No se obtuvo el registro"
}

Ahora bien, puede que por alguna razn el parmetro no haya venido en la peticin, o que
pueda que haya venido pero con otro nombre. Para este caso envas tu cdigo 3 indicando
este mensaje.

{
"estado":"3",
"mensaje":"Se necesita un identificador"
}

Paso #7: Crear un script php para la insercin de metas


La insercin requiere el uso del mtodo POST para la recepcin 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'],
$body['fechaLim'],
$body['categoria'],
$body['prioridad']);

if ($retorno) {
// Cdigo de xito
print json_encode(
array(
'estado' => '1',
'mensaje' => 'Creacin exitosa')
);
} else {
// Cdigo de falla
print json_encode(
array(
'estado' => '2',
'mensaje' => 'Creacin fallida')
);
}
}

La primera instruccin es comprobar la peticin POST obtenida. Luego de ello conviertes


el cuerpo de la peticin 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 convencin php://input para acceder al cuerpo de la peticin 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

informacin. Para ello usa la funcin json_decode() y pasa como segundo parmetro el
valor de true.
Luego usa el mtodo 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":"Creacin xitosa"
}

De lo contrario usa un mensaje general de error.

{
"estado":"2",
"mensaje":"Creacin fallida"
}

Paso #8: Crear un scritp Php para la actualizacin de metas


La actualizacin es casi idntica a la insercin, solo que esa vez debemos obtener el
identificador de la meta para saber que registro actualizar. De resto procedemos con el
mtodo 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) {
// Cdigo de xito
print json_encode(
array(
'estado' => '1',
'mensaje' => 'Actualizacin exitosa')
);
} else {
// Cdigo de falla
print json_encode(
array(
'estado' => '2',
'mensaje' => 'Actualizacin fallida')
);
}
}

Es resultado de xito es similar y repetitivo para la actualizacin:

{
"estado":"1",
"mensaje":"Actualizacin xitosa"
}

Al igual que el objeto Json de error:

"estado":"2",
"mensaje":"Actualizacin fallida"
}

Paso #9: Crear un script Php para la eliminacin de metas


La eliminacin se basa en el mtodo POST para enviar el identificador de la meta que se
necesita eliminar de la base de datos. Esta vez usaremos el mtodo 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' => 'Eliminacin exitosa')
);
} else {
print json_encode(
array(
'estado' => '2',
'mensaje' => 'Eliminacin fallida')
);
}

Como ves este servicio no es nada complicado. Su respuesta de xito ser vera de la
siguiente forma:

{
"estado":"1",
"mensaje":"Eliminacin xitosa"
}

Y los errores se mostraran as:

{
"estado":"2",
"mensaje":"Eliminacin fallida"
}

6. Codificacin De La Aplicacin Android


Una vez creado el Web Service, es hora de construir nuestra aplicacin gestora de metas.
Recuerda que es necesario que crees los siguientes elementos e interacciones de la
arquitectura:

Un patrn singleton Volley para las peticiones (o un cliente HttpURLConnection si


lo deseas).

Crear la peticin personalizada para tratar respuestas Json (el cdigo ya fue tratado
en el artculo de Volley).

Crear un adaptador que procese los elementos del recycler view.

Tratar los eventos para la comunicacin de datos a travs de los controles.


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

Paso #1: Crear Patrn Singleton Volley

Este paso ya hace parte de nuestra rutina para gestionar peticiones HTTP. As que
reutilizars el singleton de artculos pasados para simplificar procesos. La nica diferencia
que tendrs ser la ausencia delImageLoader como atributo. En esta ocasin no usaremos
caching de imgenes, as que es justo dejarlo descansar.
Recuerda incluir la librera Volley en tu proyecto de la forma que ms 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 Programacin.
*
* 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 ejecutarn 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
*/
public RequestQueue getRequestQueue() {
if (requestQueue == null) {
requestQueue = Volley.newRequestQueue(context.getApplicationContext());
}
return requestQueue;
}

/**
* Aade la peticin a la cola
* @param req peticin
* @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 aplicacin. All aadirs todas las direcciones para evitar mltiples
declaraciones:

/**
* Clase que contiene los cdigos usados en "I Wish" para
* mantener la integridad en las interacciones entre actividades
* y fragmentos
*/
public class Constantes {
/**
* Transicin Home -> Detalle
*/
public static final int CODIGO_DETALLE = 100;

/**
* Transicin Detalle -> Actualizacin
*/
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 direccin 10.0.3.2 debido a


que Genymotion (emulador alternativo) estableci este valor. Si vas a usar el emulador de
android usa la direccin 10.0.2.2. Aqu el sitio oficialte habla un poco mas sobre ests
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
informacin necesaria para proyectar el layout. Es por eso que tienes que crear una clase
que represente la existencia de una meta en la aplicacin Android.
Crea una nueva clase en Android Studio y llmala Meta. Pon todos aquellos atributos
puestos en la base de datos:
/**
* Reflejo de la tabla 'meta' en la base de datos
*/
public class Meta {

/*
Atributos
*/
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 mtodo para comparar una meta con otra para determinar si son
iguales o no. Este mtodo ser de gran ayuda al momento de validar si hay cambios en los
datos de los formularios cuando el usuario interacta con ellos. Lo que permitir determinar
si hay que lanzar dilogos de confirmacin 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 patrn ViewHolder para reducir la cantidad de llamadas del
mtodo findViewById().
Adems 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 elViewHolder 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;
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 mtodo de la interfaz {@link ItemClickListener}
*
* @param view

item actual

* @param position posicin 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);
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 comunicacin que nos ayudar a relacionar lo

posicin del view con el evento onClick() . Como ves se implementa en la


clase MetaAdapter para iniciar la actividad detalle a travs de su mtodo de
fabricacin 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 peticin para obtener los datos de la meta
seleccionada. Lo que podra evitarse a travs de caching con SQLite o enviando todos los
datos de la meta. Sin embargo el fin de este tutorial es el uso al mximo de nuestro Web
Service para que puedas interiorizar el conocimiento y practicar esta metodologa. Por
ahora no te preocupes en la arquitectura u optimizaciones.

Paso #4: Realizar Peticin Para Poblar La Lista


Ya has construido un Web Service en Php con todas las caractersticas necesarias y has
desarrollado los componentes de software para que la aplicacin Android comience a
funcionar.
El fragmento de la lista lo iniciaremos dinmicamente a travs del
mtodo 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 librera 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);

// Creacin del fragmento principal


if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new MainFragment(),"MainFragment")
.commit();
}
}
}

La comunicacin 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 aplicacin. Por lo que
debemos dirigirnos al fragmento principal y generar una peticin GET hacia el servidor
en onCreateView().
Veamos:

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

// Cargar datos en el adaptador


cargarAdaptador();

// Obtener instancia del FAB


fab = (com.melnykov.fab.FloatingActionButton) v.findViewById(R.id.fab);

// Asignar escucha al FAB


fab.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Iniciar actividad de insercin
getActivity().startActivityForResult(
new Intent(getActivity(), InsertActivity.class), 3);
}
}
);

return v;
}

/**
* Carga el adaptador con las metas obtenidas
* en la respuesta
*/
public void cargarAdaptador() {
// Peticin GET
VolleySingleton.
getInstance(getActivity()).
addToRequestQueue(
new JsonObjectRequest(
Request.Method.GET,

Constantes.GET,
null,
new Response.Listener<JSONObject>() {

@Override
public void onResponse(JSONObject response) {
// Procesar la respuesta Json
procesarRespuesta(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "Error Volley: " + error.getMessage());
}
}

)
);
}

/**
* Interpreta los resultados de la respuesta y as
* realizar las operaciones correspondientes
*
* @param response Objeto Json con la respuesta
*/
private void procesarRespuesta(JSONObject response) {
try {
// Obtener atributo "estado"
String estado = response.getString("estado");

switch (estado) {
case "1": // EXITO
// Obtener array "metas" Json

JSONArray mensaje = response.getJSONArray("metas");


// Parsear con Gson
Meta[] metas = gson.fromJson(mensaje.toString(), Meta[].class);
// Inicializar adaptador
adapter = new MetaAdapter(Arrays.asList(metas), getActivity());
// Setear adaptador a la lista
lista.setAdapter(adapter);
break;
case "2": // FALLIDO
String mensaje2 = response.getString("mensaje");
Toast.makeText(
getActivity(),
mensaje2,
Toast.LENGTH_LONG).show();
break;
}

} catch (JSONException e) {
e.printStackTrace();
}

El cdigo anterior muestra el uso de una constante llamada Constantes.GET, la cual


contiene la direccin del servicio de obtencin obtener_metas.php.
Con esa URL ya es posible realizar la peticin JsonObjectRequest con su respectivo
mtodo GET a travs del mtodo cargarAdaptador().
Para aislar un poco los procesos, he creado el mtodo procesarRespuesta(), el cual recibe
un objetoJSONObject en bruto para comenzar el parsing. Donde he divido los caminos a
travs de una estructuraswitch basado en el valor del atributo "estado".
Si el estado es exitoso inmediatamente obtendremos el array de metas que viene en el
atributo "metas". Este arreglo de objetos Json se parsea directamente a un arreglo de
objetos Meta a travs de la libreraGson.

Recuerda que el adaptador recibe una serie de metas en formato List<Meta>, por lo que
usaremos la clase Arrays para convertir el arreglo de metas a lista. Con eso listo ya es
posible instanciar el adaptador y asignarlo al recycler.

Paso #5: Ver Detalle De Items En Otra Actividad


Una vez nuestro adaptador poblado, ya podemos ver el detalle de la
descripcin en DetailActivity.java con un fragmento alojado.
Para ello inicia el fragmento dinmicamente en onCreate():
DetailActivity.java
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;

import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.tools.Constantes;
import com.herprogramacion.iwish.ui.fragmentos.DetailFragment;

/**
* Esta actividad contiene un fragmento que muestra el detalle
* de las metas.
*/
public class DetailActivity extends AppCompatActivity {

/*
Valor extra que identifica a la meta a detallar
*/
private static final String EXTRA_ID = "IDMETA";

/**
* Instancia global de la meta a detallar
*/

private String idMeta;

/**
* Inicia una nueva instancia de la actividad
*
* @param activity Contexto desde donde se lanzar
* @param idMeta Identificador de la meta a detallar
*/
public static void launch(Activity activity, String idMeta) {
Intent intent = getLaunchIntent(activity, idMeta);
activity.startActivityForResult(intent, Constantes.CODIGO_DETALLE);
}

/**
* Construye un Intent a partir del contexto y la actividad
* de detalle.
*
* @param context Contexto donde se inicia
* @param idMeta Identificador de la meta
* @return Intent listo para usar
*/
public static Intent getLaunchIntent(Context context, String idMeta) {
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_ID, idMeta);
return intent;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (getSupportActionBar() != null) {
// Dehabilitar titulo de la actividad
getSupportActionBar().setDisplayShowTitleEnabled(false);

// Setear cono "X" como Up button


getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close);
}

// Retener instancia
if (getIntent().getStringExtra(EXTRA_ID) != null)
idMeta = getIntent().getStringExtra(EXTRA_ID);

if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, DetailFragment.createInstance(idMeta), "DetailFragment")
.commit();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_detail, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

switch (id) {
case android.R.id.home:
finish();
return true;
}

return super.onOptionsItemSelected(item);
}
}

Este cdigo tiene varias cosas interesantes. En primera instancia el uso de un mtodo
esttico llamadolaunch(), el cual construye una instancia de la actividad de detalle y la
inicia a travs de un Intentconstruido a partir del contexto que el adaptador proveer.
La actividad detalle se basa en el identificador de la meta, por lo que idMeta es un atributo
que permitir retener esa instancia, cuando sea pedida con getIntent().
A los fragmentos que hemos iniciado dinmicamente se les est asignando una etiqueta que
los diferencie de los otros. Esto es de suprema importancia, ya que necesitamos obtener sus
instancias cuando la actividad se comunique con ellos.

Paso #9: Consultar Detalles De Cada Item


Ahora pregntate que debe hacer el fragmento de detalle
Dependiendo del enfoque de experiencia de usuario que tengas, puede que sean muchas
cosas. Sin embargo para este ejemplo el usuario tiene dos caminos evidentes:

Cerrar el detalle con el Up Button o Back Button

Editar la meta a travs del Floating Action Button.


La primera interaccin ya la tenemos cubierta en DetailActivity, ya que hemos
sobrescrito el comportamiento del Up Button por el cierre de la actividad.
En el segundo caso de edicin es necesario consultar la base de datos para setear los datos
en los views. Adems de ello asignar una escucha al FAB para que inicie la actividad de
actualizacin.
Realizar peticin HTTP: La realizacin de la peticin HTTP requiere consultar el detalle
con el identificador que el adaptador envo a travs del Intent.
Recuerda que la convencin createInstance() inicializa un nuevo fragmento con los
extras necesarios para su funcionamiento. Por lo que en onCreateView() es posible acceder
al identificador y enviar una peticin GET hacia el Web Service:
DetailFragment.java
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
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.actividades.UpdateActivity;
import com.herprogramacion.iwish.web.VolleySingleton;

import org.json.JSONException;
import org.json.JSONObject;

/**
* A placeholder fragment containing a simple view.
*/
public class DetailFragment extends Fragment {
/*
Etiqueta de valor extra
*/
private static final String EXTRA_ID = "IDMETA";

/**
* Etiqueta de depuracin
*/
private static final String TAG = DetailFragment.class.getSimpleName();

/*

Instancias de Views
*/
private ImageView cabecera;
private TextView titulo;
private TextView descripcion;
private TextView prioridad;
private TextView fechaLim;
private TextView categoria;
private ImageButton editButton;
private String extra;
private Gson gson = new Gson();

public DetailFragment() {
}

public static DetailFragment createInstance(String idMeta) {


DetailFragment detailFragment = new DetailFragment();
Bundle bundle = new Bundle();
bundle.putString(EXTRA_ID, idMeta);
detailFragment.setArguments(bundle);
return detailFragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_detail, container, false);

// Obtencin de views
cabecera = (ImageView) v.findViewById(R.id.cabecera);
titulo = (TextView) v.findViewById(R.id.titulo);
descripcion = (TextView) v.findViewById(R.id.descripcion);
prioridad = (TextView) v.findViewById(R.id.prioridad);
fechaLim = (TextView) v.findViewById(R.id.fecha);
categoria = (TextView) v.findViewById(R.id.categoria);

editButton = (ImageButton) v.findViewById(R.id.fab);

// Setear escucha para el fab


editButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Iniciar actividad de actualizacin
Intent i = new Intent(getActivity(), UpdateActivity.class);
i.putExtra(EXTRA_ID, extra);
getActivity().startActivityForResult(i, Constantes.CODIGO_ACTUALIZACION);
}
}
);

// Obtener extra del intent de envo


extra = getArguments().getString(EXTRA_ID);

// Cargar datos desde el web service


cargarDatos();

return v;
}

/**
* Obtiene los datos desde el servidor
*/
public void cargarDatos() {

// Aadir parmetro a la URL del web service


String newURL = Constantes.GET_BY_ID + "?idMeta=" + extra;

// Realizar peticin GET_BY_ID


VolleySingleton.getInstance(getActivity()).addToRequestQueue(
new JsonObjectRequest(

Request.Method.GET,
newURL,
null,
new Response.Listener<JSONObject>() {

@Override
public void onResponse(JSONObject response) {
// Procesar respuesta Json
procesarRespuesta(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "Error Volley: " + error.getMessage());
}
}
)
);
}

/**
* Procesa cada uno de los estados posibles de la
* respuesta enviada desde el servidor
*
* @param response Objeto Json
*/
private void procesarRespuesta(JSONObject response) {

try {
// Obtener atributo "mensaje"
String mensaje = response.getString("estado");

switch (mensaje) {
case "1":

// Obtener objeto "meta"


JSONObject object = response.getJSONObject("meta");

//Parsear objeto
Meta meta = gson.fromJson(object.toString(), Meta.class);

// Asignar color del fondo


switch (meta.getCategoria()) {
case "Salud":
cabecera.setBackgroundColor(getResources().getColor(R.color.saludColor));
break;
case "Finanzas":
cabecera.setBackgroundColor(getResources().getColor(R.color.finanzasColor));
break;
case "Espiritual":
cabecera.setBackgroundColor(getResources().getColor(R.color.espiritualColor));
break;
case "Profesional":
cabecera.setBackgroundColor(getResources().getColor(R.color.profesionalColor));
break;
case "Material":
cabecera.setBackgroundColor(getResources().getColor(R.color.materialColor));
break;
}

// Seteando valores en los views


titulo.setText(meta.getTitulo());
descripcion.setText(meta.getDescripcion());
prioridad.setText(meta.getPrioridad());
fechaLim.setText(meta.getFechaLim());
categoria.setText(meta.getCategoria());

break;

case "2":

String mensaje2 = response.getString("mensaje");


Toast.makeText(
getActivity(),
mensaje2,
Toast.LENGTH_LONG).show();
break;

case "3":
String mensaje3 = response.getString("mensaje");
Toast.makeText(
getActivity(),
mensaje3,
Toast.LENGTH_LONG).show();
break;
}

} catch (JSONException e) {
e.printStackTrace();
}

}
}

Para la inclusin de parmetros en la peticin GET, adjunta a la URL el valor


de idMeta con la convencin de formularios '?clave=valor'.
Al igual que en MainFragment, se cre un mtodo para procesar la respuesta dependiendo
del estado que se obtuvo. Si hubo xito, entonces seteamos los valores correspondientes de
cada view.

Paso #10: Realizar peticin POST para editar detalles de la meta


Para la edicin hemos creado la actividad UpdateActivity, la cual mostrar el formulario
de recoleccin con los datos de la una meta.
La lgica funciona as: Una vez el usuario haya modificado los datos, entonces puede
confirmar sus datos presionando el action button de check que usaremos para la
confirmacin. Si no est de acuerdo o se arrepiente de ello, entonces puede descartar los

cambios con el segundo action button. Incluso puedes incluir la eliminacin entre los action
buttos.
As que lo primero es crear un archivo de men para poblar la action bar:
menu_form.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.herprogramacion.iwish.ui.actividades.UpdateActivity">

<!-- Descartar Cambios -->


<item android:id="@+id/action_discard" android:title="@string/action_discard"
android:orderInCategory="3" app:showAsAction="never" />

<!-- Borrar -->


<item android:id="@+id/action_delete" android:title="@string/action_delete"
android:orderInCategory="4" app:showAsAction="never"/>
</menu>

No incluiremos el guardado de cambios, ya que lo implementaremos en el Up Button.


Teniendo en cuenta esa apreciacin las tareas que tienes por implementar son:

Cargar los datos de la meta en los componentes del formulario.

Habilitar la contribucin a la action bar.

Manejar los eventos en cada action button.

Implementar la insercin, eliminacin y borrado de las metas.

Lanzar dilogos para confirmar la eliminacin y el descarte de cambios.

Usar Toasts para afianzar la confirmacin de las operaciones.


Veamos la solucin:
UpdateFragment.java

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
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.web.VolleySingleton;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

/**
* Fragmento con formulario para actualizar la meta
*/
public class UpdateFragment extends Fragment {

/*
Etiqueta de depuracin
*/
private static final String TAG = UpdateFragment.class.getSimpleName();

/*
Etiqueta de valor extra para modo edicin
*/
private static final String EXTRA_ID = "IDMETA";

/*
Controles
*/
private EditText titulo_input;
private EditText descripcion_input;
private Spinner prioridad_spinner;
private TextView fecha_text;
private Spinner categoria_spinner;

/*
Valor del argumento extra
*/
private String idMeta;

/**
* Es la meta obtenida como respuesta de la peticin HTTP
*/
private Meta metaOriginal;

/**
* Instancia Gson para el parsing Json
*/
private Gson gson = new Gson();

public UpdateFragment() {
}

/**
* Crea un nuevo fragmento basado en un argumento
*
* @param extra Argumento de entrada
* @return Nuevo fragmento
*/
public static Fragment createInstance(String extra) {
UpdateFragment detailFragment = new UpdateFragment();
Bundle bundle = new Bundle();
bundle.putString(EXTRA_ID, extra);
detailFragment.setArguments(bundle);
return detailFragment;
}

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

// Inflando layout del fragmento


View v = inflater.inflate(R.layout.fragment_form, container, false);

// Obtencin de instancias controles


titulo_input = (EditText) v.findViewById(R.id.titulo_input);
descripcion_input = (EditText) v.findViewById(R.id.descripcion_input);
fecha_text = (TextView) v.findViewById(R.id.fecha_ejemplo_text);
categoria_spinner = (Spinner) v.findViewById(R.id.categoria_spinner);
prioridad_spinner = (Spinner) v.findViewById(R.id.prioridad_spinner);

fecha_text.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {

DialogFragment picker = new DatePickerFragment();


picker.show(getFragmentManager(), "datePicker");

}
}
);

// Obtener valor extra


idMeta = getArguments().getString(EXTRA_ID);

if (idMeta != null) {
cargarDatos();
}

return v;
}

/**
* Obtiene los datos desde el servidor
*/
private void cargarDatos() {
// Aadiendo idMeta como parmetro a la URL
String newURL = Constantes.GET_BY_ID + "?idMeta=" + idMeta;

// Consultar el detalle de la meta


VolleySingleton.getInstance(getActivity()).addToRequestQueue(
new JsonObjectRequest(
Request.Method.GET,
newURL,
null,
new Response.Listener<JSONObject>() {

@Override
public void onResponse(JSONObject response) {
// Procesa la respuesta GET_BY_ID

procesarRespuestaGet(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "Error Volley: " + error.getMessage());
}
}
)
);
}

/**
* Procesa la respuesta de obtencin obtenida desde el sevidor

*/
private void procesarRespuestaGet(JSONObject response) {

try {
String estado = response.getString("estado");

switch (estado) {
case "1":
JSONObject meta = response.getJSONObject("meta");
// Guardar instancia
metaOriginal = gson.fromJson(meta.toString(), Meta.class);
// Setear valores de la meta
cargarViews(metaOriginal);
break;

case "2":
String mensaje = response.getString("mensaje");
// Mostrar mensaje
Toast.makeText(
getActivity(),

mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de falla
getActivity().setResult(Activity.RESULT_CANCELED);
// Terminar actividad
getActivity().finish();
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}

/**
* Carga los datos iniciales del formulario con los
* atributos de un objeto {@link Meta}
*
* @param meta Instancia
*/
private void cargarViews(Meta meta) {
// Seteando valores de la respuesta
titulo_input.setText(meta.getTitulo());
descripcion_input.setText(meta.getDescripcion());
fecha_text.setText(meta.getFechaLim());

// Obteniendo acceso a los array de strings para categorias y prioridades


String[] categorias = getResources().getStringArray(R.array.entradas_categoria);
String[] prioridades = getResources().getStringArray(R.array.entradas_prioridad);

// Obteniendo la posicin del spinner categorias


int posicion_categoria = 0;
for (int i = 0; i < categorias.length; i++) {
if (categorias[i].compareTo(meta.getCategoria()) == 0) {
posicion_categoria = i;

break;
}
}

// Setear seleccin del Spinner de categoras


categoria_spinner.setSelection(posicion_categoria);

// Obteniendo la posicin del spinner de prioridades


int posicion_prioridad = 0;
for (int i = 0; i < prioridades.length; i++) {
Log.d(TAG, "posicin:" + i);
if (prioridades[i].compareTo(meta.getPrioridad()) == 0) {
posicion_prioridad = i;

break;
}
}

// Setear seleccin del Spinner de prioridades


prioridad_spinner.setSelection(posicion_prioridad);
}

/**
* Compara los datos actuales con aquellos que se obtuvieron
* por primera vez en la respuesta HTTP
*
* @return true si los datos no han cambiado, de lo contrario false
*/
public boolean validarCambios() {
return metaOriginal.compararCon(obtenederDatos());
}

/**
* Retorna en una nueva meta creada a partir
* de los datos del formulario actual

*
* @return Instancia {@link Meta}
*/
private Meta obtenederDatos() {

String titulo = titulo_input.getText().toString();


String descripcion = descripcion_input.getText().toString();
String fecha = fecha_text.getText().toString();
String categoria = (String) categoria_spinner.getSelectedItem();
String prioridad = (String) prioridad_spinner.getSelectedItem();

return new Meta("0", titulo, descripcion, fecha, categoria, prioridad);


}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true); // Contribucin a la AB
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

switch (id) {
case android.R.id.home:// CONFIRMAR
if (!validarCambios())
guardarMeta();
else
// Terminar actividad, ya que no hay cambios
getActivity().finish();
return true;

case R.id.action_delete:// ELIMINAR


mostrarDialogo(R.string.dialog_delete_msg);

break;

case R.id.action_discard:// DESCARTAR


if (!validarCambios()) {
mostrarDialogo(R.string.dialog_discard_msg);
} else
// Terminar actividad, ya que no hay cambios
getActivity().finish();
break;

}
;

return super.onOptionsItemSelected(item);
}

/**
* Guarda los cambios de una meta editada.
* <p>
* Si est en modo insercin, entonces crea una nueva
* meta en la base de datos
*/
private void guardarMeta() {

// Obtener valores actuales de los controles


final String titulo = titulo_input.getText().toString();
final String descripcion = descripcion_input.getText().toString();
final String fecha = fecha_text.getText().toString();
final String categoria = categoria_spinner.getSelectedItem().toString();
final String prioridad = prioridad_spinner.getSelectedItem().toString();

HashMap<String, String> map = new HashMap<>();// Mapeo previo

map.put("idMeta", idMeta);
map.put("titulo", titulo);

map.put("descripcion", descripcion);
map.put("fechaLim", fecha);
map.put("categoria", categoria);
map.put("prioridad", prioridad);

// Crear nuevo objeto Json basado en el mapa


JSONObject jobject = new JSONObject(map);

// Depurando objeto Json...


Log.d(TAG, jobject.toString());

// Actualizar datos en el servidor


VolleySingleton.getInstance(getActivity()).addToRequestQueue(
new JsonObjectRequest(
Request.Method.POST,
Constantes.UPDATE,
jobject,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
procesarRespuestaActualizar(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "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();
}
}
);

/**
* Procesa todos las tareas para eliminar
* una meta en la aplicacin. Este mtodo solo se usa
* en la edicin
*/
public void eliminarMeta() {

HashMap<String, String> map = new HashMap<>();// MAPEO

map.put("idMeta", idMeta);// Identificador

JSONObject jobject = new JSONObject(map);// Objeto Json

// Eliminar datos en el servidor


VolleySingleton.getInstance(getActivity()).addToRequestQueue(
new JsonObjectRequest(
Request.Method.POST,
Constantes.DELETE,
jobject,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {

// Procesar la respuesta
procesarRespuestaEliminar(response);

}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "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();
}
}
);
}

/**
* Procesa la respuesta de eliminacin obtenida desde el sevidor
*/
private void procesarRespuestaEliminar(JSONObject response) {

try {

// Obtener estado
String estado = response.getString("estado");
// Obtener mensaje
String mensaje = response.getString("mensaje");

switch (estado) {
case "1":
// Mostrar mensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de xito
getActivity().setResult(203);
// Terminar actividad
getActivity().finish();
break;

case "2":
// Mostrar mensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de falla
getActivity().setResult(Activity.RESULT_CANCELED);
// Terminar actividad
getActivity().finish();
break;
}
} catch (JSONException e) {
e.printStackTrace();
}

/**
* Procesa la respuesta de actualizacin obtenida desde el sevidor
*/
private void procesarRespuestaActualizar(JSONObject response) {

try {
// Obtener estado
String estado = response.getString("estado");
// Obtener mensaje
String mensaje = response.getString("mensaje");

switch (estado) {
case "1":
// Mostrar mensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de xito
getActivity().setResult(Activity.RESULT_OK);
// Terminar actividad
getActivity().finish();
break;

case "2":
// Mostrar mensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de falla
getActivity().setResult(Activity.RESULT_CANCELED);
// Terminar actividad
getActivity().finish();

break;
}
} catch (JSONException e) {
e.printStackTrace();
}

/**
* Actualiza la fecha del campo {@link fecha_text}
*
* @param ano Ao
* @param mes Mes
* @param dia Da
*/
public void actualizarFecha(int ano, int mes, int dia) {
// Setear en el textview la fecha
fecha_text.setText(ano + "-" + (mes + 1) + "-" + dia);
}

/**
* Muestra un dilogo de confirmacin, cuyo mensaje esta
* basado en el parmetro identificador de Strings
*
* @param id Parmetro
*/
private void mostrarDialogo(int id) {
DialogFragment dialogo = ConfirmDialogFragment.
createInstance(
getResources().
getString(id));
dialogo.show(getFragmentManager(), "ConfirmDialog");
}

Este cdigo es un poco largo debido a que tenemos la implementacin de dilogos y


comunicaciones de datos. Por lo que a continuacin te explico la esencia de las peticiones
de informacin.
Cargar los datos de la meta en los componentes del formulario: En el
mtodo onCreateView()obtenemos el valor extra con que fue creado el fragmento. Si
existe un valor extra, lanzamos la misma peticin que hemos usado para conseguir el
detalle de la meta con el mtodo cargarDatos().
Inmediatamente los datos conseguidos en la peticin, los seteamos en cada view del
formulario.
Manejar los eventos en cada action button: Para lograr esta tarea se implement el
mtodoonOptionsItemSelected(), donde se cre una estructura switch que permitiera la
ejecucin del mtodo correspondiente a la accin. Recuerda
usar onHasOptionMenu() en onCreate() para que el fragmento pueda escuchar los eventos
de la action bar.
Implementar la insercin, eliminacin y borrado de las metas: Cada operacin en la
base de datos tiene un mtodo asignado para su realizacin. Estos
son: guardarMeta() y borrarMeta(). El primer mtodo realiza una peticin POST con la
respectiva URL del servicio de actualizacin, usando los valores actuales del formulario.
Similarmente borrarMeta() enva el id de la meta que se desea eliminar hacia la direccin
correspondiente.
En cuanto a los dilogos, simplemente usamos el formato clsico
de ACEPTAR|CANCELAR para permitir o no el efecto de los mtodos en la base de datos.
Puedes encontrar la implementacin completa descargando el cdigo en la parte superior
del artculo.

Paso #11: Realizar peticin para insertar nuevos registros


La insercin de nuevas metas la crearemos en una nueva actividad
llamada InsertActivity junto a un fragmento InsertFragment. Haremos exactamente lo
mismo que hemos venido haciendo.
Iniciaremos el fragmento y estaremos a la espera de que el usuario guarde los datos o los
descarte. Veamos:
InsertActivity.java
import android.os.Bundle;

import android.support.v4.app.DialogFragment;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;

import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.ui.fragmentos.ConfirmDialogFragment;
import com.herprogramacion.iwish.ui.fragmentos.DatePickerFragment;
import com.herprogramacion.iwish.ui.fragmentos.InsertFragment;

public class InsertActivity extends AppCompatActivity


implements DatePickerFragment.OnDateSelectedListener,
ConfirmDialogFragment.ConfirmDialogListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (getSupportActionBar() != null)
getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_done);

// Creacin del fragmento de insercin


if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new InsertFragment(), "InsertFragment")
.commit();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_form, menu);
return true;
}

@Override
public void onDateSelected(int year, int month, int day) {
InsertFragment insertFragment = (InsertFragment)
getSupportFragmentManager().findFragmentByTag("InsertFragment");

if (insertFragment != null) {
insertFragment.actualizarFecha(year, month, day);
}
}

@Override
public void onDialogPositiveClick(DialogFragment dialog) {
InsertFragment insertFragment = (InsertFragment)
getSupportFragmentManager().findFragmentByTag("InsertFragment");

if (insertFragment != null) {
finish(); // Finalizar actividad descartando cambios
}
}

@Override
public void onDialogNegativeClick(DialogFragment dialog) {
InsertFragment insertFragment = (InsertFragment)
getSupportFragmentManager().findFragmentByTag("InsertFragment");

if (insertFragment != null) {
// Nada por el momento
}
}
}

Ahora el fragmento de insercin tiene las siguientes caractersticas:


InsertFragment.java

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
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.herprogramacion.iwish.R;
import com.herprogramacion.iwish.tools.Constantes;
import com.herprogramacion.iwish.web.VolleySingleton;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

/**
* Fragmento que permite al usuario insertar un nueva meta
*/
public class InsertFragment extends Fragment {

/**
* Etiqueta para depuracin
*/
private static final String TAG = InsertFragment.class.getSimpleName();

/*
Controles
*/
EditText titulo_input;
EditText descripcion_input;
Spinner prioridad_spinner;
TextView fecha_text;
Spinner categoria_spinner;

public InsertFragment() {
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Habilitar al fragmento para contribuir en la action bar
setHasOptionsMenu(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflando layout del fragmento
View v = inflater.inflate(R.layout.fragment_form, container, false);

// Obtencin de instancias controles


titulo_input = (EditText) v.findViewById(R.id.titulo_input);
descripcion_input = (EditText) v.findViewById(R.id.descripcion_input);
fecha_text = (TextView) v.findViewById(R.id.fecha_ejemplo_text);
categoria_spinner = (Spinner) v.findViewById(R.id.categoria_spinner);

prioridad_spinner = (Spinner) v.findViewById(R.id.prioridad_spinner);

fecha_text.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
DialogFragment picker = new DatePickerFragment();
picker.show(getFragmentManager(), "datePicker");

}
}
);

return v;
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
// Remover el action button de borrar
menu.removeItem(R.id.action_delete);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

switch (id) {
case android.R.id.home:// CONFIRMAR
if (!camposVacios())
guardarMeta();
else
Toast.makeText(
getActivity(),
"Completa los campos",

Toast.LENGTH_LONG).show();
return true;

case R.id.action_discard:// DESCARTAR


if (!camposVacios())
mostrarDialogo();
else
getActivity().finish();
break;

return super.onOptionsItemSelected(item);
}

/**
* Guarda los cambios de una meta editada.
* <p>
* Si est en modo insercin, entonces crea una nueva
* meta en la base de datos
*/
public void guardarMeta() {

// Obtener valores actuales de los controles


final String titulo = titulo_input.getText().toString();
final String descripcion = descripcion_input.getText().toString();
final String fecha = fecha_text.getText().toString();
final String categoria = categoria_spinner.getSelectedItem().toString();
final String prioridad = prioridad_spinner.getSelectedItem().toString();

HashMap<String, String> map = new HashMap<>();// Mapeo previo

map.put("titulo", titulo);
map.put("descripcion", descripcion);
map.put("fechaLim", fecha);

map.put("categoria", categoria);
map.put("prioridad", prioridad);

// Crear nuevo objeto Json basado en el mapa


JSONObject jobject = new JSONObject(map);

// Depurando objeto Json...


Log.d(TAG, jobject.toString());

// Actualizar datos en el servidor


VolleySingleton.getInstance(getActivity()).addToRequestQueue(
new JsonObjectRequest(
Request.Method.POST,
Constantes.INSERT,
jobject,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// Procesar la respuesta del servidor
procesarRespuesta(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "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();
}
}
);

/**
* Procesa la respuesta obtenida desde el sevidor
*
* @param response Objeto Json
*/
private void procesarRespuesta(JSONObject response) {

try {
// Obtener estado
String estado = response.getString("estado");
// Obtener mensaje
String mensaje = response.getString("mensaje");

switch (estado) {
case "1":
// Mostrar mensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de xito
getActivity().setResult(Activity.RESULT_OK);
// Terminar actividad

getActivity().finish();
break;

case "2":
// Mostrar mensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
// Enviar cdigo de falla
getActivity().setResult(Activity.RESULT_CANCELED);
// Terminar actividad
getActivity().finish();
break;
}
} catch (JSONException e) {
e.printStackTrace();
}

/**
* Valida si los campos {@link titulo_input} y {@link descripcion_input}
* se han rellenado
*
* @return true si alguno o dos de los campos estn vacios, false si ambos
* estn completos
*/
public boolean camposVacios() {
String titulo = titulo_input.getText().toString();
String descripcion = descripcion_input.getText().toString();

return (titulo.isEmpty() || descripcion.isEmpty());


}

/**
* Actualiza la fecha del campo {@link fecha_text}
*
* @param ano Ao
* @param mes Mes
* @param dia Da
*/
public void actualizarFecha(int ano, int mes, int dia) {
// Setear en el textview la fecha
fecha_text.setText(ano + "-" + (mes + 1) + "-" + dia);
}

/**
* Muestra un dilogo de confirmacin
*/
public void mostrarDialogo() {
DialogFragment dialogo = ConfirmDialogFragment.
createInstance(
getResources().
getString(R.string.dialog_discard_msg));
dialogo.show(getFragmentManager(), "ConfirmDialog");
}

Esta vez hemos creado un mtodo llamado guardarMeta() basado en la URL del servicio
de insercin y los datos que el usuario haya completado. Si te fijas en el procesamiento de
los eventos sobre la action bar, puedes ver que existe la posibilidad de guardar y descartar
los datos.
Ambos se basan en la validacin de los campos del formulario que requieren texto escrito
por parte del usuario. Para ello se cre el mtodo camposVacios(). Dependiendo de su
retorno as mismo procederemos.
Esto quiere decir que el usuario no puede guardar una meta sin completar alguno de los
campos. Ni tampoco puede intentar descartar cambios sin ver un dilogo si ya ha escrito
algn dato.

Ejecutar Proyecto Completo En Android Studio


Recuerda que puedes descargar el proyecto completo con el botn que tienes en el inicio
del articulo. Al final, luego de haber seguido todos los pasos la aplicacin se ver as:

Conclusiones
Usar un Web Service en Php permite compartir datos entre tus aplicativos externos y tus
aplicaciones android para mantener un proyecto integral. Sin embargo el uso de un estilo de
comunicacin elegante como REST es un excelente complemento para estructurar una
buena API.

Json es un formato muy flexible y cmodo a la vista. Esto lo hace un excelente


complemento para implementar una API entre Android, Mysql y Php.

You might also like