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 aplicación Android a Mysql?
¿Has intentado crear un web service con Php para la comunicación de datos de tu
aplicativo web con tu aplicativo móvil android, pero aún no comprendes bien cómo
hacerlo?
Pues bien, en este artículo te mostraré algunas ideas sobre la creación de una aplicación
android que consuma los datos de un servidor externo a través de Php, Mysql yJson.
Para ello he creado una aplicación llamada “I Wish”, la cual permite a nuestros usuarios
guardar una lista de deseos y metas que tienen en su vida. Con este ejemplo podrás ver
cómo implementar la inserción, edición, eliminación y consulta de datos a través de un
Web Service.

Si sigues leyendo podrás obtener el siguiente resultado:
El código de la aplicación puedes obtenerlo presionando el siguiente botón:

Descargar Código
Apóyanos con una señal en tu red social favorita y consigue el código 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 programación o plataforma en que
fueron desarrollados. Este debe tener una interfaz basada en un formato estándar entendible
por las maquinas como lo es XML o JSON.
Por ejemplo…
Facebook es un aplicativo web construido con una determinada arquitectura y lenguajes de
programación basados en el protocolo HTTP. Sin embargo podemos usar esta red social en
nuestro dispositivo Android.
¿Cómo es posible esto, si la aplicación Android está construida con lenguaje Java?
A través de un Web Service construido para gestionar todas aquellas operaciones sobre una
base de datos alojada en los servidores de Facebook. Quiere decir que ambos aplicativos
usan como puente la web para acceder a un solo repositorio de datos.

Como ves, un Web Service se crea con funcionalidades que permitan obtener datos
actualizados en tiempo real. El hecho de que sea dinámico incorpora el uso de un lenguaje
web para la gestión HTTP que en este caso será Php.

2. Requerimientos De La Aplicación
Como leíste al inicio, la aplicación I Wish gestiona las metas y sueños de los usuarios
permitiéndoles tener un registro completo. Básicamente el alcance del proyecto se resumen
en:
 Como usuario de I Wish, deseo mantener los datos de todas mis metas y sueños (se
refiere al CRUD).
 Como usuario de I Wish, deseo ver el detalle de cada meta.
 Como usuario de I Wish, deseo que cada ítem tenga un título, una descripción, una
fecha límite de cumplimiento, prioridad y categoría. Las categorías posibles son:
Salud, Finanzas, Profesional y Espiritual.
Estos requerimientos no son nada del otro mundo. Básicamente estas ante una situación de
listas y detalles. Algo que ya has visto en artículos pasados con gran frecuencia.
El meollo del asunto se encuentra en el Web Service que debes crear con Php y Mysql para
el mantenimiento de los datos. Esta vez no usaremos caching para el soporte de los datos
locales como lo hicimos al crear el lector Rss. Nos enfocaremos en como usar Volley para
realizar las peticiones en el localhost.

3. Wireframing De La Aplicación
A primera vista I Wish es una aplicación que se basa en la funcionalidad básica de un crud.
Tendremos una lista de los elementos que existen, podremos ver el detalle de cada uno,
modificar su contenido e incluso borrarlos.
Teniendo en cuenta este razonamiento, puedes imaginar la aplicación en primera instancia
de la siguiente forma:

fragmentos.  Actividad con fragmento de formulario para edición. diálogos y formularios que necesitas.  Layout personalizado para items.  Actividad con fragmento de formulario para inserción.1 Diseñar Actividad Principal Con Fragmento Tipo Lista . En este tutorial usaremos actividades basadas en fragmentos. ya que muchos lectores han preguntado cómo hacer para comunicar fragmentos con actividades y viceversa. Así que veamos la siguiente lista de materiales a crear:  Actividad principal con un fragmento de lista. 4. Crear UI Para La Aplicación Android 4.  Actividad con fragmento de detalle.Basado en el boceto que acabas de crear ya puedes identificar que la cantidad de actividades.

activity_main.3.com/apk/res/android" xmlns:tools="http://schemas.android. Cada librería trae la explicación de su implementación. ya que necesitamos fabs muy simples. el diseño es diferente. Debido a que vamos a añadir los fragmentos programáticamente no es necesario enfocarnos tanto en los layouts de las actividades.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".Después de haber creado tú proyecto en Android Studio vas a crear una actividad principal que contengan un fragmento con una lista. así que no hay excusas.0' Veamos como queda el layout del fragmento principal: fragment_main. Por mi parte. La idea es añadir el recycler para recubrir toda la actividad y además añadir un Floating Action Button en la parte inferior derecha con el fin de que el usuario añada nuevas metas. Todo depende de ti.MainActivity" /> El fragmento pudiese heredar de ListFragment pero debido a que vamos a usar un RecyclerView.android. Para ello incluimos la siguiente dependencia de Gradle: compile 'com. Para añadir el FAB (Floating Action Button) podemos hacer uso de una de las siguientes librerías que existen en la web:  Floating Action Button Library For Android  FloatingActionButton de makovkastar  Future Simple Incluso podrías basarte en el ejemplo del sitio de android devepers llamado FloatingActionButtonBasic. en este ejemplo usaré la librería de makovkastar.xml <LinearLayout xmlns:android="http://schemas.melnykov:floatingactionbutton:1.xml . Incluso puedes usar un solo layout para todas las actividades.

melnykov.android. .<RelativeLayout xmlns:android="http://schemas.FloatingActionButton> para implementar el FAB. Simplemente se ubica en la parte inferior derecha y le añadimos los colores correspondientes a su interacción.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.v7.fab.fab.widget.RecyclerView android:id="@+id/reciclador" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="3dp" android:scrollbars="vertical" /> <com. colorPressed es aquel que se proyecta cuando lo presionamos rapidamente y colorRipple se evidencia cuando mantienes un click largo sobre él.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.melnykov. Donde colorNormal es el color que tiene en estado natural.

. El icono que lleva debe mantenerse en 24dpx24dp. para una buena experiencia de usuario: El patrón anterior muestra un FAB grande para representar la inserción con unas dimensiones reglamentarias de 56dpx56dp.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.

android.2 Diseñar Actividad De Detalle Con Fragmento Personalizado Acudiendo a los estilos de layouts en Material Design. 4. dividiremos el fragmento de detalle en dos pasos. Adicionalmente añadiremos un Float Button Action para la edición de la meta.xml <?xml version="1. fragment_detail.También podemos tener un FAB mini con dimensiones de 40dpx40dp.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas. El primero será una ImageView alusivo a la categoría de la meta y el segundo será una hoja para sus datos completos.Parte superior --> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" .android.com/apk/res/android" xmlns:fab="http://schemas.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-. donde el icono se mantiene sobre 24dpx24dp.

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" /> .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.

Datos de la meta --> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="70" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <TextView android:id="@+id/categoria" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/categoria_label" android:text="Categoría" android:textAppearance="?android:attr/textAppearanceSmall" android:layout_marginBottom="16dp" /> <TextView android:id="@+id/fecha" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/fecha_label" android:text="Fecha" android:textAppearance="?android:attr/textAppearanceSmall" android:layout_marginBottom="16dp" /> <TextView .</RelativeLayout> <!-.

android:id="@+id/prioridad" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/prioridad_label" android:text="Prioridad" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/descripcion" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/descripcion_label" android:text="Descripción" android:textAppearance="?android:attr/textAppearanceSmall" android:layout_marginBottom="16dp" /> <TextView android:id="@+id/descripcion_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Descripción" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/black" /> <TextView android:id="@+id/fecha_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/descripcion" android:text="Fecha Límite" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/black" /> <TextView .

android:id="@+id/categoria_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/fecha" android:text="Categoría" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/black" /> <TextView android:id="@+id/prioridad_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/categoria" android:text="Prioridad" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@android:color/black" /> </RelativeLayout> </LinearLayout> El FAB debe usar el atributo fab:fab_type=”mini” para usar el botón mini. .

En este caso se usó como nodo un LinearLayout con dos RelativeLayout dentro. Esto nos permite dividir por pesos (weight) la ocupación de espacio entre ambos layouts y así mantener una proporción. Para ello debes crear un layout con los datos que viste en los requerimientos de la aplicación con las respectivos views para obtener la información. . 4.3 Diseñar Actividad Con Formulario La inserción y edición requiere de la proyección de un formulario que contenga los controles necesarios para que el usuario especifique la información personalizada que desea almacenar en la base de datos.

puedes usar un Spinner. La descripción es igual.Por ejemplo… El titulo de cada meta recibe texto escrito desde el input del dispositivo.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" . necesita un campo de texto.com/apk/res/android" xmlns:tools="http://schemas. Veamos: <RelativeLayout xmlns:android="http://schemas. La fecha limite puede ser obtenida a través de un DatePicker y para la categoría que tiene un dominio de varias opciones. por lo que sabemos que elEditText es la solución para este caso.android.android.

fragmentos.iwish.Etiqueta Fecha --> .ui.Descripción --> <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="Descripción" android:maxLength="128" android:nestedScrollingEnabled="true" android:paddingTop="16dp" android:paddingBottom="16dp" /> <!-.UpdateFragment"> <!-.Titulo--> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/titulo_input" android:layout_alignParentTop="false" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:hint="Título" android:minLines="1" android:maxLines="1" android:maxLength="55" android:phoneNumber="false" android:singleLine="true" android:paddingTop="16dp" android:paddingBottom="16dp" /> <!-.herprogramacion.android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.

<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" /> <!-.Categoría --> <Spinner android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/categoria_spinner" android:entries="@array/entradas_categoria" android:layout_below="@+id/categoria_texto" /> <!-.Etiqueta Categoría --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="Categoría" .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" /> <!-.

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. Puedes dejar la descripción solo para la actividad del detalle y eliminarlo de la presentación en la lista. item_list.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" /> <!-.xml .4 Diseñar Layout Personalizado De Los Items La organización de los atributos de cada meta dentro de los ítems de la lista debe ser un resumen de sus características principales.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" /> <!-.

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"> <!-.android.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" /> <!-.<?xml version="1.Categoría --> <TextView android:id="@+id/categoria" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:text="Categoría" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@color/accent" /> <!-.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.Fecha --> <TextView .

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 diseño se vería de la siguiente forma: .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" /> <!-.

el intérprete de Php y el gestor Mysql. No obstante. si tu proyecto es mas complicado. entonces te recomiendo este excelente curso online con Laravel.5. Codificación Del Web Service En Php Antes de crear la aplicación Android debes desarrollar primero tu Web Service con cualquiera de los estándares que te interesen.1 Diseño E Implementación De La Base De Datos Diseñar base de datos: Si ya lo has notado. Lo importante es que puedas correr Mysql e interpretar scripts de Php. el cual provee automáticamente una configuración de un servidor Apache local. Si deseas aprender a crear Web Services con diseño REST. Para desarrollar este aplicativo usaré el entorno de desarrollo XAMPP. Meta debe tener los atributos que hemos venido viendo más una llave primaria que mantenga la integridad de los datos. asegúrate de tener una buena metodología de diseño de bases de datos antes de crear el web service. Esto reduce ampliamente el diseño de bases de datos en el problema. Simplemente verás cómo crear las implementaciones Php necesarias para realizar operaciones sobre una base de datos en Mysql a través de peticiones GET y POST. Sin embargo tu puedes usar las herramientas que desees para gestionar pruebas locales. El alcance de este tutorial no abarca el uso de restricciones REST. la base de datos de la aplicación I Wish solo tiene una entidad que representa a los registros de las metas. SOAP.RPC o sus parecidos. Observa el siguiente minidiagrama entidad-relación: . 5.

titulo varchar(56) NOT NULL. Ahora crea la tabla meta para que contenga seis columnas en su estructura y además usa el formato UTF-8 para soportar acentos. Puedes hacerlo a través del editor de phpMyAdmin o con el siguiente comandoCREATE: CREATE TABLE IF NOT EXISTS meta( idMeta int(3) PRIMARY KEY AUTO_INCREMENT. Donde le asignaremos el nombre de “i_wish”. descripcion varchar(128) NOT NULL.Crear base de datos: Para implementar la base de datos lo primero que debes hacer es crear una nueva base de datos en la aplicación phpMyAdmin que te otorga tu distribución XAMPP. .

(NULL. 'Alta'. 'Natasha es la mujer de vida. 'Profesional'). 'Media'.'Baja'. Sin embargo voy a seguir una rutina de ejercicios y un régimen alimenticio'.'Finanzas'. 'Ya solo faltan 2 semestres para terminar mi carrera de ingeniería. (NULL. Debo investigar cómo conseguir mas fuentes de ingresos'. 'Salud').prioridad enum('Alta'. `fechaLim`.'Espiritual'. 'Tener un peso de 70kg'. fechaLim date NOT NULL. (NULL.`meta` (`idMeta`. (NULL. '2015-11-20'. Debo prepararme al máximo para desarrollar mi tesis de grado'. . '2016-06-17'. 'Deseo adquirir un auto mazda 6 para mi desplazamiento en la ciudad.'Profesional'. '2016-05-13'. 'Alta'. `descripcion`.'Material') NOT NULL DEFAULT 'Finanzas' ) Luego añade 5 registros de ejemplo en la tabla que permitan probar el funcionamiento en la aplicación android más adelante. 'Espiritual').'Media'. 'Baja'. 'Conseguiré una fuente de ingresos alternativa que representen un 30% de los ingresos que recibo actualmente. `categoria`) VALUES (NULL. `prioridad`. Tengo que decírselo antes de que acabe el semestre'. 'Conquistar a Natasha'. 'Comprar Mazda 6'. '2015-05-25'. 'Material'). INSERT INTO `i_wish`. 'Incrementar un 30% mis ingresos'. `titulo`. 'Obtener mi título de ingeniería de sistemas'. categoria enum('Salud'.'.'') NULL DEFAULT 'Alta'. 'Actualmente peso 92kg y estoy en sobrepeso.

puedes crear una clase que represente la conexión hacia la base de datos o simplemente crear una nueva conexión en cada script de Php que tengas. '2015-10-13'. actualización. Con ello podremos disponer de un solo objeto a través de todo el proyecto.php <?php /** * Clase que envuelve una instancia de la clase PDO * para el manejo de la base de datos */ . 'Finanzas'). Para este caso te compartiré un patrón singleton de PDO para limitar el número de aperturas a la base de datos en una sola.2 Crear Código Php Para Consumir Datos En primera instancia crea una conexión a la base de datos Mysql con la interfaz que mas se acomode a tus necesidades. Él se enfoca en la implementación del CRUD de una forma muy sencilla y orientada a objetos. En mi caso voy a crear una conexión con PDO.'Media'. No obstante hay patrones de diseño muy interesantes que puedes consultar en la web. la cual me permite proteger los datos de inyecciones sql. Paso #1: Crear conexión a la base de datos con PDO El uso de PDO depende del enfoque que tengan tus proyectos. eliminación y consulta a través de la conexión a la base de datos. Finalmente implementaré scripts Php para gestionar las peticiones que lanzan los clientes. 5. Por ejemplo el repositorio del usuario indieteq en github. La idea es parsear los datos en formato Json para que nuestra aplicación Android interprete los resultados de forma legible. Luego de eso crea una clase que mapee la estructura de la tabla meta. Veamos el resultado del patrón singleton: Database. El objetivo de ello es proveerla de comportamientos de inserción.

final private function __construct() { try { // Crear nueva conexión PDO self::getDb(). /** * Instancia de PDO */ private static $pdo. class Database { /** * Única instancia de la clase */ private static $db = null. } 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) { .php'.require_once 'mysql_login.

// Habilitar excepciones self::$pdo->setAttribute(PDO::ATTR_ERRMODE. } return self::$db.host=' . } /** * Evita la clonación del objeto */ final protected function __clone() { } . } /** * Crear una nueva conexión PDO basada * en los datos de conexión * @return PDO Objeto PDO */ public function getDb() { if (self::$pdo == null) { self::$pdo = new PDO( 'mysql:dbname=' . PDO::ERRMODE_EXCEPTION). array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8") ). PASSWORD. USERNAME.'. '. DATABASE .self::$db = new self(). } return self::$pdo. '.port:63343. HOSTNAME .

// Nombre del usuario define("PASSWORD". */ define("HOSTNAME". "i_wish"). Con ello me refiero al nombre del host. mysql_login. uno para la inserción. el nombre de la base de datos. } } ?> Ten en cuenta que la conexión se abre con 4 cadenas descriptivas del entorno que estás usando declaradas en el archivo mysql_login. el usuario con que deseas ingresar y su respectiva contraseña.php. "root"). Esto permite que los datos de la base de datos vengan codificados en este formato para evitar problemas de compatibilidad. ""). en mi caso uso el usuario por defecto "root" y sin contraseña alguna. Por el momento usaremos el localhost debido a las pruebas que estamos haciendo. // Nombre de la base de datos define("USERNAME".// Nombre del host define("DATABASE". En esencia necesitas un método para obtener todos los registros. El usuario ya depende de ti. también para la actualización y un método que permita obtener del detalle de un solo registro. otro para eliminación. // Nombre de la constraseña ?> Adicionalmente debes añadir al cuarto parámetro del constructor de PDO la indicación SET NAMES UTF-8para el servidor.php <?php /** * Provee las constantes para conectarse a la base de datos * Mysql. "localhost"). Paso #4: Crear clase para las metas En este paso vas a modelar en una clase a la tabla "meta" de tal forma que aplique el CRUD sobre los datos a través de la clase Database.function _destructor() { self::$pdo = null. .

// Ejecutar sentencia preparada $comando->execute().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). } catch (PDOException $e) { return false. return $comando->fetchAll(PDO::FETCH_ASSOC).Meta.php <?php /** * Representa el la estructura de las metas * almacenadas en la base de datos */ require 'Database. } .

prioridad. try { // Preparar sentencia $comando = Database::getInstance()->getDb()->prepare($consulta). } . descripcion. fechaLim. // Ejecutar sentencia preparada $comando->execute(array($idMeta)). } catch (PDOException $e) { // Aquí puedes clasificar el error dependiendo de la excepción // para presentarlo en la respuesta Json return -1.} /** * 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. categoria FROM meta WHERE idMeta = ?". titulo. return $row. // Capturar primera fila del resultado $row = $comando->fetch(PDO::FETCH_ASSOC).

$fechaLim. "WHERE idMeta=?". $prioridad. // Relacionar y ejecutar la sentencia $cmd->execute(array($titulo. // Preparar la sentencia $cmd = Database::getInstance()->getDb()->prepare($consulta). $fechaLim. $descripcion. fechaLim=?. return $cmd. $titulo. $descripcion. $prioridad ) { // Creando consulta UPDATE $consulta = "UPDATE meta" . categoria=?.} /** * 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. " SET titulo=?. } . $idMeta)). descripcion=?. $categoria. $categoria. prioridad=? " .

?. $categoria.?)". " categoria. $prioridad ) { // Sentencia INSERT $comando = "INSERT INTO meta ( " ./** * Insertar una nueva meta * * @param $titulo titulo del nuevo registro * @param $descripcion descripción del nuevo registro * @param $fechaLim fecha limite del nuevo registro * @param $categoria categoria del nuevo registro * @param $prioridad prioridad del nuevo registro * @return PDOStatement */ public static function insert( $titulo.?. $descripcion." ." . " fechaLim. return $sentencia->execute( array( $titulo. $fechaLim.?. // Preparar la sentencia $sentencia = Database::getInstance()->getDb()->prepare($comando)." . " prioridad)" . "titulo." . " VALUES( ?. $descripcion. . " descripcion.

La trata de la petición seguiría la siguiente lógica:  Comprobar que la petición se realizó con el método GET. . Esto protege la operación de inyecciones que puedan atentar contra la seguridad de los datos. } /** * Eliminar el registro con el identificador especificado * * @param $idMeta identificador de la meta * @return bool Respuesta de la eliminación */ public static function delete($idMeta) { // Sentencia DELETE $comando = "DELETE FROM meta WHERE idMeta=?". } } ?> Recuerda que el método prepare() permite reemplazar los placeholders ('?') a través de execute(). $categoria. Paso #5: Crear un script para obtener todas las metas Para retornar todos los registros que existen en la tabla "meta" usaremos el método getAll() de la claseMeta. // Preparar la sentencia $sentencia = Database::getInstance()->getDb()->prepare($comando).$fechaLim. return $sentencia->execute(array($idMeta)). $prioridad ) ).

Esto permitirá trackear si nuestro web service está operando bien la base de datos. Esto te será posible usando las funciones json_encode() y json_decode(). En resumen. la falla de autenticación. etc. No obstante este ejemplo se basa en el comportamiento ideal de nuestro servidor local. contempla todas las fallas tanto del lado del servidor (códigos 5xx) como las del cliente (códigos 4xx). la no existencia del recurso. la no disponibilidad del servidor.  Obtener todos los registros. La trata de errores debe comprender todos aquellos posibles caminos que puedan generarse como una petición fallida. La primera parsea un tipo de dato a un string en formato json y la segunda es el procedimiento contrario. En cada respuesta enviaremos una seria de elementos Json que puedan ser interpretados del lado del cliente. Donde solo reportaremos aquellas anomalías que sucedan en la base de datos. Además de ello PDO puede retornar en excepciones por distintas causas que puedes estandarizar para el envío de mensajes. ¿La obtención 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. Pero este trabajo te queda a tí Ahora…¿Cómo envío una respuesta de vuelta a la aplicación Android? Es justo donde entra Json para actuar como formato de comunicación. asumiendo que la respuesta siempre tendrá un código de estado 200.php <?php /** * Obtiene todas las metas de la base de datos */ . Veamos nuestro servicio de obtención: obtener_metas.

if ($_SERVER['REQUEST_METHOD'] == 'GET') { // Manejar petición GET $metas = Meta::getAll(). . Si es 2. entonces usaremos un atributo "mensaje" para avisar a la aplicación cliente que ocurrió un error en la operación a la base de datos.require 'Meta. "mensaje" => "Ha ocurrido un error" )). Una respuesta de éxito tendría el siguiente aspecto: { "estado":1. } else { print json_decode(array( "estado" => 2. $datos["metas"] = $metas.php'. "titulo":"Obtener mi t\u00edtulo de ingenier\u00eda de sistemas". } } El objeto Json que retornaremos tiene un atributo llamado "estado" el cual representa un código para indicar la calidad del resultado. entonces añadiremos otro atributo llamado "metas" el cual es un array de objetos con los datos de las metas. if ($metas) { $datos["estado"] = 1. "metas":[ { "idMeta":"2". Si es 1. print json_encode($datos).

"descripcion":"Natasha es la mujer de vida. "mensaje":"Ha ocurrido un error" } Cambiando de tema…¿Qué pasa si quieres filtrar los registros? Por ejemplo… Puede que requieras en orden ascendente o descendente de los registros con respecto a un campo. Debo prepararme al m\u00e1ximo para desarrollar mi tesis de grado". Tengo que dec\u00edrselo antes de que acabe el semestre". . Sin embargo dicho tema está fuera del alcance de nuestro artículo. "fechaLim":"2015-05-25". "prioridad":"Alta". "titulo":"Conquistar a Natasha". O simplemente obtener las metas que van de una fecha a otra. Para tener en cuenta estos casos. la respuesta de error simplemente sería: { "estado":"2". Esto quiere decir que podrías incluir en el cuerpo de la petición variables que actúen como filtros en la selección. puedes consultar los datos de acuerdo a una serie de parámetros establecidos en la API. "fechaLim":"2015-05-29". "categoria":"Profesional" }."descripcion":"Ya solo faltan 2 semestres para terminar mi carrera de ingenier\u00eda. { "idMeta":"3". "prioridad":"Media". "categoria":"Espiritual" } ] } Por el otro lado.

// Tratar retorno $retorno = Meta::getById($parametro). $meta["meta"] = $retorno. Veamos: <?php /** * Obtiene el detalle de una meta especificada por * su identificador "idMeta" */ require 'Meta. Paso #6: Crear un script php para consultar el detalle de una meta El segundo caso requiere que la petición traiga consigo el identificador de la meta que se desea ver en detalle. .php'. // Enviar objeto json de la meta print json_encode($meta). if ($retorno) { $meta["estado"] = "1". Con este dato es posible usar el método getById() de Meta para conseguir el array necesario.El diseño RESTful para Web Services provee reglas supremamente estilizadas para filtrar y consultar datos de forma más sencilla que estableciendo filtros manuales. if ($_SERVER['REQUEST_METHOD'] == 'GET') { if (isset($_GET['idMeta'])) { // Obtener parámetro idMeta $parametro = $_GET['idMeta'].

"meta":{ "idMeta":"2". } } else { // Enviar respuesta de error print json_encode( array( 'estado' => '3'. Recuerda que la función isset() es quién realiza este trabajo. { "estado":"1". 'mensaje' => 'Se necesita un identificador' ) ). . 'mensaje' => 'No se obtuvo el registro' ) ). } } Para retornar el detalle obviamente primero debes comprobar que el parámetro vino con la petición GET y si vino bien definido. Lo que retorna en un objeto Json con un objeto interno que tiene los datos de la meta. Que la consulta sea un éxito y el registro con el identificador enviado existe. "titulo":"Obtener mi t\u00edtulo de ingenier\u00eda de sistemas".} else { // Enviar respuesta de error general print json_encode( array( 'estado' => '2'. "descripcion":"Ya solo faltan 2 semestres para terminar mi carrera de ingenier\u00eda. Esta vez tenemos tres casos generales posibles. Debo prepararme al m\u00e1ximo para desarrollar mi tesis de grado".

Para este caso envías tu código 3 indicando este mensaje. .php'. Por ejemplo un error de sintaxis."prioridad":"Media". "mensaje":"Se necesita un identificador" } Paso #7: Crear un script php para la inserción de metas La inserción requiere el uso del método POST para la recepción de los datos de la meta. "categoria":"Profesional" } } O también puede que PDO haya arrojado una excepción por algún motivo. { "estado":"2". etc. "fechaLim":"2015-05-29". { "estado":"3". Por lo que debemos leer los datos en formato Json: <?php /** * Insertar una nueva meta en la base de datos */ require 'Meta. Con ello envías tu objeto representativo del estado 2. la inexistencia del registro. puede que por alguna razón el parámetro no haya venido en la petición. "mensaje":"No se obtuvo el registro" } Ahora bien. o que pueda que haya venido pero con otro nombre.

por lo que convertiremos esos datos a un arreglo asociativo que nos permita acceder a la .if ($_SERVER['REQUEST_METHOD'] == 'POST') { // Decodificando formato Json $body = json_decode(file_get_contents("php://input"). } else { // Código de falla print json_encode( array( 'estado' => '2'. 'mensaje' => 'Creación fallida') ). $body['descripcion']. $body['categoria']. // Insertar meta $retorno = Meta::insert( $body['titulo']. if ($retorno) { // Código de éxito print json_encode( array( 'estado' => '1'. $body['fechaLim']. } } La primera instrucción es comprobar la petición POST obtenida. Esto es posible consultando el flujo con file_get_contents(). 'mensaje' => 'Creación exitosa') ). que convierte un archivo a string. Luego de ello conviertes el cuerpo de la petición a un arreglo de strings. true). Obviamente es necesario que uses la convención “php://input” para acceder al cuerpo de la petición POST. el resultado que obtengas con file_get_contents() debe estar en formato Json. Ahora. $body['prioridad']).

De resto procedemos con el método update() de Meta sin problemas: <?php /** * Actualiza una meta especificada por su identificador */ require 'Meta. "mensaje":"Creación éxitosa" } De lo contrario usa un mensaje general de error. Para ello usa la función json_decode() y pasa como segundo parámetro el valor de true. "mensaje":"Creación fallida" } Paso #8: Crear un scritp Php para la actualización de metas La actualización es casi idéntica a la inserción. { "estado":"2". true). Esta vez no retornas en filas de la base de datos. así que el estado 1 contiene un mensaje de éxito.php'. if ($_SERVER['REQUEST_METHOD'] == 'POST') { // Decodificando formato Json $body = json_decode(file_get_contents("php://input"). // Actualizar meta $retorno = Meta::update( . { "estado":"1". Luego usa el método insert() de Meta y comprueba el resultado. solo que esa vez debemos obtener el identificador de la meta para saber que registro actualizar.información.

$body['categoria']. $body['fechaLim']. 'mensaje' => 'Actualización fallida') ). if ($retorno) { // Código de éxito print json_encode( array( 'estado' => '1'. $body['titulo']. $body['prioridad']). "mensaje":"Actualización éxitosa" } Al igual que el objeto Json de error: { . } else { // Código de falla print json_encode( array( 'estado' => '2'.$body['idMeta']. } } Es resultado de éxito es similar y repetitivo para la actualización: { "estado":"1". $body['descripcion']. 'mensaje' => 'Actualización exitosa') ).

if ($_SERVER['REQUEST_METHOD'] == 'POST') { // Decodificando formato Json $body = json_decode(file_get_contents("php://input")."estado":"2". $retorno = Meta::delete($body['idMeta']). 'mensaje' => 'Eliminación fallida') ). 'mensaje' => 'Eliminación exitosa') ). Esta vez usaremos el método delete() de Meta. } else { print json_encode( array( 'estado' => '2'. true).php'. <?php /** * Elimina una meta de la base de datos * distinguida por su identificador */ require 'Meta. } . if ($retorno) { print json_encode( array( 'estado' => '1'. "mensaje":"Actualización fallida" } Paso #9: Crear un script Php para la eliminación de metas La eliminación se basa en el método POST para enviar el identificador de la meta que se necesita eliminar de la base de datos.

}

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

{
"estado":"1",
"mensaje":"Eliminación éxitosa"
}

Y los errores se mostrarían así:

{
"estado":"2",
"mensaje":"Eliminación fallida"
}

6. Codificación De La Aplicación Android
Una vez creado el Web Service, es hora de construir nuestra aplicación gestora de metas.
Recuerda que es necesario que crees los siguientes elementos e interacciones de la
arquitectura:

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

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

Crear un adaptador que procese los elementos del recycler view.

Tratar los eventos para la comunicación de datos a través de los controles.
La idea es enfocarnos en el uso del servicio web y aprovechar al máximo las peticiones
Json que nos provee Volley.

Paso #1: Crear Patrón Singleton Volley

Este paso ya hace parte de nuestra rutina para gestionar peticiones HTTP. Así que
reutilizarás el singleton de artículos pasados para simplificar procesos. La única diferencia
que tendrás será la ausencia delImageLoader como atributo. En esta ocasión no usaremos
caching de imágenes, así que es justo dejarlo descansar.
Recuerda incluir la librería Volley en tu proyecto de la forma que más te parezca.
VolleySingleton.java
import android.content.Context;

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

/**
* Creado por Hermosa Programación.
*
* Clase que representa un cliente HTTP Volley
*/

public final class VolleySingleton {

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

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

/**
* Retorna la instancia unica del singleton
* @param context contexto donde se ejecutarán las peticiones

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

/**
* Obtiene la instancia de la cola de peticiones
* @return cola de peticiones
*/
public RequestQueue getRequestQueue() {
if (requestQueue == null) {
requestQueue = Volley.newRequestQueue(context.getApplicationContext());
}
return requestQueue;
}

/**
* Añade la petición a la cola
* @param req petición
* @param <T> Resultado final de tipo T
*/
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}

}

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

0.3.0.2:63343/I%20Wish/insertar_meta. /** * Clave para el valor extra que representa al identificador de una meta */ public static final String EXTRA_ID = "IDEXTRA".php". public static final String DELETE = "http://10.2./** * Clase que contiene los códigos usados en "I Wish" para * mantener la integridad en las interacciones entre actividades * y fragmentos */ public class Constantes { /** * Transición Home -> Detalle */ public static final int CODIGO_DETALLE = 100. public static final String GET_BY_ID = "http://10.2:63343/I%20Wish/borrar_meta.2:63343/I%20Wish/obtener_metas.0.php".3. Aquí el sitio oficialte habla un poco mas sobre estás convenciones de direcciones para operaciones en la web. } Como ves.0. public static final String UPDATE = "http://10.0.php".3.php".3. public static final String INSERT = "http://10. yo uso para el localhost la dirección 10.2 debido a que Genymotion (emulador alternativo) estableció este valor.2:63343/I%20Wish/obtener_meta_por_id. .2. /** * Transición Detalle -> Actualización */ public static final int CODIGO_ACTUALIZACION = 101.php". Si vas a usar el emulador de android usa la dirección 10.0. /** * URLs del Web Service */ public static final String GET = "http://10.3.3.0.2:63343/I%20Wish/actualizar_meta.

this. private String descripcion.prioridad = prioridad. private String titulo. this.Paso #2: Crear fuente de datos para las metas Nuestro adaptador necesita alimentarse de una lista de elementos que le proporcionen la información necesaria para proyectar el layout. } public String getIdMeta() { return idMeta.categoria = categoria. private String prioridad. Crea una nueva clase en Android Studio y llámala Meta. private String categoria. String titulo. this. this.descripcion = descripcion. String categoria) { this.idMeta = idMeta. String descripcion. public Meta(String idMeta. 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.fechaLim = fechaLim. Es por eso que tienes que crear una clase que represente la existencia de una meta en la aplicación Android. private String fechaLim. String fechaLim. String prioridad.titulo = titulo. this. } .

} public String getFechaLim() { return fechaLim. } public String getCategoria() { return categoria. false si hay cambios */ public boolean compararCon(Meta meta) { return this.prioridad) == 0.fechaLim) == 0 && this. } public String getDescripcion() { return descripcion.categoria) == 0 && this. } public String getPrioridad() { return prioridad.descripcion) == 0 && this.descripcion.fechaLim.titulo.categoria.compareTo(meta.public String getTitulo() { return titulo.titulo) == 0 && this.prioridad.compareTo(meta.compareTo(meta. } } .compareTo(meta.compareTo(meta. } /** * Compara los atributos de dos metas * @param meta Meta externa * @return true si son iguales.

import android. de tal forma que cuando se active el evento onClick() este inicie la actividad de detalle.Adapter<MetaAdapter.herprogramacion.modelo.app.LayoutInflater.view.Activity.MetaViewHolder> implements ItemClickListener { /** . import android. import com.widget.view. import java.DetailActivity.view. Además de ello tenemos que implementar sobre cada view holder una escucha OnClickListener para recibir los eventos del usuario en la lista. Este método será de gran ayuda al momento de validar si hay cambios en los datos de los formularios cuando el usuario interactúa con ellos.util.iwish. Paso #3: Crear adaptador personalizado para el Recycler View En este paso debes relacionar el layout item_list.iwish.Meta.widget.ui.TextView.herprogramacion. Lo que permitirá determinar si hay que lanzar diálogos de confirmación antes de aplicar acciones. import com.herprogramacion.actividades.View. /** * Adaptador del recycler view */ public class MetaAdapter extends RecyclerView. No olvides usar le patrón ViewHolder para reducir la cantidad de llamadas del método findViewById(). import android. import android.content. import android.Si te fijas.v7.List.support. tenemos un método para comparar una meta con otra para determinar si son iguales o no.R.Context. Para ello se creará una interfaz intermediaria entre elViewHolder y el adaptador.ViewGroup.iwish.RecyclerView.xml con los datos que tenga cada objeto Meta de la fuente de datos. import android. import com. import android.

this). } .size().layout.categoria. int i) { viewHolder. viewHolder. this.setText(items. public MetaAdapter(List<Meta> items. false).prioridad. } @Override public void onBindViewHolder(MetaViewHolder viewHolder.getCategoria()).context = context.getPrioridad()). viewHolder.get(i).get(i).getTitulo()).fechaLim.getContext()) .* Lista de objetos {@link Meta} que representan la fuente de datos * de inflado */ private List<Meta> items.from(viewGroup. viewHolder.setText(items.titulo. int i) { View v = LayoutInflater.setText(items. Context context) { this.setText(items. viewGroup.get(i).getFechaLim()).items = items. } @Override public int getItemCount() { return items.inflate(R. } @Override public MetaViewHolder onCreateViewHolder(ViewGroup viewGroup.get(i). /* Contexto donde actua el recycler view */ private Context context.item_list. return new MetaViewHolder(v.

fechaLim = (TextView) v.get(position). public ItemClickListener listener. } @Override public void onClick(View v) { .OnClickListener { // Campos respectivos de un item public TextView titulo. ItemClickListener listener) { super(v).fecha).id. public MetaViewHolder(View v.findViewById(R. } public static class MetaViewHolder extends RecyclerView. v./** * Sobrescritura del método de la interfaz {@link ItemClickListener} * * @param view item actual * @param position posición del item actual */ @Override public void onItemClick(View view.categoria).listener = listener. categoria = (TextView) v.launch( (Activity) context.prioridad).id.titulo).setOnClickListener(this).findViewById(R. public TextView fechaLim. titulo = (TextView) v. public TextView categoria. this. int position) { DetailActivity.ViewHolder implements View. items.id.id.findViewById(R.findViewById(R.getIdMeta()). public TextView prioridad. prioridad = (TextView) v.

content. import android. Sin embargo el fin de este tutorial es el uso al máximo de nuestro Web Service para que puedas interiorizar el conocimiento y practicar esta metodología. Es necesario que enviemos el identificador de la meta para tener una referencia de la meta que debemos detallar.R. Lo que podría evitarse a través de caching con SQLite o enviando todos los datos de la meta. int position).herprogramacion. import com.app. Como ves se implementa en la clase MetaAdapter para iniciar la actividad detalle a través de su método de fabricación launch(). import android. .Bundle.onItemClick(v.listener. } ItemClickListener es la interfaz de comunicación que nos ayudará a relacionar lo posición del view con el evento onClick() . El fragmento de la lista lo iniciaremos dinámicamente a través del método onCreate() de MainActivity: import android.iwish. getAdapterPosition()).support.Intent. Esto significa que se debe realizar otra petición para obtener los datos de la meta seleccionada.os. Por ahora no te preocupes en la arquitectura u optimizaciones.v7.AppCompatActivity. } } } interface ItemClickListener { void onItemClick(View view. Paso #4: Realizar Petición Para Poblar La Lista Ya has construido un Web Service en Php con todas las características necesarias y has desarrollado los componentes de software para que la aplicación Android comience a funcionar.

Intent.fragmentos. .Fragment. Veamos: import android.container.Bundle.v4.onCreate(savedInstanceState).id. Con ellas poblaremos la lista a penas inicie la aplicación.support.activity_main).ActionBarActivity} por * {@link AppCompatActivity} para el uso de la action bar * en versiones antiguas.support.iwish. import android. // Creación del fragmento principal if (savedInstanceState == null) { getSupportFragmentManager().add(R.os.v7. setContentView(R. import android.beginTransaction() . /** * Actividad principal que contiene un fragmento con una lista.LinearLayoutManager. import android.import com.layout. */ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super. * Recuerda que la nueva librería de soporte reemplazó la clase * {@link android.app.ui."MainFragment") .herprogramacion.commit().app.MainFragment. } } } La comunicación inicial con el servidor es la lectura de todas las metas que se han guardado hasta el momento.v7.content.widget. Por lo que debemos dirigirnos al fragmento principal y generar una petición GET hacia el servidor en onCreateView(). new MainFragment().support.

import com.ViewGroup.JsonObjectRequest. import org.ui. import com. import com. import java.Arrays.Constantes.View.herprogramacion.volley.util. import com.LayoutInflater.iwish.Gson.class.MetaAdapter.widget.actividades. .support.util.view. import org.json.view. import android. import com. import com. import android.volley.android.gson.JSONObject.Request.android. import com.Toast.volley. import android. import com.InsertActivity.iwish.herprogramacion. import android.v7.iwish.R.toolbox.json.iwish.volley.Meta.widget.view. /** * Fragmento principal que contiene la lista de las metas */ public class MainFragment extends Fragment { /* Etiqueta de depuracion */ private static final String TAG = MainFragment.import android.herprogramacion. import com.JSONException. import android.Response. import com.iwish.iwish.getSimpleName().ui.web.modelo. import com.VolleyError.android.tools.JSONArray.Log.herprogramacion.RecyclerView. import org.android.herprogramacion.google.json.VolleySingleton.herprogramacion.

fragment_main.inflate(R. /* instancia global del administrador */ private RecyclerView.id.FloatingActionButton fab./* Adaptador del recycler view */ private MetaAdapter adapter. public MainFragment() { } @Override public View onCreateView(LayoutInflater inflater. container. private Gson gson = new Gson(). Bundle savedInstanceState) { View v = inflater.melnykov. lista. // Usar un administrador para LinearLayout . /* Instancia global del recycler view */ private RecyclerView lista.findViewById(R. ViewGroup container.setHasFixedSize(true).LayoutManager lManager. /* Instancia global del FAB */ com. false).fab.reciclador).layout. lista = (RecyclerView) v.

getInstance(getActivity()). // Cargar datos en el adaptador cargarAdaptador().lManager = new LinearLayoutManager(getActivity()).setOnClickListener( new View. // Obtener instancia del FAB fab = (com. } } ).fab). addToRequestQueue( new JsonObjectRequest( Request.setLayoutManager(lManager).FloatingActionButton) v.GET.class). return v.startActivityForResult( new Intent(getActivity(). lista.fab.melnykov.id. .OnClickListener() { @Override public void onClick(View v) { // Iniciar actividad de inserción getActivity(). InsertActivity. } /** * Carga el adaptador con las metas obtenidas * en la respuesta */ public void cargarAdaptador() { // Petición GET VolleySingleton. 3).Method.findViewById(R. // Asignar escucha al FAB fab.

getMessage()). switch (estado) { case "1": // EXITO // Obtener array "metas" Json .getString("estado").Constantes. } /** * 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. null. new Response. new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { // Procesar la respuesta Json procesarRespuesta(response).ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG. "Error Volley: " + error. } }.GET. } } ) ).

mensaje2.getJSONArray("metas").fromJson(mensaje.setAdapter(adapter).toString().makeText( getActivity(). Toast. // Setear adaptador a la lista lista.class). case "2": // FALLIDO String mensaje2 = response. // Inicializar adaptador adapter = new MetaAdapter(Arrays.printStackTrace().JSONArray mensaje = response. getActivity()). Con esa URL ya es posible realizar la petición JsonObjectRequest con su respectivo método GET a través del método cargarAdaptador(). Para aislar un poco los procesos.getString("mensaje").asList(metas).GET. // Parsear con Gson Meta[] metas = gson.LENGTH_LONG). la cual contiene la dirección del servicio de obtención obtener_metas.show(). Toast. Si el estado es exitoso inmediatamente obtendremos el array de metas que viene en el atributo "metas". break. he creado el método procesarRespuesta(). el cual recibe un objetoJSONObject en bruto para comenzar el parsing. . } } } El código anterior muestra el uso de una constante llamada Constantes. Este arreglo de objetos Json se parsea directamente a un arreglo de objetos Meta a través de la libreríaGson.php. Donde he divido los caminos a través de una estructuraswitch basado en el valor del atributo "estado". Meta[]. } } catch (JSONException e) { e. break.

import android.fragmentos. import android.support.view. /** * Esta actividad contiene un fragmento que muestra el detalle * de las metas.Menu.DetailFragment.Constantes.tools.app.java import android.R.java con un fragmento alojado.os. import android.Activity.herprogramacion. Para ello inicia el fragmento dinámicamente en onCreate(): DetailActivity.MenuItem.content. import com.Bundle.herprogramacion.iwish. por lo que usaremos la clase Arrays para convertir el arreglo de metas a lista. import android. import com.view. */ public class DetailActivity extends AppCompatActivity { /* Valor extra que identifica a la meta a detallar */ private static final String EXTRA_ID = "IDMETA".Context.Recuerda que el adaptador recibe una serie de metas en formato List<Meta>. import android.iwish. Paso #5: Ver Detalle De Items En Otra Actividad Una vez nuestro adaptador poblado. ya podemos ver el detalle de la descripción en DetailActivity. import com.content. Con eso listo ya es posible instanciar el adaptador y asignarlo al recycler.iwish. import android.v7. /** * Instancia global de la meta a detallar */ .app.herprogramacion.ui.AppCompatActivity.Intent.

intent.CODIGO_DETALLE).setDisplayShowTitleEnabled(false). String idMeta) { Intent intent = new Intent(context.layout.onCreate(savedInstanceState).startActivityForResult(intent. setContentView(R. DetailActivity. Constantes. String idMeta) { Intent intent = getLaunchIntent(activity. idMeta). 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. } @Override protected void onCreate(Bundle savedInstanceState) { super. } /** * 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. activity.putExtra(EXTRA_ID. . return intent.class).private String idMeta. if (getSupportActionBar() != null) { // Dehabilitar titulo de la actividad getSupportActionBar().activity_main).

mipmap. DetailFragment.createInstance(idMeta).setHomeAsUpIndicator(R.R. } } .container.add(R.id.getStringExtra(EXTRA_ID) != null) idMeta = getIntent().getStringExtra(EXTRA_ID).home: finish(). return true. if (savedInstanceState == null) { getSupportFragmentManager(). menu).ic_close). switch (id) { case android. } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().onOptionsItemSelected(item).// Setear ícono "X" como Up button getSupportActionBar().getItemId().beginTransaction() .menu. "DetailFragment") .commit().menu_detail.inflate(R. } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.id. } // Retener instancia if (getIntent(). return true. } return super.

import android.app. import android. Por lo que en onCreateView() es posible acceder al identificador y enviar una petición GET hacia el Web Service: DetailFragment. En el segundo caso de edición es necesario consultar la base de datos para setear los datos en los views. Recuerda que la convención createInstance() inicializa un nuevo fragmento con los extras necesarios para su funcionamiento.view.Log. import android.content.os. Paso #9: Consultar Detalles De Cada Item Ahora pregúntate que debe hacer el fragmento de detalle… Dependiendo del enfoque de experiencia de usuario que tengas. puede que sean muchas cosas.Este código tiene varias cosas interesantes.Intent.LayoutInflater. cuando sea pedida con getIntent().View. A los fragmentos que hemos iniciado dinámicamente se les está asignando una etiqueta que los diferencie de los otros. import android.Bundle. La actividad detalle se basa en el identificador de la meta.util. Además de ello asignar una escucha al FAB para que inicie la actividad de actualización.Fragment. La primera interacción ya la tenemos cubierta en DetailActivity. . En primera instancia el uso de un método estático llamadolaunch(). Realizar petición HTTP: La realización de la petición HTTP requiere consultar el detalle con el identificador que el adaptador envío a través del Intent. ya que necesitamos obtener sus instancias cuando la actividad se comunique con ellos.java import android.v4.view. ya que hemos sobrescrito el comportamiento del Up Button por el cierre de la actividad. Esto es de suprema importancia. 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 través del Floating Action Button. import android. el cual construye una instancia de la actividad de detalle y la inicia a través de un Intentconstruido a partir del contexto que el adaptador proveerá.support. por lo que idMeta es un atributo que permitirá retener esa instancia.

VolleyError.ImageButton.getSimpleName().widget.iwish.volley.widget. import com.widget. import com.json.view.Request. import android.android. */ public class DetailFragment extends Fragment { /* Etiqueta de valor extra */ private static final String EXTRA_ID = "IDMETA".json.Gson. import com.herprogramacion.volley.Constantes. import com. /* . import org.herprogramacion.herprogramacion.android. import android.iwish.JsonObjectRequest.Meta.google.TextView.Response.android.actividades.VolleySingleton. import com.import android.herprogramacion. /** * A placeholder fragment containing a simple view.volley. import com.ImageView.web.herprogramacion.JSONException. import com.tools.volley. import org. import android.widget. import com. import android.Toast.R.iwish.JSONObject.modelo.ViewGroup.android.gson.toolbox.iwish. import com. import com.iwish.ui. /** * Etiqueta de depuración */ private static final String TAG = DetailFragment.UpdateActivity.class.

detailFragment. categoria = (TextView) v.fecha). .findViewById(R. idMeta). fechaLim = (TextView) v. private ImageButton editButton.cabecera).layout. false). } @Override public View onCreateView(LayoutInflater inflater.putString(EXTRA_ID.titulo). return detailFragment. private TextView prioridad.fragment_detail. public DetailFragment() { } public static DetailFragment createInstance(String idMeta) { DetailFragment detailFragment = new DetailFragment().Instancias de Views */ private ImageView cabecera. private Gson gson = new Gson(). titulo = (TextView) v.id. Bundle savedInstanceState) { View v = inflater.id.findViewById(R.setArguments(bundle). descripcion = (TextView) v.descripcion).findViewById(R.id.prioridad). private TextView descripcion.findViewById(R. Bundle bundle = new Bundle(). private String extra.id. bundle. private TextView categoria. container.id. private TextView titulo. prioridad = (TextView) v.id. ViewGroup container.inflate(R.findViewById(R. // Obtención de views cabecera = (ImageView) v. private TextView fechaLim.findViewById(R.categoria).

findViewById(R. i. } /** * Obtiene los datos desde el servidor */ public void cargarDatos() { // Añadir parámetro a la URL del web service String newURL = Constantes.editButton = (ImageButton) v.getString(EXTRA_ID). // Obtener extra del intent de envío extra = getArguments(). } } ). getActivity().GET_BY_ID + "?idMeta=" + extra. // Realizar petición GET_BY_ID VolleySingleton.putExtra(EXTRA_ID. UpdateActivity.class). Constantes. return v.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // Iniciar actividad de actualización Intent i = new Intent(getActivity().addToRequestQueue( new JsonObjectRequest( . extra).fab).getInstance(getActivity()). // Setear escucha para el fab editButton.startActivityForResult(i.id.CODIGO_ACTUALIZACION). // Cargar datos desde el web service cargarDatos().

"Error Volley: " + error. } /** * 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. newURL. new Response.d(TAG.GET. new Response. null.getString("estado").getMessage()). switch (mensaje) { case "1": . } }. } } ) ).Request.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { // Procesar respuesta Json procesarRespuesta(response).ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.Method.

//Parsear objeto Meta meta = gson.setText(meta.setBackgroundColor(getResources(). Meta. case "Espiritual": cabecera.getColor(R.color. } // Seteando valores en los views titulo.fromJson(object. case "2": .espiritualColor)).getCategoria()) { case "Salud": cabecera. // Asignar color del fondo switch (meta. break. case "Profesional": cabecera.color.color.profesionalColor)). break.setText(meta.toString(). case "Material": cabecera.saludColor)).class).setBackgroundColor(getResources(). break.getCategoria()). break. case "Finanzas": cabecera.getDescripcion()).getJSONObject("meta").color.getColor(R.// Obtener objeto "meta" JSONObject object = response. prioridad.setBackgroundColor(getResources().getColor(R.getColor(R. fechaLim.setBackgroundColor(getResources().getColor(R. descripcion.setText(meta.setText(meta.setBackgroundColor(getResources(). break.getPrioridad()).getTitulo()).finanzasColor)).setText(meta.getFechaLim()). break. categoria.materialColor)).color.

Toast. case "3": String mensaje3 = response. entonces seteamos los valores correspondientes de cada view. break. La lógica funciona así: Una vez el usuario haya modificado los datos.String mensaje2 = response.getString("mensaje").printStackTrace(). Toast. break.makeText( getActivity().show().LENGTH_LONG).makeText( getActivity().show(). Si hubo éxito. mensaje3. Toast. } } } Para la inclusión de parámetros en la petición GET. } } catch (JSONException e) { e. Si no está de acuerdo o se arrepiente de ello. entonces puede descartar los . Paso #10: Realizar petición POST para editar detalles de la meta Para la edición hemos creado la actividad UpdateActivity.LENGTH_LONG). Al igual que en MainFragment. mensaje2. entonces puede confirmar sus datos presionando el action button de check que usaremos para la confirmación.getString("mensaje"). la cual mostrará el formulario de recolección con los datos de la una meta. se creó un método para procesar la respuesta dependiendo del estado que se obtuvo. adjunta a la URL el valor de idMeta con la convención de formularios '?clave=valor'. Toast.

eliminación y borrado de las metas.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.  Lanzar diálogos para confirmar la eliminación y el descarte de cambios. Incluso puedes incluir la eliminación entre los action buttos.cambios con el segundo action button.android.com/tools" tools:context="com.  Usar Toasts para afianzar la confirmación de las operaciones.android. Veamos la solución: UpdateFragment.actividades.com/apk/res/android" xmlns:app="http://schemas.  Implementar la inserción.herprogramacion.iwish. Así que lo primero es crear un archivo de menú para poblar la action bar: menu_form.java .xml <menu xmlns:android="http://schemas.ui.  Habilitar la contribución a la action bar. ya que lo implementaremos en el Up Button.  Manejar los eventos en cada action button. Teniendo en cuenta esa apreciación las tareas que tienes por implementar son:  Cargar los datos de la meta en los componentes del formulario.UpdateActivity"> <!-.com/apk/res-auto" xmlns:tools="http://schemas.android.Descartar Cambios --> <item android:id="@+id/action_discard" android:title="@string/action_discard" android:orderInCategory="3" app:showAsAction="never" /> <!-.

herprogramacion.Fragment. import java. import com. import com.toolbox.gson.volley. import android. import com.iwish.Activity.Request. import org.app.Spinner.EditText.TextView.Bundle. import com.DialogFragment. import com.iwish.support.MenuItem.os. import android.modelo.herprogramacion.JsonObjectRequest.widget.google.json.widget. /** * Fragmento con formulario para actualizar la meta */ public class UpdateFragment extends Fragment { .app.iwish.support. import android.Response.tools.v4.android.view.view.Toast.Log.Gson.util.import android.Meta.util.herprogramacion.JSONObject. import com.app.json.view.android.HashMap. import android.android. import android.View. import android. import android. import java. import android.v4. import android.LayoutInflater.Constantes.VolleySingleton.Map.util.herprogramacion.view. import android.web. import android.android.widget. import com. import com.volley.VolleyError.iwish.R. import org.widget.volley.JSONException. import com.ViewGroup.volley. import android.

private Spinner prioridad_spinner. private EditText descripcion_input.class.getSimpleName(). private Spinner categoria_spinner. /* Valor del argumento extra */ private String idMeta. /* Controles */ private EditText titulo_input. /* Etiqueta de valor extra para modo edición */ private static final String EXTRA_ID = "IDMETA". private TextView fecha_text. /** * Instancia Gson para el parsing Json */ private Gson gson = new Gson(). ./* Etiqueta de depuración */ private static final String TAG = UpdateFragment. /** * Es la meta obtenida como respuesta de la petición HTTP */ private Meta metaOriginal.

fecha_ejemplo_text).putString(EXTRA_ID. fecha_text.findViewById(R.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().id. descripcion_input = (EditText) v.titulo_input).id.descripcion_input).fragment_form. // Obtención de instancias controles titulo_input = (EditText) v. fecha_text = (TextView) v.setOnClickListener( new View. detailFragment. false). container.findViewById(R. Bundle savedInstanceState) { // Inflando layout del fragmento View v = inflater. return detailFragment. prioridad_spinner = (Spinner) v.id.prioridad_spinner). ViewGroup container.setArguments(bundle).inflate(R. Bundle bundle = new Bundle().findViewById(R.OnClickListener() { @Override public void onClick(View v) { .layout.id.categoria_spinner).id. categoria_spinner = (Spinner) v. extra).findViewById(R. } @Override public View onCreateView(LayoutInflater inflater. bundle.findViewById(R.

GET.Method.GET_BY_ID + "?idMeta=" + idMeta. } /** * Obtiene los datos desde el servidor */ private void cargarDatos() { // Añadiendo idMeta como parámetro a la URL String newURL = Constantes. picker.addToRequestQueue( new JsonObjectRequest( Request. } } ). null. "datePicker").DialogFragment picker = new DatePickerFragment(). new Response. newURL.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { // Procesa la respuesta GET_BY_ID .show(getFragmentManager(). // Obtener valor extra idMeta = getArguments().getString(EXTRA_ID). // Consultar el detalle de la meta VolleySingleton. if (idMeta != null) { cargarDatos().getInstance(getActivity()). } return v.

makeText( getActivity().getString("estado"). // Setear valores de la meta cargarViews(metaOriginal).procesarRespuestaGet(response).getMessage()). } } ) ).getJSONObject("meta").fromJson(meta. .ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log. case "2": String mensaje = response. break.getString("mensaje"). Meta. } /** * Procesa la respuesta de obtención obtenida desde el sevidor * */ private void procesarRespuestaGet(JSONObject response) { try { String estado = response.d(TAG. } }. "Error Volley: " + error.toString(). // Mostrar mensaje Toast. // Guardar instancia metaOriginal = gson. new Response.class). switch (estado) { case "1": JSONObject meta = response.

getDescripcion()).entradas_categoria). } } catch (JSONException e) { e. // Obteniendo la posición del spinner categorias int posicion_categoria = 0. // Obteniendo acceso a los array de strings para categorias y prioridades String[] categorias = getResources(). i++) { if (categorias[i].array.getStringArray(R. String[] prioridades = getResources().RESULT_CANCELED). // Terminar actividad getActivity(). fecha_text. .setText(meta.getFechaLim()). for (int i = 0. } } /** * 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.finish().getStringArray(R.mensaje.entradas_prioridad).array. descripcion_input.setResult(Activity.LENGTH_LONG). Toast.setText(meta.compareTo(meta. // Enviar código de falla getActivity().printStackTrace().getTitulo()).length. i < categorias.getCategoria()) == 0) { posicion_categoria = i.show(). break.

i < prioridades.setSelection(posicion_categoria). de lo contrario false */ public boolean validarCambios() { return metaOriginal.compararCon(obtenederDatos()). } } // Setear selección del Spinner de categorías categoria_spinner.getPrioridad()) == 0) { posicion_prioridad = i.compareTo(meta.d(TAG. for (int i = 0. i++) { Log. break. "posición:" + i). // Obteniendo la posición del spinner de prioridades int posicion_prioridad = 0. } } // Setear selección del Spinner de prioridades prioridad_spinner. } /** * Compara los datos actuales con aquellos que se obtuvieron * por primera vez en la respuesta HTTP * * @return true si los datos no han cambiado. } /** * Retorna en una nueva meta creada a partir * de los datos del formulario actual .length. if (prioridades[i].setSelection(posicion_prioridad).break.

String descripcion = descripcion_input.getText(). ya que no hay cambios getActivity().id.* * @return Instancia {@link Meta} */ private Meta obtenederDatos() { String titulo = titulo_input. else // Terminar actividad. String prioridad = (String) prioridad_spinner.onCreate(savedInstanceState).R. prioridad). titulo.id. descripcion.dialog_delete_msg).action_delete:// ELIMINAR mostrarDialogo(R.toString().toString(). return new Meta("0".getItemId().home:// CONFIRMAR if (!validarCambios()) guardarMeta().toString(). } @Override public void onCreate(Bundle savedInstanceState) { super. return true.getSelectedItem(). fecha. case R.string. String fecha = fecha_text. categoria.getSelectedItem().getText(). String categoria = (String) categoria_spinner. switch (id) { case android. setHasOptionsMenu(true).finish(). .getText(). // Contribución a la AB } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.

} .toString(). final String prioridad = prioridad_spinner.string.toString(). HashMap<String. . final String fecha = fecha_text. return super.put("titulo". } /** * Guarda los cambios de una meta editada. String> map = new HashMap<>(). final String descripcion = descripcion_input. titulo).getSelectedItem().dialog_discard_msg). final String categoria = categoria_spinner.onOptionsItemSelected(item). } else // Terminar actividad.getText(). ya que no hay cambios getActivity().action_discard:// DESCARTAR if (!validarCambios()) { mostrarDialogo(R.toString(). entonces crea una nueva * meta en la base de datos */ private void guardarMeta() { // Obtener valores actuales de los controles final String titulo = titulo_input.finish(). * <p> * Si está en modo inserción.toString().// Mapeo previo map. break. case R.getText(). map.getSelectedItem().id.break. idMeta).getText().toString().put("idMeta".

// Depurando objeto Json. prioridad). map.put("descripcion". .put("prioridad".put("fechaLim".ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log. fecha). "application/json. headers.. jobject. // Actualizar datos en el servidor VolleySingleton.d(TAG.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { procesarRespuestaActualizar(response). String> headers = new HashMap<String.put("Content-Type". jobject..addToRequestQueue( new JsonObjectRequest( Request. new Response. map.getMessage()).map.UPDATE. } }. // Crear nuevo objeto Json basado en el mapa JSONObject jobject = new JSONObject(map). Log.POST.getInstance(getActivity()).put("categoria". } } ){ @Override public Map<String.d(TAG. String> getHeaders() { Map<String. Constantes. String>(). map. charset=utf-8"). new Response. "Error Volley: " + error. descripcion).toString()). categoria).Method.

charset=utf-8" + getParamsEncoding(). return headers. "application/json").put("idMeta".put("Accept". new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { .headers.POST. } } ).getInstance(getActivity()).addToRequestQueue( new JsonObjectRequest( Request.// MAPEO map. idMeta).// Identificador JSONObject jobject = new JSONObject(map).// Objeto Json // Eliminar datos en el servidor VolleySingleton. String> map = new HashMap<>().DELETE. } @Override public String getBodyContentType() { return "application/json.Method. } /** * Procesa todos las tareas para eliminar * una meta en la aplicación. Este método solo se usa * en la edición */ public void eliminarMeta() { HashMap<String. Constantes. jobject.

charset=utf-8"). new Response. } } ). return headers. } /** * Procesa la respuesta de eliminación obtenida desde el sevidor */ private void procesarRespuestaEliminar(JSONObject response) { try { . headers. "application/json. charset=utf-8" + getParamsEncoding().// Procesar la respuesta procesarRespuestaEliminar(response). } @Override public String getBodyContentType() { return "application/json. String> getHeaders() { Map<String.put("Content-Type".d(TAG. headers.getMessage()). "Error Volley: " + error. "application/json"). String> headers = new HashMap<String.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.put("Accept". String>(). } } ){ @Override public Map<String. } }.

// Terminar actividad getActivity().setResult(203). } } .LENGTH_LONG).show(). // Enviar código de falla getActivity().setResult(Activity.makeText( getActivity().printStackTrace().show(). mensaje. } } catch (JSONException e) { e. Toast. Toast.makeText( getActivity().LENGTH_LONG). case "2": // Mostrar mensaje Toast. switch (estado) { case "1": // Mostrar mensaje Toast. // Obtener mensaje String mensaje = response. mensaje.getString("estado").getString("mensaje").RESULT_CANCELED).// Obtener estado String estado = response.finish(). break.finish(). // Terminar actividad getActivity(). // Enviar código de éxito getActivity(). break.

getString("mensaje").setResult(Activity. Toast.LENGTH_LONG).makeText( getActivity(). break.RESULT_OK). // Terminar actividad getActivity().finish(). // Enviar código de éxito getActivity(). // Obtener mensaje String mensaje = response.show(). Toast.setResult(Activity.getString("estado").LENGTH_LONG). mensaje. .show(). // Enviar código de falla getActivity(). mensaje./** * Procesa la respuesta de actualización obtenida desde el sevidor */ private void procesarRespuestaActualizar(JSONObject response) { try { // Obtener estado String estado = response.finish().makeText( getActivity(). case "2": // Mostrar mensaje Toast.RESULT_CANCELED). switch (estado) { case "1": // Mostrar mensaje Toast. // Terminar actividad getActivity().

int mes. } } /** * Actualiza la fecha del campo {@link fecha_text} * * @param ano Año * @param mes Mes * @param dia Día */ public void actualizarFecha(int ano. dialogo.break. } /** * Muestra un diálogo de confirmación.show(getFragmentManager().setText(ano + "-" + (mes + 1) + "-" + dia). } } . } } catch (JSONException e) { e. "ConfirmDialog"). getString(id)). int dia) { // Setear en el textview la fecha fecha_text. createInstance( getResources().printStackTrace(). cuyo mensaje esta * basado en el parámetro identificador de Strings * * @param id Parámetro */ private void mostrarDialogo(int id) { DialogFragment dialogo = ConfirmDialogFragment.

Este código es un poco largo debido a que tenemos la implementación de diálogos y comunicaciones de datos. En cuanto a los diálogos. Estos son: guardarMeta() y borrarMeta().Bundle. Manejar los eventos en cada action button: Para lograr esta tarea se implementó el métodoonOptionsItemSelected(). Recuerda usar onHasOptionMenu() en onCreate() para que el fragmento pueda escuchar los eventos de la action bar. . Inmediatamente los datos conseguidos en la petición.java import android. Haremos exactamente lo mismo que hemos venido haciendo. Por lo que a continuación te explico la esencia de las peticiones de información.os. Paso #11: Realizar petición para insertar nuevos registros La inserción de nuevas metas la crearemos en una nueva actividad llamada InsertActivity junto a un fragmento InsertFragment. los seteamos en cada view del formulario. Veamos: InsertActivity. usando los valores actuales del formulario. Si existe un valor extra. lanzamos la misma petición que hemos usado para conseguir el detalle de la meta con el método cargarDatos(). Implementar la inserción. simplemente usamos el formato clásico de ACEPTAR|CANCELAR para permitir o no el efecto de los métodos en la base de datos. eliminación y borrado de las metas: Cada operación en la base de datos tiene un método asignado para su realización. Iniciaremos el fragmento y estaremos a la espera de que el usuario guarde los datos o los descarte. El primer método realiza una petición POST con la respectiva URL del servicio de actualización. Puedes encontrar la implementación completa descargando el código en la parte superior del artículo. Cargar los datos de la meta en los componentes del formulario: En el método onCreateView()obtenemos el valor extra con que fue creado el fragmento. Similarmente borrarMeta() envía el id de la meta que se desea eliminar hacia la dirección correspondiente. donde se creó una estructura switch que permitiera la ejecución del método correspondiente a la acción.

herprogramacion.v4.beginTransaction() .herprogramacion.iwish.OnDateSelectedListener.iwish.support.iwish.InsertFragment.herprogramacion. import com. return true. } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().view.iwish. import com.ConfirmDialogListener { @Override protected void onCreate(Bundle savedInstanceState) { super.herprogramacion.ConfirmDialogFragment.Menu.DialogFragment. import com. import com.setHomeAsUpIndicator(R.container. menu). setContentView(R.R.onCreate(savedInstanceState).menu_form.AppCompatActivity.inflate(R.fragmentos. import android.v7.ui.ui.menu.app.ui.fragmentos. import android.id.add(R.layout.app.commit(). "InsertFragment") . ConfirmDialogFragment.ic_done).DatePickerFragment. public class InsertActivity extends AppCompatActivity implements DatePickerFragment. } .mipmap. if (getSupportActionBar() != null) getSupportActionBar(). new InsertFragment().activity_main).import android. // Creación del fragmento de inserción if (savedInstanceState == null) { getSupportFragmentManager().fragmentos.support.

findFragmentByTag("InsertFragment"). if (insertFragment != null) { finish(). if (insertFragment != null) { insertFragment. int day) { InsertFragment insertFragment = (InsertFragment) getSupportFragmentManager(). day). } } @Override public void onDialogPositiveClick(DialogFragment dialog) { InsertFragment insertFragment = (InsertFragment) getSupportFragmentManager().findFragmentByTag("InsertFragment").@Override public void onDateSelected(int year. month. int month.java . if (insertFragment != null) { // Nada por el momento } } } Ahora el fragmento de inserción tiene las siguientes características: InsertFragment.actualizarFecha(year. // Finalizar actividad descartando cambios } } @Override public void onDialogNegativeClick(DialogFragment dialog) { InsertFragment insertFragment = (InsertFragment) getSupportFragmentManager().findFragmentByTag("InsertFragment").

JSONObject.app.HashMap. import android.view. import android.Spinner.TextView.Log.JsonObjectRequest.volley.ViewGroup.tools.Constantes.LayoutInflater.View.view.DialogFragment.volley.json.widget.view. import android.view. import android.v4.VolleySingleton.web. import android.android. import android.json. import com.util.android. import com.app. import com.v4.R.iwish.Toast.volley.JSONException.EditText.Response.MenuItem.herprogramacion.view.support. import android. import com.android.android.Request.Activity. /** * Fragmento que permite al usuario insertar un nueva meta */ public class InsertFragment extends Fragment { . import android.widget.view. import com.Bundle. import java.herprogramacion.app.toolbox.iwish.VolleyError.Fragment.iwish.os.Menu.util. import android. import android. import org.widget. import com.widget.volley.Map. import android.import android. import com. import android. import java. import org.herprogramacion. import android.MenuInflater. import android.util.support.

/**
* Etiqueta para depuración
*/
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);

// Obtención 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 inserción, 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);

. Constantes. .map.put("Content-Type". jobject. String> getHeaders() { Map<String.Method. // Depurando objeto Json. "application/json").ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.put("categoria". } } ){ @Override public Map<String.toString()). charset=utf-8"). String> headers = new HashMap<String.put("Accept". new Response. "Error Volley: " + error. String>(). new Response.d(TAG.addToRequestQueue( new JsonObjectRequest( Request. jobject. } }. headers.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { // Procesar la respuesta del servidor procesarRespuesta(response). Log. // Crear nuevo objeto Json basado en el mapa JSONObject jobject = new JSONObject(map).getMessage()). "application/json. headers. // Actualizar datos en el servidor VolleySingleton. categoria).getInstance(getActivity()).POST..d(TAG.put("prioridad". map.INSERT. prioridad).

} } ). } @Override public String getBodyContentType() { return "application/json. // Enviar código de éxito getActivity(). switch (estado) { case "1": // Mostrar mensaje Toast. mensaje. // Terminar actividad .show().getString("mensaje").RESULT_OK).getString("estado"). } /** * Procesa la respuesta obtenida desde el sevidor * * @param response Objeto Json */ private void procesarRespuesta(JSONObject response) { try { // Obtener estado String estado = response.makeText( getActivity(). Toast.return headers. charset=utf-8" + getParamsEncoding().LENGTH_LONG).setResult(Activity. // Obtener mensaje String mensaje = response.

} } catch (JSONException e) { e. String descripcion = descripcion_input. false si ambos * están completos */ public boolean camposVacios() { String titulo = titulo_input.finish(). Toast. } . case "2": // Mostrar mensaje Toast. } } /** * Valida si los campos {@link titulo_input} y {@link descripcion_input} * se han rellenado * * @return true si alguno o dos de los campos están vacios.getText(). // Terminar actividad getActivity(). break.setResult(Activity.toString().getText().show(). mensaje.finish(). break.makeText( getActivity().getActivity(). // Enviar código de falla getActivity().isEmpty()).toString().printStackTrace().LENGTH_LONG). return (titulo.isEmpty() || descripcion.RESULT_CANCELED).

dialog_discard_msg)).setText(ano + "-" + (mes + 1) + "-" + dia). createInstance( getResources(). Ambos se basan en la validación de los campos del formulario que requieren texto escrito por parte del usuario. puedes ver que existe la posibilidad de guardar y descartar los datos./** * Actualiza la fecha del campo {@link fecha_text} * * @param ano Año * @param mes Mes * @param dia Día */ public void actualizarFecha(int ano.show(getFragmentManager(). getString(R. int mes. Si te fijas en el procesamiento de los eventos sobre la action bar. int dia) { // Setear en el textview la fecha fecha_text. Dependiendo de su retorno así mismo procederemos. } } Esta vez hemos creado un método llamado guardarMeta() basado en la URL del servicio de inserción y los datos que el usuario haya completado. Ni tampoco puede intentar descartar cambios sin ver un diálogo si ya ha escrito algún dato.string. "ConfirmDialog"). Esto quiere decir que el usuario no puede guardar una meta sin completar alguno de los campos. } /** * Muestra un diálogo de confirmación */ public void mostrarDialogo() { DialogFragment dialogo = ConfirmDialogFragment. . dialogo. Para ello se creó el método camposVacios().

Ejecutar Proyecto Completo En Android Studio Recuerda que puedes descargar el proyecto completo con el botón que tienes en el inicio del articulo. . Al final. Sin embargo el uso de un estilo de comunicación elegante como REST es un excelente complemento para estructurar una buena API. luego de haber seguido todos los pasos la aplicación 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.

. Mysql y Php. Esto lo hace un excelente complemento para implementar una API entre Android.Json es un formato muy flexible y cómodo a la vista.