Anda di halaman 1dari 46

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

Deseas conectaruna 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 y Json.
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.
El cdigo de la aplicacin puedes obtenerlo presionando el siguiente botn:
Descargar Cdigo

1. Qu Es Un Web Service?
Un Web Service o Servicio Web es un aplicativoque facilita lainteroperabilidad 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 laaplicacin 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 formulariopara insercin.
Actividad con fragmento de formulariopara 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

<LinearLayoutxmlns: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 deGradle:

compile'com.melnykov:floatingactionbutton:1.3.0'

Veamos como queda el layout del fragmento principal:


fragment_main.xml

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/resauto"
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 elicono de un FABdebe 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

<?xmlversion="1.0"encoding="utf8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/resauto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<!Partesuperior>
<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>
<!Datosdelameta>
<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="FechaLmite"
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 atributofab: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 el EditText 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 quetiene un
dominio de varias opciones, puedes usar un Spinner .
Veamos:

<RelativeLayoutxmlns: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"/>
<!EtiquetaFecha>
<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"/>
<!EtiquetaCategora>
<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"/>
<!EtiquetaPrioridad>
<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

<?xmlversion="1.0"encoding="utf8"?>
<RelativeLayoutxmlns: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"/>
<!Iconoparalafecha>
<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 deunservidor
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: Siya 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 UTF8 para soportar acentos. Puedes
hacerlo a travs del editor de phpMyAdmin o conel siguiente comando CREATE :

CREATETABLEIFNOTEXISTSmeta(
idMetaint(3)PRIMARYKEYAUTO_INCREMENT,
titulovarchar(56)NOTNULL,
descripcionvarchar(128)NOTNULL,
prioridadenum('Alta','Media','Baja','')NULLDEFAULT'Alta',
fechaLimdateNOTNULL,
categoriaenum('Salud','Finanzas','Espiritual','Profesional','Material')NOTNULLDEFAULT'Finanzas'
)

Luego aade 5 registros de ejemplo en la tabla que permitan probar el funcionamiento en la aplicacin android ms adelante.

INSERTINTO`i_wish`.`meta`(`idMeta`,`titulo`,`descripcion`,`prioridad`,`fechaLim`,`categoria`)
VALUES(NULL,
'ComprarMazda6',
'Deseoadquirirunautomazda6paramidesplazamientoenlaciudad.Deboinvestigarcmoconseguirmasfuentesdeingresos',
'Media',
'20151120',
'Material'),(NULL,
'Obtenermittulodeingenieradesistemas',
'Yasolofaltan2semestresparaterminarmicarreradeingeniera.Deboprepararmealmximoparadesarrollarmitesisdegrado',
'Alta',
'20160617',
'Profesional'),(NULL,
'ConquistaraNatasha',
'Natashaeslamujerdevida.Tengoquedecrseloantesdequeacabeelsemestre',
'Alta',
'20150525',
'Espiritual'),(NULL,
'Tenerunpesode70kg',
'Actualmentepeso92kgyestoyensobrepeso.Sinembargovoyaseguirunarutinadeejerciciosyunrgimenalimenticio',
'Baja',
'20160513',
'Salud'),(NULL,
'Incrementarun30%misingresos',
'Conseguirunafuentedeingresosalternativaquerepresentenun30%delosingresosquereciboactualmente.',
'Media',
'20151013',
'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 delusuario indieteq en
github. l se enfoca en la implementacin del CRUD de una forma muy sencilla y orientada a objetos.
Veamosel resultado del patrn singleton:
Database.php

<?php
/**
*ClasequeenvuelveunainstanciadelaclasePDO
*paraelmanejodelabasededatos
*/
require_once'mysql_login.php';

classDatabase
{
/**
*nicainstanciadelaclase
*/
privatestatic$db=null;
/**
*InstanciadePDO
*/
privatestatic$pdo;
finalprivatefunction__construct()
{
try{
//CrearnuevaconexinPDO
self::getDb();
}catch(PDOException$e){
//Manejodeexcepciones
}

}
/**
*Retornaenlanicainstanciadelaclase
*@returnDatabase|null
*/
publicstaticfunctiongetInstance()
{
if(self::$db===null){
self::$db=newself();
}
returnself::$db;
}
/**
*CrearunanuevaconexinPDObasada
*enlosdatosdeconexin
*@returnPDOObjetoPDO
*/
publicfunctiongetDb()
{
if(self::$pdo==null){
self::$pdo=newPDO(
'mysql:dbname='.DATABASE.
';host='.HOSTNAME.
';port:63343;',
USERNAME,
PASSWORD,
array(PDO::MYSQL_ATTR_INIT_COMMAND=>"SETNAMESutf8")
);
//Habilitarexcepciones
self::$pdo>setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
}
returnself::$pdo;
}
/**
*Evitalaclonacindelobjeto
*/
finalprotectedfunction__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
/**
*Proveelasconstantesparaconectarsealabasededatos
*Mysql.
*/
define("HOSTNAME","localhost");//Nombredelhost
define("DATABASE","i_wish");//Nombredelabasededatos
define("USERNAME","root");//Nombredelusuario
define("PASSWORD","");//Nombredelaconstrasea
?>

Adicionalmente debes aadir al cuarto parmetro del constructor de PDO la indicacin SETNAMESUTF8 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 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
/**
*Representaellaestructuradelasmetas
*almacenadasenlabasededatos
*/
require'Database.php';
classMeta
{
function__construct()
{
}
/**
*Retornaenlafilaespecificadadelatabla'meta'
*
*@param$idMetaIdentificadordelregistro
*@returnarrayDatosdelregistro
*/
publicstaticfunctiongetAll()
{
$consulta="SELECT*FROMmeta";
try{
//Prepararsentencia
$comando=Database::getInstance()>getDb()>prepare($consulta);
//Ejecutarsentenciapreparada
$comando>execute();
return$comando>fetchAll(PDO::FETCH_ASSOC);
}catch(PDOException$e){

returnfalse;
}
}
/**
*Obtieneloscamposdeunametaconunidentificador
*determinado
*
*@param$idMetaIdentificadordelameta
*@returnmixed
*/
publicstaticfunctiongetById($idMeta)
{
//Consultadelameta
$consulta="SELECTidMeta,
titulo,
descripcion,
prioridad,
fechaLim,
categoria
FROMmeta
WHEREidMeta=?";
try{
//Prepararsentencia
$comando=Database::getInstance()>getDb()>prepare($consulta);
//Ejecutarsentenciapreparada
$comando>execute(array($idMeta));
//Capturarprimerafiladelresultado
$row=$comando>fetch(PDO::FETCH_ASSOC);
return$row;
}catch(PDOException$e){
//Aqupuedesclasificarelerrordependiendodelaexcepcin
//parapresentarloenlarespuestaJson
return1;
}
}
/**
*Actualizaunregistrodelabasesdedatosbasado
*enlosnuevosvaloresrelacionadosconunidentificador
*
*@param$idMetaidentificador
*@param$titulonuevotitulo
*@param$descripcionnuevadescripcion
*@param$fechaLimnuevafechalimitedecumplimiento
*@param$categorianuevacategoria
*@param$prioridadnuevaprioridad
*/
publicstaticfunctionupdate(
$idMeta,
$titulo,
$descripcion,
$fechaLim,
$categoria,
$prioridad
)
{
//CreandoconsultaUPDATE
$consulta="UPDATEmeta".
"SETtitulo=?,descripcion=?,fechaLim=?,categoria=?,prioridad=?".
"WHEREidMeta=?";
//Prepararlasentencia
$cmd=Database::getInstance()>getDb()>prepare($consulta);
//Relacionaryejecutarlasentencia
$cmd>execute(array($titulo,$descripcion,$fechaLim,$categoria,$prioridad,$idMeta));
return$cmd;
}
/**
*Insertarunanuevameta
*
*@param$titulotitulodelnuevoregistro

*@param$descripciondescripcindelnuevoregistro
*@param$fechaLimfechalimitedelnuevoregistro
*@param$categoriacategoriadelnuevoregistro
*@param$prioridadprioridaddelnuevoregistro
*@returnPDOStatement
*/
publicstaticfunctioninsert(
$titulo,
$descripcion,
$fechaLim,
$categoria,
$prioridad
)
{
//SentenciaINSERT
$comando="INSERTINTOmeta(".
"titulo,".
"descripcion,".
"fechaLim,".
"categoria,".
"prioridad)".
"VALUES(?,?,?,?,?)";
//Prepararlasentencia
$sentencia=Database::getInstance()>getDb()>prepare($comando);
return$sentencia>execute(
array(
$titulo,
$descripcion,
$fechaLim,
$categoria,
$prioridad
)
);
}
/**
*Eliminarelregistroconelidentificadorespecificado
*
*@param$idMetaidentificadordelameta
*@returnboolRespuestadelaeliminacin
*/
publicstaticfunctiondelete($idMeta)
{
//SentenciaDELETE
$comando="DELETEFROMmetaWHEREidMeta=?";
//Prepararlasentencia
$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 scriptpara obtener todas las metas


Para retornar todos los registros que existen en la tabla "meta" usaremos el mtodo getAll() de la clase Meta . La trata de la peticin seguira
la siguiente lgica:
1. Comprobar que la peticin se realiz con el mtodo GET.
2. Obtener todos los registros.
3. La obtencin tuvo xito?
A. SI -> Retornar objeto Json con los datos
B. 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 (cdigos5xx) 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
datoa un string en formato json y la segunda es el procedimiento contrario.
Veamos nuestro servicio de obtencin:
obtener_metas.php

<?php
/**
*Obtienetodaslasmetasdelabasededatos
*/
require'Meta.php';
if($_SERVER['REQUEST_METHOD']=='GET'){
//ManejarpeticinGET
$metas=Meta::getAll();
if($metas){
$datos["estado"]=1;
$datos["metas"]=$metas;
printjson_encode($datos);
}else{
printjson_decode(array(
"estado"=>2,
"mensaje"=>"Haocurridounerror"
));
}
}

El objeto Json que retornaremos tiene un atributo llamado "estado" el cualrepresenta un cdigo para indicar la calidad del resultado. Si es
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":"Obtenermit\u00edtulodeingenier\u00edadesistemas",
"descripcion":"Yasolofaltan2semestresparaterminarmicarreradeingenier\u00eda.Deboprepararmealm\u00e1ximoparadesarrollarmit
"prioridad":"Media",
"fechaLim":"20150529",
"categoria":"Profesional"
},
{
"idMeta":"3",
"titulo":"ConquistaraNatasha",
"descripcion":"Natashaeslamujerdevida.Tengoquedec\u00edrseloantesdequeacabeelsemestre",

"prioridad":"Alta",
"fechaLim":"20150525",
"categoria":"Espiritual"
}
]
}

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

{
"estado":"2",
"mensaje":"Haocurridounerror"
}

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 enla 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
/**
*Obtieneeldetalledeunametaespecificadapor
*suidentificador"idMeta"
*/
require'Meta.php';
if($_SERVER['REQUEST_METHOD']=='GET'){
if(isset($_GET['idMeta'])){
//ObtenerparmetroidMeta
$parametro=$_GET['idMeta'];
//Tratarretorno
$retorno=Meta::getById($parametro);

if($retorno){
$meta["estado"]="1";
$meta["meta"]=$retorno;
//Enviarobjetojsondelameta
printjson_encode($meta);
}else{
//Enviarrespuestadeerrorgeneral
printjson_encode(
array(
'estado'=>'2',
'mensaje'=>'Noseobtuvoelregistro'
)

);
}
}else{
//Enviarrespuestadeerror
printjson_encode(
array(
'estado'=>'3',
'mensaje'=>'Senecesitaunidentificador'
)
);
}
}

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":"Obtenermit\u00edtulodeingenier\u00edadesistemas",
"descripcion":"Yasolofaltan2semestresparaterminarmicarreradeingenier\u00eda.Deboprepararmealm\u00e1ximoparadesarrollarmitesi
"prioridad":"Media",
"fechaLim":"20150529",
"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":"Noseobtuvoelregistro"
}

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":"Senecesitaunidentificador"
}

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 quedebemos leer los datos en formato Json:

<?php
/**
*Insertarunanuevametaenlabasededatos
*/
require'Meta.php';
if($_SERVER['REQUEST_METHOD']=='POST'){
//DecodificandoformatoJson
$body=json_decode(file_get_contents("php://input"),true);
//Insertarmeta
$retorno=Meta::insert(
$body['titulo'],
$body['descripcion'],

$body['fechaLim'],
$body['categoria'],
$body['prioridad']);
if($retorno){
//Cdigodexito
printjson_encode(
array(
'estado'=>'1',
'mensaje'=>'Creacinexitosa')
);
}else{
//Cdigodefalla
printjson_encode(
array(
'estado'=>'2',
'mensaje'=>'Creacinfallida')
);
}
}

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. Obviamentees necesario queuses la convencin
php://input para accederal 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":"Creacinxitosa"
}

De lo contrario usa un mensaje general de error.

{
"estado":"2",
"mensaje":"Creacinfallida"
}

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
/**
*Actualizaunametaespecificadaporsuidentificador
*/
require'Meta.php';
if($_SERVER['REQUEST_METHOD']=='POST'){
//DecodificandoformatoJson
$body=json_decode(file_get_contents("php://input"),true);
//Actualizarmeta
$retorno=Meta::update(
$body['idMeta'],
$body['titulo'],
$body['descripcion'],
$body['fechaLim'],
$body['categoria'],
$body['prioridad']);
if($retorno){

//Cdigodexito
printjson_encode(
array(
'estado'=>'1',
'mensaje'=>'Actualizacinexitosa')
);
}else{
//Cdigodefalla
printjson_encode(
array(
'estado'=>'2',
'mensaje'=>'Actualizacinfallida')
);
}
}

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

{
"estado":"1",
"mensaje":"Actualizacinxitosa"
}

Al igual que el objeto Json de error:

{
"estado":"2",
"mensaje":"Actualizacinfallida"
}

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


La eliminacin se basa enel 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
/**
*Eliminaunametadelabasededatos
*distinguidaporsuidentificador
*/
require'Meta.php';
if($_SERVER['REQUEST_METHOD']=='POST'){
//DecodificandoformatoJson
$body=json_decode(file_get_contents("php://input"),true);
$retorno=Meta::delete($body['idMeta']);
if($retorno){
printjson_encode(
array(
'estado'=>'1',
'mensaje'=>'Eliminacinexitosa')
);
}else{
printjson_encode(
array(
'estado'=>'2',
'mensaje'=>'Eliminacinfallida')
);
}
}

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

{
"estado":"1",
"mensaje":"Eliminacinxitosa"
}

Y los errores se mostraran as:

{
"estado":"2",
"mensaje":"Eliminacinfallida"
}

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:
Unpatrn 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 del ImageLoader 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

importandroid.content.Context;
importcom.android.volley.Request;
importcom.android.volley.RequestQueue;
importcom.android.volley.toolbox.Volley;
/**
*CreadoporHermosaProgramacin.
*
*ClasequerepresentaunclienteHTTPVolley
*/
publicfinalclassVolleySingleton{
//Atributos
privatestaticVolleySingletonsingleton;
privateRequestQueuerequestQueue;
privatestaticContextcontext;

privateVolleySingleton(Contextcontext){
VolleySingleton.context=context;
requestQueue=getRequestQueue();
}
/**
*Retornalainstanciaunicadelsingleton
*@paramcontextcontextodondeseejecutarnlaspeticiones
*@returnInstancia
*/
publicstaticsynchronizedVolleySingletongetInstance(Contextcontext){
if(singleton==null){
singleton=newVolleySingleton(context.getApplicationContext());
}
returnsingleton;
}
/**
*Obtienelainstanciadelacoladepeticiones
*@returncoladepeticiones

*/
publicRequestQueuegetRequestQueue(){
if(requestQueue==null){
requestQueue=Volley.newRequestQueue(context.getApplicationContext());
}
returnrequestQueue;
}
/**
*Aadelapeticinalacola
*@paramreqpeticin
*@param<T>ResultadofinaldetipoT
*/
public<T>voidaddToRequestQueue(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:

/**
*Clasequecontieneloscdigosusadosen"IWish"para
*mantenerlaintegridadenlasinteraccionesentreactividades
*yfragmentos
*/
publicclassConstantes{
/**
*TransicinHome>Detalle
*/
publicstaticfinalintCODIGO_DETALLE=100;
/**
*TransicinDetalle>Actualizacin
*/
publicstaticfinalintCODIGO_ACTUALIZACION=101;
/**
*URLsdelWebService
*/
publicstaticfinalStringGET="http://10.0.3.2:63343/I%20Wish/obtener_metas.php";
publicstaticfinalStringGET_BY_ID="http://10.0.3.2:63343/I%20Wish/obtener_meta_por_id.php";
publicstaticfinalStringUPDATE="http://10.0.3.2:63343/I%20Wish/actualizar_meta.php";
publicstaticfinalStringDELETE="http://10.0.3.2:63343/I%20Wish/borrar_meta.php";
publicstaticfinalStringINSERT="http://10.0.3.2:63343/I%20Wish/insertar_meta.php";
/**
*Claveparaelvalorextraquerepresentaalidentificadordeunameta
*/
publicstaticfinalStringEXTRA_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 oficial te 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 crearuna 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:

/**
*Reflejodelatabla'meta'enlabasededatos
*/
publicclassMeta{
/*
Atributos

*/
privateStringidMeta;
privateStringtitulo;
privateStringdescripcion;
privateStringprioridad;
privateStringfechaLim;
privateStringcategoria;
publicMeta(StringidMeta,Stringtitulo,Stringdescripcion,Stringprioridad,StringfechaLim,Stringcategoria){
this.idMeta=idMeta;
this.titulo=titulo;
this.descripcion=descripcion;
this.prioridad=prioridad;
this.fechaLim=fechaLim;
this.categoria=categoria;
}
publicStringgetIdMeta(){
returnidMeta;
}
publicStringgetTitulo(){
returntitulo;
}
publicStringgetDescripcion(){
returndescripcion;
}
publicStringgetPrioridad(){
returnprioridad;
}
publicStringgetFechaLim(){
returnfechaLim;
}
publicStringgetCategoria(){
returncategoria;
}
/**
*Comparalosatributosdedosmetas
*@parammetaMetaexterna
*@returntruesisoniguales,falsesihaycambios
*/
publicbooleancompararCon(Metameta){
returnthis.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 holderuna 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.

importandroid.app.Activity;
importandroid.content.Context;
importandroid.support.v7.widget.RecyclerView;
importandroid.view.LayoutInflater;

importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.TextView;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.modelo.Meta;
importcom.herprogramacion.iwish.ui.actividades.DetailActivity;
importjava.util.List;
/**
*Adaptadordelrecyclerview
*/
publicclassMetaAdapterextendsRecyclerView.Adapter<MetaAdapter.MetaViewHolder>
implementsItemClickListener{
/**
*Listadeobjetos{@linkMeta}querepresentanlafuentededatos
*deinflado
*/
privateList<Meta>items;
/*
Contextodondeactuaelrecyclerview
*/
privateContextcontext;

publicMetaAdapter(List<Meta>items,Contextcontext){
this.context=context;
this.items=items;
}
@Override
publicintgetItemCount(){
returnitems.size();
}
@Override
publicMetaViewHolderonCreateViewHolder(ViewGroupviewGroup,inti){
Viewv=LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_list,viewGroup,false);
returnnewMetaViewHolder(v,this);
}
@Override
publicvoidonBindViewHolder(MetaViewHolderviewHolder,inti){
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());
}
/**
*Sobrescrituradelmtododelainterfaz{@linkItemClickListener}
*
*@paramviewitemactual
*@parampositionposicindelitemactual
*/
@Override
publicvoidonItemClick(Viewview,intposition){
DetailActivity.launch(
(Activity)context,items.get(position).getIdMeta());
}

publicstaticclassMetaViewHolderextendsRecyclerView.ViewHolder
implementsView.OnClickListener{
//Camposrespectivosdeunitem
publicTextViewtitulo;
publicTextViewprioridad;
publicTextViewfechaLim;
publicTextViewcategoria;
publicItemClickListenerlistener;
publicMetaViewHolder(Viewv,ItemClickListenerlistener){
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
publicvoidonClick(Viewv){
listener.onItemClick(v,getAdapterPosition());
}
}
}

interfaceItemClickListener{
voidonItemClick(Viewview,intposition);
}

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 evitarsea 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 lascaractersticas necesariasy 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 :

importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.ui.fragmentos.MainFragment;
/**
*Actividadprincipalquecontieneunfragmentoconunalista.
*Recuerdaquelanuevalibreradesoportereemplazlaclase
*{@linkandroid.support.v7.app.ActionBarActivity}por
*{@linkAppCompatActivity}paraelusodelaactionbar
*enversionesantiguas.
*/
publicclassMainActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Creacindelfragmentoprincipal
if(savedInstanceState==null){
getSupportFragmentManager().beginTransaction()
.add(R.id.container,newMainFragment(),"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:

importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.support.v7.widget.LinearLayoutManager;
importandroid.support.v7.widget.RecyclerView;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.Toast;
importcom.android.volley.Request;
importcom.android.volley.Response;
importcom.android.volley.VolleyError;
importcom.android.volley.toolbox.JsonObjectRequest;
importcom.google.gson.Gson;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.modelo.Meta;
importcom.herprogramacion.iwish.tools.Constantes;
importcom.herprogramacion.iwish.ui.MetaAdapter;
importcom.herprogramacion.iwish.ui.actividades.InsertActivity;
importcom.herprogramacion.iwish.web.VolleySingleton;
importorg.json.JSONArray;
importorg.json.JSONException;
importorg.json.JSONObject;
importjava.util.Arrays;

/**
*Fragmentoprincipalquecontienelalistadelasmetas
*/
publicclassMainFragmentextendsFragment{
/*
Etiquetadedepuracion
*/
privatestaticfinalStringTAG=MainFragment.class.getSimpleName();
/*
Adaptadordelrecyclerview
*/
privateMetaAdapteradapter;
/*
Instanciaglobaldelrecyclerview
*/
privateRecyclerViewlista;
/*
instanciaglobaldeladministrador
*/
privateRecyclerView.LayoutManagerlManager;
/*
InstanciaglobaldelFAB
*/
com.melnykov.fab.FloatingActionButtonfab;
privateGsongson=newGson();
publicMainFragment(){
}
@Override
publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
BundlesavedInstanceState){
Viewv=inflater.inflate(R.layout.fragment_main,container,false);
lista=(RecyclerView)v.findViewById(R.id.reciclador);
lista.setHasFixedSize(true);
//UsarunadministradorparaLinearLayout
lManager=newLinearLayoutManager(getActivity());
lista.setLayoutManager(lManager);

//Cargardatoseneladaptador
cargarAdaptador();
//ObtenerinstanciadelFAB
fab=(com.melnykov.fab.FloatingActionButton)v.findViewById(R.id.fab);
//AsignarescuchaalFAB
fab.setOnClickListener(
newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
//Iniciaractividaddeinsercin
getActivity().startActivityForResult(
newIntent(getActivity(),InsertActivity.class),3);
}
}
);
returnv;
}
/**
*Cargaeladaptadorconlasmetasobtenidas
*enlarespuesta
*/
publicvoidcargarAdaptador(){
//PeticinGET
VolleySingleton.
getInstance(getActivity()).
addToRequestQueue(
newJsonObjectRequest(
Request.Method.GET,
Constantes.GET,
null,
newResponse.Listener<JSONObject>(){
@Override
publicvoidonResponse(JSONObjectresponse){
//ProcesarlarespuestaJson
procesarRespuesta(response);
}
},
newResponse.ErrorListener(){
@Override
publicvoidonErrorResponse(VolleyErrorerror){
Log.d(TAG,"ErrorVolley:"+error.getMessage());
}
}
)
);
}
/**
*Interpretalosresultadosdelarespuestayas
*realizarlasoperacionescorrespondientes
*
*@paramresponseObjetoJsonconlarespuesta
*/
privatevoidprocesarRespuesta(JSONObjectresponse){
try{
//Obteneratributo"estado"
Stringestado=response.getString("estado");
switch(estado){
case"1"://EXITO
//Obtenerarray"metas"Json
JSONArraymensaje=response.getJSONArray("metas");
//ParsearconGson
Meta[]metas=gson.fromJson(mensaje.toString(),Meta[].class);
//Inicializaradaptador
adapter=newMetaAdapter(Arrays.asList(metas),getActivity());
//Setearadaptadoralalista
lista.setAdapter(adapter);
break;
case"2"://FALLIDO
Stringmensaje2=response.getString("mensaje");

Toast.makeText(
getActivity(),
mensaje2,
Toast.LENGTH_LONG).show();
break;
}
}catch(JSONExceptione){
e.printStackTrace();
}
}

El cdigo anterior muestra el usode 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 objeto JSONObject en bruto para comenzar el
parsing. Donde he divido los caminos a travs de una estructura switch 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 librera Gson.
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

importandroid.app.Activity;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.view.Menu;
importandroid.view.MenuItem;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.tools.Constantes;
importcom.herprogramacion.iwish.ui.fragmentos.DetailFragment;
/**
*Estaactividadcontieneunfragmentoquemuestraeldetalle
*delasmetas.
*/
publicclassDetailActivityextendsAppCompatActivity{
/*
Valorextraqueidentificaalametaadetallar
*/
privatestaticfinalStringEXTRA_ID="IDMETA";
/**
*Instanciaglobaldelametaadetallar
*/
privateStringidMeta;
/**
*Iniciaunanuevainstanciadelaactividad
*
*@paramactivityContextodesdedondeselanzar
*@paramidMetaIdentificadordelametaadetallar

*/
publicstaticvoidlaunch(Activityactivity,StringidMeta){
Intentintent=getLaunchIntent(activity,idMeta);
activity.startActivityForResult(intent,Constantes.CODIGO_DETALLE);
}
/**
*ConstruyeunIntentapartirdelcontextoylaactividad
*dedetalle.
*
*@paramcontextContextodondeseinicia
*@paramidMetaIdentificadordelameta
*@returnIntentlistoparausar
*/
publicstaticIntentgetLaunchIntent(Contextcontext,StringidMeta){
Intentintent=newIntent(context,DetailActivity.class);
intent.putExtra(EXTRA_ID,idMeta);
returnintent;
}
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(getSupportActionBar()!=null){
//Dehabilitartitulodelaactividad
getSupportActionBar().setDisplayShowTitleEnabled(false);
//Setearcono"X"comoUpbutton
getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close);
}
//Retenerinstancia
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
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_detail,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
switch(id){
caseandroid.R.id.home:
finish();
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
}

Este cdigo tiene varias cosas interesantes. En primera instancia el uso de un mtodo esttico llamado launch() , el cual construye una
instancia de la actividad de detalle y la inicia a travs de un Intent construido 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 embargopara este ejemplo el usuario
tiene dos caminos evidentes:
Cerrarel detallecon 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 delapeticin 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

importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ImageButton;
importandroid.widget.ImageView;
importandroid.widget.TextView;
importandroid.widget.Toast;
importcom.android.volley.Request;
importcom.android.volley.Response;
importcom.android.volley.VolleyError;
importcom.android.volley.toolbox.JsonObjectRequest;
importcom.google.gson.Gson;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.modelo.Meta;
importcom.herprogramacion.iwish.tools.Constantes;
importcom.herprogramacion.iwish.ui.actividades.UpdateActivity;
importcom.herprogramacion.iwish.web.VolleySingleton;
importorg.json.JSONException;
importorg.json.JSONObject;
/**
*Aplaceholderfragmentcontainingasimpleview.
*/
publicclassDetailFragmentextendsFragment{
/*
Etiquetadevalorextra
*/
privatestaticfinalStringEXTRA_ID="IDMETA";
/**
*Etiquetadedepuracin
*/
privatestaticfinalStringTAG=DetailFragment.class.getSimpleName();
/*
InstanciasdeViews
*/
privateImageViewcabecera;
privateTextViewtitulo;
privateTextViewdescripcion;
privateTextViewprioridad;
privateTextViewfechaLim;

privateTextViewcategoria;
privateImageButtoneditButton;
privateStringextra;
privateGsongson=newGson();
publicDetailFragment(){
}
publicstaticDetailFragmentcreateInstance(StringidMeta){
DetailFragmentdetailFragment=newDetailFragment();
Bundlebundle=newBundle();
bundle.putString(EXTRA_ID,idMeta);
detailFragment.setArguments(bundle);
returndetailFragment;
}
@Override
publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
BundlesavedInstanceState){
Viewv=inflater.inflate(R.layout.fragment_detail,container,false);
//Obtencindeviews
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);
//Setearescuchaparaelfab
editButton.setOnClickListener(
newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
//Iniciaractividaddeactualizacin
Intenti=newIntent(getActivity(),UpdateActivity.class);
i.putExtra(EXTRA_ID,extra);
getActivity().startActivityForResult(i,Constantes.CODIGO_ACTUALIZACION);
}
}
);
//Obtenerextradelintentdeenvo
extra=getArguments().getString(EXTRA_ID);
//Cargardatosdesdeelwebservice
cargarDatos();
returnv;
}
/**
*Obtienelosdatosdesdeelservidor
*/
publicvoidcargarDatos(){
//AadirparmetroalaURLdelwebservice
StringnewURL=Constantes.GET_BY_ID+"?idMeta="+extra;
//RealizarpeticinGET_BY_ID
VolleySingleton.getInstance(getActivity()).addToRequestQueue(
newJsonObjectRequest(
Request.Method.GET,
newURL,
null,
newResponse.Listener<JSONObject>(){
@Override
publicvoidonResponse(JSONObjectresponse){
//ProcesarrespuestaJson
procesarRespuesta(response);
}
},
newResponse.ErrorListener(){
@Override
publicvoidonErrorResponse(VolleyErrorerror){

Log.d(TAG,"ErrorVolley:"+error.getMessage());
}
}
)
);
}
/**
*Procesacadaunodelosestadosposiblesdela
*respuestaenviadadesdeelservidor
*
*@paramresponseObjetoJson
*/
privatevoidprocesarRespuesta(JSONObjectresponse){
try{
//Obteneratributo"mensaje"
Stringmensaje=response.getString("estado");
switch(mensaje){
case"1":
//Obtenerobjeto"meta"
JSONObjectobject=response.getJSONObject("meta");
//Parsearobjeto
Metameta=gson.fromJson(object.toString(),Meta.class);
//Asignarcolordelfondo
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;
}
//Seteandovaloresenlosviews
titulo.setText(meta.getTitulo());
descripcion.setText(meta.getDescripcion());
prioridad.setText(meta.getPrioridad());
fechaLim.setText(meta.getFechaLim());
categoria.setText(meta.getCategoria());
break;
case"2":
Stringmensaje2=response.getString("mensaje");
Toast.makeText(
getActivity(),
mensaje2,
Toast.LENGTH_LONG).show();
break;
case"3":
Stringmensaje3=response.getString("mensaje");
Toast.makeText(
getActivity(),
mensaje3,
Toast.LENGTH_LONG).show();
break;
}

}catch(JSONExceptione){
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 losdatos 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

<menuxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/resauto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.herprogramacion.iwish.ui.actividades.UpdateActivity">
<!DescartarCambios>
<itemandroid:id="@+id/action_discard"android:title="@string/action_discard"
android:orderInCategory="3"app:showAsAction="never"/>
<!Borrar>
<itemandroid: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 porimplementar 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

importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.support.v4.app.DialogFragment;
importandroid.support.v4.app.Fragment;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.EditText;
importandroid.widget.Spinner;
importandroid.widget.TextView;
importandroid.widget.Toast;
importcom.android.volley.Request;
importcom.android.volley.Response;
importcom.android.volley.VolleyError;
importcom.android.volley.toolbox.JsonObjectRequest;

importcom.google.gson.Gson;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.modelo.Meta;
importcom.herprogramacion.iwish.tools.Constantes;
importcom.herprogramacion.iwish.web.VolleySingleton;
importorg.json.JSONException;
importorg.json.JSONObject;
importjava.util.HashMap;
importjava.util.Map;

/**
*Fragmentoconformularioparaactualizarlameta
*/
publicclassUpdateFragmentextendsFragment{
/*
Etiquetadedepuracin
*/
privatestaticfinalStringTAG=UpdateFragment.class.getSimpleName();
/*
Etiquetadevalorextraparamodoedicin
*/
privatestaticfinalStringEXTRA_ID="IDMETA";
/*
Controles
*/
privateEditTexttitulo_input;
privateEditTextdescripcion_input;
privateSpinnerprioridad_spinner;
privateTextViewfecha_text;
privateSpinnercategoria_spinner;
/*
Valordelargumentoextra
*/
privateStringidMeta;
/**
*EslametaobtenidacomorespuestadelapeticinHTTP
*/
privateMetametaOriginal;
/**
*InstanciaGsonparaelparsingJson
*/
privateGsongson=newGson();

publicUpdateFragment(){
}
/**
*Creaunnuevofragmentobasadoenunargumento
*
*@paramextraArgumentodeentrada
*@returnNuevofragmento
*/
publicstaticFragmentcreateInstance(Stringextra){
UpdateFragmentdetailFragment=newUpdateFragment();
Bundlebundle=newBundle();
bundle.putString(EXTRA_ID,extra);
detailFragment.setArguments(bundle);
returndetailFragment;
}
@Override
publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
BundlesavedInstanceState){
//Inflandolayoutdelfragmento
Viewv=inflater.inflate(R.layout.fragment_form,container,false);
//Obtencindeinstanciascontroles

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(
newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
DialogFragmentpicker=newDatePickerFragment();
picker.show(getFragmentManager(),"datePicker");
}
}
);
//Obtenervalorextra
idMeta=getArguments().getString(EXTRA_ID);
if(idMeta!=null){
cargarDatos();
}
returnv;
}
/**
*Obtienelosdatosdesdeelservidor
*/
privatevoidcargarDatos(){
//AadiendoidMetacomoparmetroalaURL
StringnewURL=Constantes.GET_BY_ID+"?idMeta="+idMeta;
//Consultareldetalledelameta
VolleySingleton.getInstance(getActivity()).addToRequestQueue(
newJsonObjectRequest(
Request.Method.GET,
newURL,
null,
newResponse.Listener<JSONObject>(){
@Override
publicvoidonResponse(JSONObjectresponse){
//ProcesalarespuestaGET_BY_ID
procesarRespuestaGet(response);
}
},
newResponse.ErrorListener(){
@Override
publicvoidonErrorResponse(VolleyErrorerror){
Log.d(TAG,"ErrorVolley:"+error.getMessage());
}
}
)
);
}
/**
*Procesalarespuestadeobtencinobtenidadesdeelsevidor*
*/
privatevoidprocesarRespuestaGet(JSONObjectresponse){
try{
Stringestado=response.getString("estado");
switch(estado){
case"1":
JSONObjectmeta=response.getJSONObject("meta");
//Guardarinstancia
metaOriginal=gson.fromJson(meta.toString(),Meta.class);
//Setearvaloresdelameta
cargarViews(metaOriginal);
break;
case"2":
Stringmensaje=response.getString("mensaje");

//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodefalla
getActivity().setResult(Activity.RESULT_CANCELED);
//Terminaractividad
getActivity().finish();
break;
}
}catch(JSONExceptione){
e.printStackTrace();
}
}
/**
*Cargalosdatosinicialesdelformularioconlos
*atributosdeunobjeto{@linkMeta}
*
*@parammetaInstancia
*/
privatevoidcargarViews(Metameta){
//Seteandovaloresdelarespuesta
titulo_input.setText(meta.getTitulo());
descripcion_input.setText(meta.getDescripcion());
fecha_text.setText(meta.getFechaLim());

//Obteniendoaccesoalosarraydestringsparacategoriasyprioridades
String[]categorias=getResources().getStringArray(R.array.entradas_categoria);
String[]prioridades=getResources().getStringArray(R.array.entradas_prioridad);
//Obteniendolaposicindelspinnercategorias
intposicion_categoria=0;
for(inti=0;i<categorias.length;i++){
if(categorias[i].compareTo(meta.getCategoria())==0){
posicion_categoria=i;
break;
}
}
//SetearseleccindelSpinnerdecategoras
categoria_spinner.setSelection(posicion_categoria);
//Obteniendolaposicindelspinnerdeprioridades
intposicion_prioridad=0;
for(inti=0;i<prioridades.length;i++){
Log.d(TAG,"posicin:"+i);
if(prioridades[i].compareTo(meta.getPrioridad())==0){
posicion_prioridad=i;
break;
}
}
//SetearseleccindelSpinnerdeprioridades
prioridad_spinner.setSelection(posicion_prioridad);
}
/**
*Comparalosdatosactualesconaquellosqueseobtuvieron
*porprimeravezenlarespuestaHTTP
*
*@returntruesilosdatosnohancambiado,delocontrariofalse
*/
publicbooleanvalidarCambios(){
returnmetaOriginal.compararCon(obtenederDatos());
}
/**
*Retornaenunanuevametacreadaapartir
*delosdatosdelformularioactual
*
*@returnInstancia{@linkMeta}
*/
privateMetaobtenederDatos(){

Stringtitulo=titulo_input.getText().toString();
Stringdescripcion=descripcion_input.getText().toString();
Stringfecha=fecha_text.getText().toString();
Stringcategoria=(String)categoria_spinner.getSelectedItem();
Stringprioridad=(String)prioridad_spinner.getSelectedItem();
returnnewMeta("0",titulo,descripcion,fecha,categoria,prioridad);
}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);//ContribucinalaAB
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
switch(id){
caseandroid.R.id.home://CONFIRMAR
if(!validarCambios())
guardarMeta();
else
//Terminaractividad,yaquenohaycambios
getActivity().finish();
returntrue;
caseR.id.action_delete://ELIMINAR
mostrarDialogo(R.string.dialog_delete_msg);
break;
caseR.id.action_discard://DESCARTAR
if(!validarCambios()){
mostrarDialogo(R.string.dialog_discard_msg);
}else
//Terminaractividad,yaquenohaycambios
getActivity().finish();
break;
}
;
returnsuper.onOptionsItemSelected(item);
}
/**
*Guardaloscambiosdeunametaeditada.
*<p>
*Siestenmodoinsercin,entoncescreaunanueva
*metaenlabasededatos
*/
privatevoidguardarMeta(){
//Obtenervaloresactualesdeloscontroles
finalStringtitulo=titulo_input.getText().toString();
finalStringdescripcion=descripcion_input.getText().toString();
finalStringfecha=fecha_text.getText().toString();
finalStringcategoria=categoria_spinner.getSelectedItem().toString();
finalStringprioridad=prioridad_spinner.getSelectedItem().toString();
HashMap<String,String>map=newHashMap<>();//Mapeoprevio
map.put("idMeta",idMeta);
map.put("titulo",titulo);
map.put("descripcion",descripcion);
map.put("fechaLim",fecha);
map.put("categoria",categoria);
map.put("prioridad",prioridad);
//CrearnuevoobjetoJsonbasadoenelmapa
JSONObjectjobject=newJSONObject(map);
//DepurandoobjetoJson...
Log.d(TAG,jobject.toString());

//Actualizardatosenelservidor
VolleySingleton.getInstance(getActivity()).addToRequestQueue(
newJsonObjectRequest(
Request.Method.POST,
Constantes.UPDATE,
jobject,
newResponse.Listener<JSONObject>(){
@Override
publicvoidonResponse(JSONObjectresponse){
procesarRespuestaActualizar(response);
}
},
newResponse.ErrorListener(){
@Override
publicvoidonErrorResponse(VolleyErrorerror){
Log.d(TAG,"ErrorVolley:"+error.getMessage());
}
}
){
@Override
publicMap<String,String>getHeaders(){
Map<String,String>headers=newHashMap<String,String>();
headers.put("ContentType","application/json;charset=utf8");
headers.put("Accept","application/json");
returnheaders;
}
@Override
publicStringgetBodyContentType(){
return"application/json;charset=utf8"+getParamsEncoding();
}
}
);
}
/**
*Procesatodoslastareasparaeliminar
*unametaenlaaplicacin.Estemtodosoloseusa
*enlaedicin
*/
publicvoideliminarMeta(){
HashMap<String,String>map=newHashMap<>();//MAPEO
map.put("idMeta",idMeta);//Identificador
JSONObjectjobject=newJSONObject(map);//ObjetoJson
//Eliminardatosenelservidor
VolleySingleton.getInstance(getActivity()).addToRequestQueue(
newJsonObjectRequest(
Request.Method.POST,
Constantes.DELETE,
jobject,
newResponse.Listener<JSONObject>(){
@Override
publicvoidonResponse(JSONObjectresponse){
//Procesarlarespuesta
procesarRespuestaEliminar(response);
}
},
newResponse.ErrorListener(){
@Override
publicvoidonErrorResponse(VolleyErrorerror){
Log.d(TAG,"ErrorVolley:"+error.getMessage());
}
}
){
@Override
publicMap<String,String>getHeaders(){
Map<String,String>headers=newHashMap<String,String>();
headers.put("ContentType","application/json;charset=utf8");
headers.put("Accept","application/json");

returnheaders;
}
@Override
publicStringgetBodyContentType(){
return"application/json;charset=utf8"+getParamsEncoding();
}
}
);
}
/**
*Procesalarespuestadeeliminacinobtenidadesdeelsevidor
*/
privatevoidprocesarRespuestaEliminar(JSONObjectresponse){
try{
//Obtenerestado
Stringestado=response.getString("estado");
//Obtenermensaje
Stringmensaje=response.getString("mensaje");
switch(estado){
case"1":
//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodexito
getActivity().setResult(203);
//Terminaractividad
getActivity().finish();
break;
case"2":
//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodefalla
getActivity().setResult(Activity.RESULT_CANCELED);
//Terminaractividad
getActivity().finish();
break;
}
}catch(JSONExceptione){
e.printStackTrace();
}
}
/**
*Procesalarespuestadeactualizacinobtenidadesdeelsevidor
*/
privatevoidprocesarRespuestaActualizar(JSONObjectresponse){
try{
//Obtenerestado
Stringestado=response.getString("estado");
//Obtenermensaje
Stringmensaje=response.getString("mensaje");
switch(estado){
case"1":
//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodexito
getActivity().setResult(Activity.RESULT_OK);
//Terminaractividad
getActivity().finish();
break;

case"2":
//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodefalla
getActivity().setResult(Activity.RESULT_CANCELED);
//Terminaractividad
getActivity().finish();
break;
}
}catch(JSONExceptione){
e.printStackTrace();
}
}
/**
*Actualizalafechadelcampo{@linkfecha_text}
*
*@paramanoAo
*@parammesMes
*@paramdiaDa
*/
publicvoidactualizarFecha(intano,intmes,intdia){
//Seteareneltextviewlafecha
fecha_text.setText(ano+""+(mes+1)+""+dia);
}
/**
*Muestraundilogodeconfirmacin,cuyomensajeesta
*basadoenelparmetroidentificadordeStrings
*
*@paramidParmetro
*/
privatevoidmostrarDialogo(intid){
DialogFragmentdialogo=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 acontinuacin 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 mtodo onOptionsItemSelected() , 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

importandroid.os.Bundle;
importandroid.support.v4.app.DialogFragment;
importandroid.support.v7.app.AppCompatActivity;
importandroid.view.Menu;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.ui.fragmentos.ConfirmDialogFragment;
importcom.herprogramacion.iwish.ui.fragmentos.DatePickerFragment;
importcom.herprogramacion.iwish.ui.fragmentos.InsertFragment;
publicclassInsertActivityextendsAppCompatActivity
implementsDatePickerFragment.OnDateSelectedListener,
ConfirmDialogFragment.ConfirmDialogListener{

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(getSupportActionBar()!=null)
getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_done);
//Creacindelfragmentodeinsercin
if(savedInstanceState==null){
getSupportFragmentManager().beginTransaction()
.add(R.id.container,newInsertFragment(),"InsertFragment")
.commit();
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_form,menu);
returntrue;
}
@Override
publicvoidonDateSelected(intyear,intmonth,intday){
InsertFragmentinsertFragment=(InsertFragment)
getSupportFragmentManager().findFragmentByTag("InsertFragment");
if(insertFragment!=null){
insertFragment.actualizarFecha(year,month,day);
}
}
@Override
publicvoidonDialogPositiveClick(DialogFragmentdialog){
InsertFragmentinsertFragment=(InsertFragment)
getSupportFragmentManager().findFragmentByTag("InsertFragment");
if(insertFragment!=null){
finish();//Finalizaractividaddescartandocambios
}
}
@Override
publicvoidonDialogNegativeClick(DialogFragmentdialog){
InsertFragmentinsertFragment=(InsertFragment)
getSupportFragmentManager().findFragmentByTag("InsertFragment");
if(insertFragment!=null){
//Nadaporelmomento
}
}
}

Ahora el fragmento de insercin tiene las siguientes caractersticas:


InsertFragment.java

importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.support.v4.app.DialogFragment;
importandroid.support.v4.app.Fragment;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.Menu;
importandroid.view.MenuInflater;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.EditText;
importandroid.widget.Spinner;
importandroid.widget.TextView;
importandroid.widget.Toast;
importcom.android.volley.Request;
importcom.android.volley.Response;
importcom.android.volley.VolleyError;
importcom.android.volley.toolbox.JsonObjectRequest;
importcom.herprogramacion.iwish.R;
importcom.herprogramacion.iwish.tools.Constantes;
importcom.herprogramacion.iwish.web.VolleySingleton;
importorg.json.JSONException;
importorg.json.JSONObject;
importjava.util.HashMap;
importjava.util.Map;

/**
*Fragmentoquepermitealusuarioinsertarunnuevameta
*/
publicclassInsertFragmentextendsFragment{
/**
*Etiquetaparadepuracin
*/
privatestaticfinalStringTAG=InsertFragment.class.getSimpleName();
/*
Controles
*/
EditTexttitulo_input;
EditTextdescripcion_input;
Spinnerprioridad_spinner;
TextViewfecha_text;
Spinnercategoria_spinner;
publicInsertFragment(){
}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
//Habilitaralfragmentoparacontribuirenlaactionbar
setHasOptionsMenu(true);
}
@Override
publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
BundlesavedInstanceState){
//Inflandolayoutdelfragmento
Viewv=inflater.inflate(R.layout.fragment_form,container,false);
//Obtencindeinstanciascontroles
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(
newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
DialogFragmentpicker=newDatePickerFragment();
picker.show(getFragmentManager(),"datePicker");
}
}
);
returnv;
}
@Override
publicvoidonCreateOptionsMenu(Menumenu,MenuInflaterinflater){
super.onCreateOptionsMenu(menu,inflater);
//Removerelactionbuttondeborrar
menu.removeItem(R.id.action_delete);
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
switch(id){
caseandroid.R.id.home://CONFIRMAR
if(!camposVacios())
guardarMeta();
else
Toast.makeText(
getActivity(),
"Completaloscampos",
Toast.LENGTH_LONG).show();
returntrue;
caseR.id.action_discard://DESCARTAR
if(!camposVacios())
mostrarDialogo();
else
getActivity().finish();
break;
}
returnsuper.onOptionsItemSelected(item);
}
/**
*Guardaloscambiosdeunametaeditada.
*<p>
*Siestenmodoinsercin,entoncescreaunanueva
*metaenlabasededatos
*/
publicvoidguardarMeta(){
//Obtenervaloresactualesdeloscontroles
finalStringtitulo=titulo_input.getText().toString();
finalStringdescripcion=descripcion_input.getText().toString();
finalStringfecha=fecha_text.getText().toString();
finalStringcategoria=categoria_spinner.getSelectedItem().toString();
finalStringprioridad=prioridad_spinner.getSelectedItem().toString();
HashMap<String,String>map=newHashMap<>();//Mapeoprevio
map.put("titulo",titulo);
map.put("descripcion",descripcion);
map.put("fechaLim",fecha);
map.put("categoria",categoria);
map.put("prioridad",prioridad);
//CrearnuevoobjetoJsonbasadoenelmapa
JSONObjectjobject=newJSONObject(map);
//DepurandoobjetoJson...
Log.d(TAG,jobject.toString());

//Actualizardatosenelservidor
VolleySingleton.getInstance(getActivity()).addToRequestQueue(
newJsonObjectRequest(
Request.Method.POST,
Constantes.INSERT,
jobject,
newResponse.Listener<JSONObject>(){
@Override
publicvoidonResponse(JSONObjectresponse){
//Procesarlarespuestadelservidor
procesarRespuesta(response);
}
},
newResponse.ErrorListener(){
@Override
publicvoidonErrorResponse(VolleyErrorerror){
Log.d(TAG,"ErrorVolley:"+error.getMessage());
}
}
){
@Override
publicMap<String,String>getHeaders(){
Map<String,String>headers=newHashMap<String,String>();
headers.put("ContentType","application/json;charset=utf8");
headers.put("Accept","application/json");
returnheaders;
}
@Override
publicStringgetBodyContentType(){
return"application/json;charset=utf8"+getParamsEncoding();
}
}
);
}
/**
*Procesalarespuestaobtenidadesdeelsevidor
*
*@paramresponseObjetoJson
*/
privatevoidprocesarRespuesta(JSONObjectresponse){
try{
//Obtenerestado
Stringestado=response.getString("estado");
//Obtenermensaje
Stringmensaje=response.getString("mensaje");
switch(estado){
case"1":
//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodexito
getActivity().setResult(Activity.RESULT_OK);
//Terminaractividad
getActivity().finish();
break;
case"2":
//Mostrarmensaje
Toast.makeText(
getActivity(),
mensaje,
Toast.LENGTH_LONG).show();
//Enviarcdigodefalla
getActivity().setResult(Activity.RESULT_CANCELED);
//Terminaractividad
getActivity().finish();
break;
}

}catch(JSONExceptione){
e.printStackTrace();
}
}
/**
*Validasiloscampos{@linktitulo_input}y{@linkdescripcion_input}
*sehanrellenado
*
*@returntruesialgunoodosdeloscamposestnvacios,falsesiambos
*estncompletos
*/
publicbooleancamposVacios(){
Stringtitulo=titulo_input.getText().toString();
Stringdescripcion=descripcion_input.getText().toString();
return(titulo.isEmpty()||descripcion.isEmpty());
}
/**
*Actualizalafechadelcampo{@linkfecha_text}
*
*@paramanoAo
*@parammesMes
*@paramdiaDa
*/
publicvoidactualizarFecha(intano,intmes,intdia){
//Seteareneltextviewlafecha
fecha_text.setText(ano+""+(mes+1)+""+dia);
}
/**
*Muestraundilogodeconfirmacin
*/
publicvoidmostrarDialogo(){
DialogFragmentdialogo=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 implementaruna API entre Android, Mysql y
Php.
Aade caching de informacin a travs de SQLite para evitar realizar gran cantidad de operaciones de red.
Usa la clase ContentProvider para completar tu patrn MVC de Red y aadir restricciones RESTful en tu aplicacin. Esto te permitir
independizar tus clases y evitar problemas en tu hilo principal de forma sencilla.

Anda mungkin juga menyukai