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

¿Deseas conectar una aplicación Android a Mysql?

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

Pues bien, en este artículo te mostraré algunas ideas sobre la creación de una aplicación android que consuma los datos de un servidor externo a través de Php, Mysql y Json.

Para ello he creado una aplicación llamada “I Wish”, la cual permite a nuestros usuarios guardar una lista de deseos y metas que tienen en su vida. Con este ejemplo podrás ver cómo implementar la inserción, edición, eliminación y consulta de datos a través de un Web Service.

Si sigues leyendo podrás obtener el siguiente resultado:

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

1. ¿Qué Es Un Web Service?

Un Web Service o Servicio Web es un aplicativo que facilita la interoperabilidad entre varios sistemas independientemente 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:

Wireframe Aplicación Android CRUD

Basado en el boceto que acabas de crear ya puedes identificar que la cantidad de actividades, fragmentos, diálogos y formularios que necesitas. Así que veamos la siguiente lista de materiales a crear:

  • Actividad principal con un fragmento de lista.
  • Layout personalizado para ítems.
  • Actividad con fragmento de detalle.
  • Actividad con fragmento de formulario para inserción.
  • Actividad con fragmento de formulario para edición.

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

4. Crear UI Para La Aplicación Android

4.1 Diseñar Actividad Principal Con Fragmento Tipo Lista

Después de haber creado tú proyecto en Android Studio vas a crear una actividad principal que contengan un fragmento con una lista. Debido a que vamos a añadir los fragmentos programáticamente no es necesario enfocarnos tanto en los layouts de las actividades. Incluso puedes usar un solo layout para todas las actividades.

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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 diseño es diferente. La idea es añadir el recycler para recubrir toda la actividad y además añadir un Floating Action Button en la parte inferior derecha con el fin de que el usuario añada nuevas metas.

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

Incluso podrías basarte en el ejemplo del sitio de android devepers llamado FloatingActionButtonBasic. Todo depende de ti. Cada librería trae la explicación de su implementación, así que no hay excusas.

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

compile 'com.melnykov:floatingactionbutton:1.3.0'

Veamos cómo queda el layout del fragmento principal:

fragment_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fab="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fragment_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/reciclador"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="3dp"
        android:scrollbars="vertical" />

    <com.melnykov.fab.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@mipmap/ic_add"
        fab:fab_colorNormal="@color/accent"
        fab:fab_colorPressed="@color/primary"
        fab:fab_colorRipple="@color/ripple" />


</RelativeLayout>

Se usa una etiqueta <com.melnykov.fab.FloatingActionButton> para implementar el FAB. Simplemente se ubica en la parte inferior derecha y le añadimos los colores correspondientes a su interacción.

Donde colorNormal es el color que tiene en estado natural; colorPressed es aquel que se proyecta cuando lo presionamos rapidamente y colorRipple se evidencia cuando mantienes un click largo sobre él.

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

Dimensiones Floating Action Button Grande En Material Design

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

Dimensiones Floating Action Button Mini En Material Design

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

4.2 Diseñar Actividad De Detalle Con Fragmento Personalizado

Acudiendo a los estilos de layouts en Material Design, dividiremos el fragmento de detalle en dos pasos. El primero será una ImageView alusivo a la categoría de la meta y el segundo será una hoja para sus datos completos. Adicionalmente añadiremos un Float Button Action para la edición de la meta.

fragment_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fab="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <!-- Parte superior -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="50">

        <ImageView
            android:id="@+id/cabecera"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="30"
            android:layout_marginBottom="28dp" />

        <com.melnykov.fab.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_gravity="bottom|right"
            android:src="@mipmap/ic_edit"
            fab:fab_colorNormal="@color/colorNormalMini"
            fab:fab_colorPressed="@color/colorPressedMini"
            fab:fab_colorRipple="@color/colorRippleMini"
            android:layout_marginLeft="16dp"
            fab:fab_type="mini"
            android:layout_marginBottom="8dp"/>

        <TextView
            android:id="@+id/titulo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Titulo"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:layout_marginBottom="48dp"
            android:layout_toRightOf="@+id/fab"
            android:layout_alignParentBottom="true"
            android:layout_marginLeft="16dp"
            android:textColor="@android:color/white" />

    </RelativeLayout>

    <!-- Datos de la meta -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="70"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">


        <TextView
            android:id="@+id/categoria"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/categoria_label"
            android:text="Categoría"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:layout_marginBottom="16dp" />

        <TextView
            android:id="@+id/fecha"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@+id/fecha_label"
            android:text="Fecha"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:layout_marginBottom="16dp" />

        <TextView
            android:id="@+id/prioridad"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/prioridad_label"
            android:text="Prioridad"
            android:textAppearance="?android:attr/textAppearanceSmall" />

        <TextView
            android:id="@+id/descripcion"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/descripcion_label"
            android:text="Descripción"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:layout_marginBottom="16dp" />


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

Actividad De Detalle Aplicación Android Con Material Design

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.

4.3 Diseñar Actividad Con Formulario

La inserción y edición requiere de la proyección de un formulario que contenga los controles necesarios para que el usuario especifique la información personalizada que desea almacenar en la base de datos. Para ello debes crear un layout con los datos que viste en los requerimientos de la aplicación con las respectivos views para obtener la información.

Formulario Aplicación Android Con Material Design

Por ejemplo…

El titulo de cada meta recibe texto escrito desde el input del dispositivo, por lo que sabemos que el EditText es la solución para este caso. La descripción es igual, necesita un campo de texto. La fecha límite puede ser obtenida a través de un DatePicker y para la categoría que tiene un dominio de varias opciones, puedes usar un Spinner.

Veamos:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.herprogramacion.iwish.ui.fragmentos.UpdateFragment">

    <!-- Titulo-->
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/titulo_input"
        android:layout_alignParentTop="false"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:hint="Título"
        android:minLines="1"
        android:maxLines="1"
        android:maxLength="55"
        android:phoneNumber="false"
        android:singleLine="true"
        android:paddingTop="16dp"
        android:paddingBottom="16dp" />

    <!-- Descripción -->
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/descripcion_input"
        android:layout_below="@+id/titulo_input"
        android:layout_centerHorizontal="true"
        android:hint="Descripción"
        android:maxLength="128"
        android:nestedScrollingEnabled="true"
        android:paddingTop="16dp"
        android:paddingBottom="16dp" />

    <!-- Etiqueta Fecha -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Fecha"
        android:id="@+id/fecha_text"
        android:layout_below="@+id/descripcion_input"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:paddingTop="16dp"
        android:textColor="@android:color/black" />

    <!-- Fecha -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="2015/05/17"
        android:id="@+id/fecha_ejemplo_text"
        android:layout_below="@+id/fecha_text" />

    <!-- Categoría -->
    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/categoria_spinner"
        android:entries="@array/entradas_categoria"
        android:layout_below="@+id/categoria_texto" />

    <!-- Etiqueta Categoría -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Categoría"
        android:id="@+id/categoria_texto"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/fecha_ejemplo_text"
        android:paddingTop="16dp"
        android:textColor="@android:color/black" />

    <!-- Etiqueta Prioridad -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Prioridad"
        android:id="@+id/prioridad_text"
        android:layout_below="@+id/categoria_spinner"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:textColor="@android:color/black"
        android:paddingTop="16dp" />

    <!-- Prioridad -->
    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/prioridad_spinner"
        android:layout_below="@+id/prioridad_text"
        android:entries="@array/entradas_prioridad" />

</RelativeLayout>

4.4 Diseñar Layout Personalizado De Los Items

La organización de los atributos de cada meta dentro de los ítems de la lista debe ser un resumen de sus características principales. Puedes dejar la descripción solo para la actividad del detalle y eliminarlo de la presentación en la lista.

item_list.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin">

    <!-- Titulo -->
    <TextView
        android:id="@+id/titulo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Titulo"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_below="@+id/fecha"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="16dp" />

    <!-- Categoría -->
    <TextView
        android:id="@+id/categoria"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Categoría"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@color/accent" />

    <!-- Fecha -->
    <TextView
        android:id="@+id/fecha"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fecha"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="false"
        android:layout_alignParentStart="false"
        android:textColor="@android:color/black"
        android:layout_toRightOf="@+id/imageView" />

    <!-- Prioridad -->
    <TextView
        android:id="@+id/prioridad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Prioridad"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:layout_marginTop="8dp"
        android:layout_below="@+id/titulo"
        android:textStyle="italic" />

    <!-- Icono para la fecha -->
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:src="@mipmap/ic_calendar"
        android:layout_marginRight="3dp" />
</RelativeLayout>

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

Item De Un RecyclerView En Android

5. Codificación Del Web Service En Php

Antes de crear la aplicación Android debes desarrollar primero tu Web Service con cualquiera de los estándares que te interesen. El alcance de este tutorial no abarca el uso de restricciones REST, SOAP, RPC o sus parecidos. Simplemente verás cómo crear las implementaciones Php necesarias para realizar operaciones sobre una base de datos en Mysql a través de peticiones GET y POST.

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

Para desarrollar este aplicativo usaré el entorno de desarrollo XAMPP, el cual provee automáticamente una configuración de un servidor Apache local, el intérprete de Php y el gestor Mysql.

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

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

Diseñar base de datos: Si ya lo has notado, la base de datos de la aplicación I Wish solo tiene una entidad que representa a los registros de las metas. Esto reduce ampliamente el diseño de bases de datos en el problema.

Los proyectos reales tienen modelos más complejos.

Nota: Si aún eres principiante en bases de datos, te recomiendo descargar mi ebook metodología de diseño de bases de datos. Créeme, te será de mucha utilidad.

Meta debe tener los atributos que hemos venido viendo más una llave primaria que mantenga la integridad de los datos. Observa el siguiente minidiagrama entidad-relación:

Diagrama Entidad-Relación De Aplicación Android

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. Donde le asignaremos el nombre de “i_wish”.

Crear Nueva Base De Datos en phpMyAdmin

Ahora crea la tabla meta para que contenga seis columnas en su estructura y además usa el formato UTF-8 para soportar acentos. Puedes hacerlo a través del editor de phpMyAdmin o con el siguiente comando CREATE:

CREATE TABLE IF NOT EXISTS meta(
    idMeta int(3) PRIMARY KEY AUTO_INCREMENT,
  titulo varchar(56) NOT NULL,
  descripcion varchar(128) NOT NULL,
  prioridad enum('Alta','Media','Baja','') NULL DEFAULT 'Alta',
  fechaLim date NOT NULL,
  categoria enum('Salud','Finanzas','Espiritual','Profesional','Material') NOT NULL DEFAULT 'Finanzas'
)

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

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

5.2 Crear Código Php Para Consumir Datos

En primera instancia crea una conexión a la base de datos Mysql con la interfaz que más se acomode a tus necesidades. En mi caso voy a crear una conexión con PDO, la cual me permite proteger los datos de inyecciones sql.

Luego de eso crea una clase que mapee la estructura de la tabla meta. El objetivo de ello es proveerla de comportamientos de inserción, actualización, eliminación y consulta a través de la conexión a la base de datos.

Finalmente implementaré scripts Php para gestionar las peticiones que lanzan los clientes. La idea es parsear los datos en formato Json para que nuestra aplicación Android interprete los resultados de forma legible.

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

El uso de PDO depende del enfoque que tengan tus proyectos, puedes crear una clase que represente la conexión hacia la base de datos o simplemente crear una nueva conexión en cada script de Php que tengas.

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

No obstante hay patrones de diseño muy interesantes que puedes consultar en la web. Por ejemplo el repositorio del usuario indieteq en github. Él se enfoca en la implementación del CRUD de una forma muy sencilla y orientada a objetos.

Veamos el resultado del patrón singleton:

Database.php

<?php
/**
 * Clase que envuelve una instancia de la clase PDO
 * para el manejo de la base de datos
 */

require_once 'mysql_login.php';


class Database
{

    /**
     * Única instancia de la clase
     */
    private static $db = null;

    /**
     * Instancia de PDO
     */
    private static $pdo;

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


    }

    /**
     * Retorna en la única instancia de la clase
     * @return Database|null
     */
    public static function getInstance()
    {
        if (self::$db === null) {
            self::$db = new self();
        }
        return self::$db;
    }

    /**
     * Crear una nueva conexión PDO basada
     * en los datos de conexión
     * @return PDO Objeto PDO
     */
    public function getDb()
    {
        if (self::$pdo == null) {
            self::$pdo = new PDO(
                'mysql:dbname=' . DATABASE .
                ';host=' . HOSTNAME .
                ';port:63343;',
                USERNAME,
                PASSWORD,
                array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
            );

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

        return self::$pdo;
    }

    /**
     * Evita la clonación del objeto
     */
    final protected function __clone()
    {
    }

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

?>

Ten en cuenta que la conexión se abre con 4 cadenas descriptivas del entorno que estás usando declaradas en el archivo mysql_login.php. Con ello me refiero al nombre del host, el nombre de la base de datos, el usuario con que deseas ingresar y su respectiva contraseña.

Por el momento usaremos el localhost debido a las pruebas que estamos haciendo. El usuario ya depende de ti, en mi caso uso el usuario por defecto "root" y sin contraseña alguna.

mysql_login.php

<?php
/**
 * Provee las constantes para conectarse a la base de datos
 * Mysql.
 */
define("HOSTNAME", "localhost");// Nombre del host
define("DATABASE", "i_wish"); // Nombre de la base de datos
define("USERNAME", "root"); // Nombre del usuario
define("PASSWORD", ""); // Nombre de la constraseña
?>

Adicionalmente debes añadir al cuarto parámetro del constructor de PDO la indicación SET NAMES UTF-8 para el servidor. Esto permite que los datos de la base de datos vengan codificados en este formato para evitar problemas de compatibilidad.

Paso #4: Crear clase para las metas

En este paso vas a modelar en una clase a la tabla "meta" de tal forma que aplique el CRUD sobre los datos a través de la clase Database. En esencia necesitas un método para obtener todos los registros, uno para la inserción, otro para eliminación, también para la actualización y un método que permita obtener del detalle de un solo registro.

Meta.php

<?php

/**
 * Representa el la estructura de las metas
 * almacenadas en la base de datos
 */
require 'Database.php';

class Meta
{
    function __construct()
    {
    }

    /**
     * Retorna en la fila especificada de la tabla 'meta'
     *
     * @param $idMeta Identificador del registro
     * @return array Datos del registro
     */
    public static function getAll()
    {
        $consulta = "SELECT * FROM meta";
        try {
            // Preparar sentencia
            $comando = Database::getInstance()->getDb()->prepare($consulta);
            // Ejecutar sentencia preparada
            $comando->execute();

            return $comando->fetchAll(PDO::FETCH_ASSOC);

        } catch (PDOException $e) {
            return false;
        }
    }

    /**
     * Obtiene los campos de una meta con un identificador
     * determinado
     *
     * @param $idMeta Identificador de la meta
     * @return mixed
     */
    public static function getById($idMeta)
    {
        // Consulta de la meta
        $consulta = "SELECT idMeta,
                            titulo,
                             descripcion,
                             prioridad,
                             fechaLim,
                             categoria
                             FROM meta
                             WHERE idMeta = ?";

        try {
            // Preparar sentencia
            $comando = Database::getInstance()->getDb()->prepare($consulta);
            // Ejecutar sentencia preparada
            $comando->execute(array($idMeta));
            // Capturar primera fila del resultado
            $row = $comando->fetch(PDO::FETCH_ASSOC);
            return $row;

        } catch (PDOException $e) {
            // Aquí puedes clasificar el error dependiendo de la excepción
            // para presentarlo en la respuesta Json
            return -1;
        }
    }

    /**
     * Actualiza un registro de la bases de datos basado
     * en los nuevos valores relacionados con un identificador
     *
     * @param $idMeta      identificador
     * @param $titulo      nuevo titulo
     * @param $descripcion nueva descripcion
     * @param $fechaLim    nueva fecha limite de cumplimiento
     * @param $categoria   nueva categoria
     * @param $prioridad   nueva prioridad
     */
    public static function update(
        $idMeta,
        $titulo,
        $descripcion,
        $fechaLim,
        $categoria,
        $prioridad
    )
    {
        // Creando consulta UPDATE
        $consulta = "UPDATE meta" .
            " SET titulo=?, descripcion=?, fechaLim=?, categoria=?, prioridad=? " .
            "WHERE idMeta=?";

        // Preparar la sentencia
        $cmd = Database::getInstance()->getDb()->prepare($consulta);

        // Relacionar y ejecutar la sentencia
        $cmd->execute(array($titulo, $descripcion, $fechaLim, $categoria, $prioridad, $idMeta));

        return $cmd;
    }

    /**
     * Insertar una nueva meta
     *
     * @param $titulo      titulo del nuevo registro
     * @param $descripcion descripción del nuevo registro
     * @param $fechaLim    fecha limite del nuevo registro
     * @param $categoria   categoria del nuevo registro
     * @param $prioridad   prioridad del nuevo registro
     * @return PDOStatement
     */
    public static function insert(
        $titulo,
        $descripcion,
        $fechaLim,
        $categoria,
        $prioridad
    )
    {
        // Sentencia INSERT
        $comando = "INSERT INTO meta ( " .
            "titulo," .
            " descripcion," .
            " fechaLim," .
            " categoria," .
            " prioridad)" .
            " VALUES( ?,?,?,?,?)";

        // Preparar la sentencia
        $sentencia = Database::getInstance()->getDb()->prepare($comando);

        return $sentencia->execute(
            array(
                $titulo,
                $descripcion,
                $fechaLim,
                $categoria,
                $prioridad
            )
        );

    }

    /**
     * Eliminar el registro con el identificador especificado
     *
     * @param $idMeta identificador de la meta
     * @return bool Respuesta de la eliminación
     */
    public static function delete($idMeta)
    {
        // Sentencia DELETE
        $comando = "DELETE FROM meta WHERE idMeta=?";

        // Preparar la sentencia
        $sentencia = Database::getInstance()->getDb()->prepare($comando);

        return $sentencia->execute(array($idMeta));
    }
}

?>

Recuerda que el método prepare() permite reemplazar los placeholders ('?') a través de execute(). Esto protege la operación de inyecciones que puedan atentar contra la seguridad de los datos.

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

Para retornar todos los registros que existen en la tabla "meta" usaremos el método getAll() de la clase Meta. La trata de la petición seguiría la siguiente lógica:

  1. Comprobar que la petición se realizó con el método GET.
  2. Obtener todos los registros.
  3. ¿La obtención tuvo éxito?
    1. SI -> Retornar objeto Json con los datos
    2. 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 petición fallida, la falla de autenticación, la no existencia del recurso, la no disponibilidad del servidor, etc. En resumen, contempla todas las fallas tanto del lado del servidor (códigos 5xx) como las del cliente (códigos 4xx).

No obstante este ejemplo se basa en el comportamiento ideal de nuestro servidor local. Donde solo reportaremos aquellas anomalías que sucedan en la base de datos, asumiendo que la respuesta siempre tendrá un código de estado 200. Esto permitirá trackear si nuestro web service está operando bien la base de datos.

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

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

Es justo donde entra Json para actuar como formato de comunicación. En cada respuesta enviaremos una seria de elementos Json que puedan ser interpretados del lado del cliente. Esto te será posible usando las funciones json_encode() y json_decode(). La primera parsea un tipo de dato a un string en formato json y la segunda es el procedimiento contrario.

Veamos nuestro servicio de obtención:

obtener_metas.php

<?php
/**
 * Obtiene todas las metas de la base de datos
 */

require 'Meta.php';

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

    // Manejar petición GET
    $metas = Meta::getAll();

    if ($metas) {

        $datos["estado"] = 1;
        $datos["metas"] = $metas;

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

El objeto Json que retornaremos tiene un atributo llamado "estado" el cual representa un código para indicar la calidad del resultado. Si es 1, entonces añadiremos otro atributo llamado "metas" el cual es un array de objetos con los datos de las metas. Si es 2, entonces usaremos un atributo "mensaje" para avisar a la aplicación cliente que ocurrió un error en la operación a la base de datos.

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

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

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

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

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

Por ejemplo…

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

Para tener en cuenta estos casos, puedes consultar los datos de acuerdo a una serie de parámetros establecidos en la API. Esto quiere decir que podrías incluir en el cuerpo de la petición variables que actúen como filtros en la selección. Sin embargo dicho tema está fuera del alcance de nuestro artículo.

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

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

El segundo caso requiere que la petición traiga consigo el identificador de la meta que se desea ver en detalle. Con este dato es posible usar el método getById() de Meta para conseguir el array necesario.

Veamos:

<?php
/**
 * Obtiene el detalle de una meta especificada por
 * su identificador "idMeta"
 */

require 'Meta.php';

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

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

        // Obtener parámetro idMeta
        $parametro = $_GET['idMeta'];

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


        if ($retorno) {

            $meta["estado"] = "1";
            $meta["meta"] = $retorno;
            // Enviar objeto json de la meta
            print json_encode($meta);
        } else {
            // Enviar respuesta de error general
            print json_encode(
                array(
                    'estado' => '2',
                    'mensaje' => 'No se obtuvo el registro'
                )
            );
        }

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

Para retornar el detalle obviamente primero debes comprobar que el parámetro vino con la petición GET y si vino bien definido. Recuerda que la función isset() es quién realiza este trabajo.

Esta vez tenemos tres casos generales posibles. Que la consulta sea un éxito y el registro con el identificador enviado existe. Lo que retorna en un objeto Json con un objeto interno que tiene los datos de la meta.

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

O también puede que PDO haya arrojado una excepción por algún motivo. Por ejemplo un error de sintaxis, la inexistencia del registro, etc. Con ello envías tu objeto representativo del estado 2.

{
    "estado":"2",
   "mensaje":"No se obtuvo el registro"
}

Ahora bien, puede que por alguna razón el parámetro no haya venido en la petición, o que pueda que haya venido pero con otro nombre. Para este caso envías tu código 3 indicando este mensaje.

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

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

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

<?php
/**
 * Insertar una nueva meta en la base de datos
 */

require 'Meta.php';

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

    // Decodificando formato Json
    $body = json_decode(file_get_contents("php://input"), true);

    // Insertar meta
    $retorno = Meta::insert(
        $body['titulo'],
        $body['descripcion'],
        $body['fechaLim'],
        $body['categoria'],
        $body['prioridad']);

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

La primera instrucción es comprobar la petición POST obtenida. Luego de ello conviertes el cuerpo de la petición a un arreglo de strings. Esto es posible consultando el flujo con file_get_contents(), que convierte un archivo a string. Obviamente es necesario que uses la convención “php://input” para acceder al cuerpo de la petición POST.

Ahora, el resultado que obtengas con file_get_contents() debe estar en formato Json, por lo que convertiremos esos datos a un arreglo asociativo que nos permita acceder a la información. Para ello usa la función json_decode() y pasa como segundo parámetro el valor de true.

Luego usa el método insert() de Meta y comprueba el resultado. Esta vez no retornas en filas de la base de datos, así que el estado 1 contiene un mensaje de éxito.

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

De lo contrario usa un mensaje general de error.

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

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

La actualización es casi idéntica a la inserción, solo que esa vez debemos obtener el identificador de la meta para saber que registro actualizar. De resto procedemos con el método update() de Meta sin problemas:

<?php
/**
 * Actualiza una meta especificada por su identificador
 */

require 'Meta.php';

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

    // Decodificando formato Json
    $body = json_decode(file_get_contents("php://input"), true);

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

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

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

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

Al igual que el objeto Json de error:

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

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

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

<?php
/**
 * Elimina una meta de la base de datos
 * distinguida por su identificador
 */

require 'Meta.php';

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

    // Decodificando formato Json
    $body = json_decode(file_get_contents("php://input"), true);

    $retorno = Meta::delete($body['idMeta']);

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

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

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

Y los errores se mostrarían así:

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

6. Codificación De La Aplicación Android

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

  • Un patrón singleton Volley para las peticiones (o un cliente HttpURLConnection si lo deseas).
  • Crear la petición personalizada para tratar respuestas Json (el código ya fue tratado en el artículo de Volley).
  • Crear un adaptador que procese los elementos del recycler view.
  • Tratar los eventos para la comunicación de datos a través de los controles.

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

Paso #1: Crear Patrón Singleton Volley

Este paso ya hace parte de nuestra rutina para gestionar peticiones HTTP. Así que reutilizarás el singleton de artículos pasados para simplificar procesos. La única diferencia que tendrás será la ausencia del ImageLoader como atributo. En esta ocasión no usaremos caching de imágenes, así que es justo dejarlo descansar.

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

VolleySingleton.java

import android.content.Context;

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

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

public final class VolleySingleton {

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


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

    /**
     * Retorna la instancia unica del singleton
     * @param context contexto donde se ejecutarán las peticiones
     * @return Instancia
     */
    public static synchronized VolleySingleton getInstance(Context context) {
        if (singleton == null) {
            singleton = new VolleySingleton(context.getApplicationContext());
        }
        return singleton;
    }

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

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

}

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

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

    /**
     * Transición Detalle -> Actualización
     */
    public static final int CODIGO_ACTUALIZACION = 101;

    /**
     * Puerto que utilizas para la conexión. 
     * Dejalo en blanco si no has configurado esta carácteristica.
     */
    private static final String PUERTO_HOST = "63343";

    /**
     * Dirección IP de genymotion o AVD
     */
    private static final String IP = "http://10.0.3.2:";
    /**
     * URLs del Web Service
     */
    public static final String GET = IP + PUERTO_HOST + "/I%20Wish/obtener_metas.php";
    public static final String GET_BY_ID = IP + PUERTO_HOST + "/I%20Wish/obtener_meta_por_id.php";
    public static final String UPDATE = IP + PUERTO_HOST + "/I%20Wish/actualizar_meta.php";
    public static final String DELETE = IP + PUERTO_HOST + "/I%20Wish/borrar_meta.php";
    public static final String INSERT = IP + PUERTO_HOST + "/I%20Wish/insertar_meta.php";

    /**
     * Clave para el valor extra que representa al identificador de una meta
     */
    public static final String EXTRA_ID = "IDEXTRA";

}

Como ves, yo uso para el localhost la dirección 10.0.3.2 debido a que Genymotion (emulador alternativo) estableció este valor. Si vas a usar el emulador de android usa la dirección 10.0.2.2. Aquí el sitio oficial te habla un poco más sobre estas convenciones de direcciones para operaciones en la web.

Paso #2: Crear fuente de datos para las metas

Nuestro adaptador necesita alimentarse de una lista de elementos que le proporcionen la información necesaria para proyectar el layout. Es por eso que tienes que crear una clase que represente la existencia de una meta en la aplicación Android.

Crea una nueva clase en Android Studio y llámala Meta. Pon todos aquellos atributos puestos en la base de datos:

/** 
 * Reflejo de la tabla 'meta' en la base de datos
 */
public class Meta {

    /*
    Atributos
     */
    private String idMeta;
    private String titulo;
    private String descripcion;
    private String prioridad;
    private String fechaLim;
    private String categoria;

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

    public String getIdMeta() {
        return idMeta;
    }

    public String getTitulo() {
        return titulo;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public String getPrioridad() {
        return prioridad;
    }

    public String getFechaLim() {
        return fechaLim;
    }

    public String getCategoria() {
        return categoria;
    }

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

Si te fijas, tenemos un método para comparar una meta con otra para determinar si son iguales o no. Este método será de gran ayuda al momento de validar si hay cambios en los datos de los formularios cuando el usuario interactúa con ellos. Lo que permitirá determinar si hay que lanzar diálogos de confirmación antes de aplicar acciones.

Paso #3: Crear adaptador personalizado para el Recycler View

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

No olvides usar el patrón ViewHolder para reducir la cantidad de llamadas del método findViewById().

Además de ello tenemos que implementar sobre cada view holder una escucha OnClickListener para recibir los eventos del usuario en la lista. Para ello se creará una interfaz intermediaria entre el ViewHolder y el adaptador, de tal forma que cuando se active el evento onClick() este inicie la actividad de detalle.

import android.app.Activity;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.modelo.Meta;
import com.herprogramacion.iwish.ui.actividades.DetailActivity;

import java.util.List;

/**
 * Adaptador del recycler view
 */
public class MetaAdapter extends RecyclerView.Adapter<MetaAdapter.MetaViewHolder>
        implements ItemClickListener {

    /**
     * Lista de objetos {@link Meta} que representan la fuente de datos
     * de inflado
     */
    private List<Meta> items;

    /*
    Contexto donde actua el recycler view
     */
    private Context context;


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

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

    @Override
    public MetaViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_list, viewGroup, false);
        return new MetaViewHolder(v, this);
    }

    @Override
    public void onBindViewHolder(MetaViewHolder viewHolder, int i) {
        viewHolder.titulo.setText(items.get(i).getTitulo());
        viewHolder.prioridad.setText(items.get(i).getPrioridad());
        viewHolder.fechaLim.setText(items.get(i).getFechaLim());
        viewHolder.categoria.setText(items.get(i).getCategoria());
    }

    /**
     * Sobrescritura del método de la interfaz {@link ItemClickListener}
     *
     * @param view     item actual
     * @param position posición del item actual
     */
    @Override
    public void onItemClick(View view, int position) {
        DetailActivity.launch(
                (Activity) context, items.get(position).getIdMeta());
    }


    public static class MetaViewHolder extends RecyclerView.ViewHolder
            implements View.OnClickListener {
        // Campos respectivos de un item
        public TextView titulo;
        public TextView prioridad;
        public TextView fechaLim;
        public TextView categoria;
        public ItemClickListener listener;

        public MetaViewHolder(View v, ItemClickListener listener) {
            super(v);
            titulo = (TextView) v.findViewById(R.id.titulo);
            prioridad = (TextView) v.findViewById(R.id.prioridad);
            fechaLim = (TextView) v.findViewById(R.id.fecha);
            categoria = (TextView) v.findViewById(R.id.categoria);
            this.listener = listener;
            v.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            listener.onItemClick(v, getAdapterPosition());
        }
    }
}


interface ItemClickListener {
    void onItemClick(View view, int position);
}

ItemClickListener es la interfaz de comunicación que nos ayudará a relacionar lo posición del view con el evento onClick() . Como ves se implementa en la clase MetaAdapter para iniciar la actividad detalle a través de su método de fabricación launch().

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

Esto significa que se debe realizar otra petición para obtener los datos de la meta seleccionada. Lo que podría evitarse a través de caching con SQLite o enviando todos los datos de la meta. Sin embargo el fin de este tutorial es el uso al máximo de nuestro Web Service para que puedas interiorizar el conocimiento y practicar esta metodología. Por ahora no te preocupes en la arquitectura u optimizaciones.

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

Ya has construido un Web Service en Php con todas las características necesarias y has desarrollado los componentes de software para que la aplicación Android comience a funcionar.

El fragmento de la lista lo iniciaremos dinámicamente a través del método onCreate() de MainActivity:

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

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

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

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

        // Creación del fragmento principal
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new MainFragment(),"MainFragment")
                    .commit();
        }
    }
}

La comunicación inicial con el servidor es la lectura de todas las metas que se han guardado hasta el momento. Con ellas poblaremos la lista a penas inicie la aplicación. Por lo que debemos dirigirnos al fragmento principal y generar una petición GET hacia el servidor en onCreateView().

Veamos:

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.google.gson.Gson;
import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.modelo.Meta;
import com.herprogramacion.iwish.tools.Constantes;
import com.herprogramacion.iwish.ui.MetaAdapter;
import com.herprogramacion.iwish.ui.actividades.InsertActivity;
import com.herprogramacion.iwish.web.VolleySingleton;

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

import java.util.Arrays;


/**
 * Fragmento principal que contiene la lista de las metas
 */
public class MainFragment extends Fragment {

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

    /*
    Adaptador del recycler view
     */
    private MetaAdapter adapter;

    /*
    Instancia global del recycler view
     */
    private RecyclerView lista;

    /*
    instancia global del administrador
     */
    private RecyclerView.LayoutManager lManager;

    /*
    Instancia global del FAB
     */
    com.melnykov.fab.FloatingActionButton fab;
    private Gson gson = new Gson();

    public MainFragment() {
    }

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

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

        lista = (RecyclerView) v.findViewById(R.id.reciclador);
        lista.setHasFixedSize(true);

        // Usar un administrador para LinearLayout
        lManager = new LinearLayoutManager(getActivity());
        lista.setLayoutManager(lManager);

        // Cargar datos en el adaptador
        cargarAdaptador();

        // Obtener instancia del FAB
        fab = (com.melnykov.fab.FloatingActionButton) v.findViewById(R.id.fab);

        // Asignar escucha al FAB
        fab.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // Iniciar actividad de inserción
                        getActivity().startActivityForResult(
                                new Intent(getActivity(), InsertActivity.class), 3);
                    }
                }
        );

        return v;
    }

    /**
     * Carga el adaptador con las metas obtenidas
     * en la respuesta
     */
    public void cargarAdaptador() {
        // Petición GET
        VolleySingleton.
                getInstance(getActivity()).
                addToRequestQueue(
                        new JsonObjectRequest(
                                Request.Method.GET,
                                Constantes.GET,
                                null,
                                new Response.Listener<JSONObject>() {

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

                        )
                );
    }

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

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

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

    }


}

El código anterior muestra el uso de una constante llamada Constantes.GET, la cual contiene la dirección del servicio de obtención obtener_metas.php.

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, he creado el método procesarRespuesta(), el cual recibe un objeto JSONObject en bruto para comenzar el parsing. Donde he divido los caminos a través 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 través de la librería 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 descripción  en DetailActivity.java con un fragmento alojado.

Para ello inicia el fragmento dinámicamente en onCreate():

DetailActivity.java

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;

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

/**
 * Esta actividad contiene un fragmento que muestra el detalle
 * de las metas.
 */
public class DetailActivity extends AppCompatActivity {
    /**
     * Instancia global de la meta a detallar
     */
    private String idMeta;

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

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

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

        if (getSupportActionBar() != null) {
            // Dehabilitar titulo de la actividad
            getSupportActionBar().setDisplayShowTitleEnabled(false);
            // Setear ícono "X" como Up button
            getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close);
        }

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


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

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

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

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

        return super.onOptionsItemSelected(item);
    }
}

Este código tiene varias cosas interesantes. En primera instancia el uso de un método estático llamado launch(), el cual construye una instancia de la actividad de detalle y la inicia a través 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 dinámicamente 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 pregúntate que debe hacer el fragmento de detalle…

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

  • Cerrar el detalle con el Up Button o Back Button
  • Editar la meta a través del Floating Action Button.

La primera interacción 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 edición es necesario consultar la base de datos para setear los datos en los views. Además de ello asignar una escucha al FAB para que inicie la actividad de actualización.

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.

Recuerda que la convención 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 petición GET hacia el Web Service:

DetailFragment.java

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.google.gson.Gson;
import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.modelo.Meta;
import com.herprogramacion.iwish.tools.Constantes;
import com.herprogramacion.iwish.ui.actividades.UpdateActivity;
import com.herprogramacion.iwish.web.VolleySingleton;

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

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

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

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

    public DetailFragment() {
    }

    public static DetailFragment createInstance(String idMeta) {
        DetailFragment detailFragment = new DetailFragment();
        Bundle bundle = new Bundle();
        bundle.putString(EXTRA_ID, idMeta);
        detailFragment.setArguments(bundle);
        return detailFragment;
    }

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

        // Obtención de views
        cabecera = (ImageView) v.findViewById(R.id.cabecera);
        titulo = (TextView) v.findViewById(R.id.titulo);
        descripcion = (TextView) v.findViewById(R.id.descripcion);
        prioridad = (TextView) v.findViewById(R.id.prioridad);
        fechaLim = (TextView) v.findViewById(R.id.fecha);
        categoria = (TextView) v.findViewById(R.id.categoria);
        editButton = (ImageButton) v.findViewById(R.id.fab);

        // Setear escucha para el fab
        editButton.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // Iniciar actividad de actualización
                        Intent i = new Intent(getActivity(), UpdateActivity.class);
                        i.putExtra(EXTRA_ID, extra);
                        getActivity().startActivityForResult(i, Constantes.CODIGO_ACTUALIZACION);
                    }
                }
        );

        // Obtener extra del intent de envío
        extra = getArguments().getString(EXTRA_ID);

        // Cargar datos desde el web service
        cargarDatos();

        return v;
    }

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

        // Añadir parámetro a la URL del web service
        String newURL = Constantes.GET_BY_ID + "?idMeta=" + extra;

        // Realizar petición GET_BY_ID
        VolleySingleton.getInstance(getActivity()).addToRequestQueue(
                new JsonObjectRequest(
                        Request.Method.GET,
                        newURL,
                        null,
                        new Response.Listener<JSONObject>() {

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

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

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

            switch (mensaje) {
                case "1":
                    // Obtener objeto "meta"
                    JSONObject object = response.getJSONObject("meta");

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

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

                    // Seteando valores en los views
                    titulo.setText(meta.getTitulo());
                    descripcion.setText(meta.getDescripcion());
                    prioridad.setText(meta.getPrioridad());
                    fechaLim.setText(meta.getFechaLim());
                    categoria.setText(meta.getCategoria());

                    break;

                case "2":
                    String mensaje2 = response.getString("mensaje");
                    Toast.makeText(
                            getActivity(),
                            mensaje2,
                            Toast.LENGTH_LONG).show();
                    break;

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


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

    }
}

Para la inclusión de parámetros  en la petición GET, adjunta a la URL el valor de idMeta con la convención de formularios '?clave=valor'.

Al igual que en MainFragment, se creó un método para procesar la respuesta dependiendo del estado que se obtuvo. Si hubo éxito, entonces seteamos los valores correspondientes de cada view.

Paso #10: Realizar petición POST para editar detalles de la meta

Para la edición hemos creado la actividad UpdateActivity, la cual mostrará el formulario de recolección con los datos de la una meta.

La lógica 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 confirmación. Si no está de acuerdo o se arrepiente de ello, entonces puede descartar los cambios con el segundo action button. Incluso puedes incluir la eliminación entre los action buttos.

Así que lo primero es crear un archivo de menú para poblar la action bar:

menu_form.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.herprogramacion.iwish.ui.actividades.UpdateActivity">

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

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

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

Teniendo en cuenta esa apreciación las tareas que tienes por implementar son:

  • Cargar los datos de la meta en los componentes del formulario.
  • Habilitar la contribución a la action bar.
  • Manejar los eventos en cada action button.
  • Implementar la inserción, eliminación y borrado de las metas.
  • Lanzar diálogos para confirmar la eliminación y el descarte de cambios.
  • Usar Toasts para afianzar la confirmación de las operaciones.

Veamos la solución:

UpdateFragment.java

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.google.gson.Gson;
import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.modelo.Meta;
import com.herprogramacion.iwish.tools.Constantes;
import com.herprogramacion.iwish.web.VolleySingleton;

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

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


/**
 * Fragmento con formulario para actualizar la meta
 */
public class UpdateFragment extends Fragment {
    /*
    Etiqueta de depuración
     */
    private static final String TAG = UpdateFragment.class.getSimpleName();

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

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

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

    /**
     * Es la meta obtenida como respuesta de la petición HTTP
     */
    private Meta metaOriginal;

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


    public UpdateFragment() {
    }

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

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

        // Inflando layout del fragmento
        View v = inflater.inflate(R.layout.fragment_form, container, false);

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

                    }
                }
        );

        // Obtener valor extra
        idMeta = getArguments().getString(EXTRA_ID);

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

        return v;
    }

    /**
     * Obtiene los datos desde el servidor
     */
    private void cargarDatos() {
        // Añadiendo idMeta como parámetro a la URL
        String newURL = Constantes.GET_BY_ID + "?idMeta=" + idMeta;

        // Consultar el detalle de la meta
        VolleySingleton.getInstance(getActivity()).addToRequestQueue(
                new JsonObjectRequest(
                        Request.Method.GET,
                        newURL,
                        null,
                        new Response.Listener<JSONObject>() {

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

    /**
     * Procesa la respuesta de obtención obtenida desde el sevidor     *
     */
    private void procesarRespuestaGet(JSONObject response) {

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

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

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

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


        // Obteniendo acceso a los array de strings para categorias y prioridades
        String[] categorias = getResources().getStringArray(R.array.entradas_categoria);
        String[] prioridades = getResources().getStringArray(R.array.entradas_prioridad);

        // Obteniendo la posición del spinner categorias
        int posicion_categoria = 0;
        for (int i = 0; i < categorias.length; i++) {
            if (categorias[i].compareTo(meta.getCategoria()) == 0) {
                posicion_categoria = i;
                break;
            }
        }

        // Setear selección del Spinner de categorías
        categoria_spinner.setSelection(posicion_categoria);

        // Obteniendo la posición del spinner de prioridades
        int posicion_prioridad = 0;
        for (int i = 0; i < prioridades.length; i++) {
            Log.d(TAG, "posición:" + i);
            if (prioridades[i].compareTo(meta.getPrioridad()) == 0) {
                posicion_prioridad = i;

                break;
            }
        }

        // Setear selección del Spinner de prioridades
        prioridad_spinner.setSelection(posicion_prioridad);
    }

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

    /**
     * Retorna en una nueva meta creada a partir
     * de los datos del formulario actual
     *
     * @return Instancia {@link Meta}
     */
    private Meta obtenederDatos() {

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

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

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

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

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

            case R.id.action_delete:// ELIMINAR
                mostrarDialogo(R.string.dialog_delete_msg);
                break;

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

        }
        ;

        return super.onOptionsItemSelected(item);
    }

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

        // Obtener valores actuales de los controles
        final String titulo = titulo_input.getText().toString();
        final String descripcion = descripcion_input.getText().toString();
        final String fecha = fecha_text.getText().toString();
        final String categoria = categoria_spinner.getSelectedItem().toString();
        final String prioridad = prioridad_spinner.getSelectedItem().toString();

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

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

        // Crear nuevo objeto Json basado en el mapa
        JSONObject jobject = new JSONObject(map);

        // Depurando objeto Json...
        Log.d(TAG, jobject.toString());

        // Actualizar datos en el servidor
        VolleySingleton.getInstance(getActivity()).addToRequestQueue(
                new JsonObjectRequest(
                        Request.Method.POST,
                        Constantes.UPDATE,
                        jobject,
                        new Response.Listener<JSONObject>() {
                            @Override
                            public void onResponse(JSONObject response) {
                                procesarRespuestaActualizar(response);
                            }
                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.d(TAG, "Error Volley: " + error.getMessage());
                            }
                        }

                ) {
                    @Override
                    public Map<String, String> getHeaders() {
                        Map<String, String> headers = new HashMap<String, String>();
                        headers.put("Content-Type", "application/json; charset=utf-8");
                        headers.put("Accept", "application/json");
                        return headers;
                    }

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

    }

    /**
     * Procesa todos las tareas para eliminar
     * una meta en la aplicación. Este método solo se usa
     * en la edición
     */
    public void eliminarMeta() {

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

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

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

        // Eliminar datos en el servidor
        VolleySingleton.getInstance(getActivity()).addToRequestQueue(
                new JsonObjectRequest(
                        Request.Method.POST,
                        Constantes.DELETE,
                        jobject,
                        new Response.Listener<JSONObject>() {
                            @Override
                            public void onResponse(JSONObject response) {
                                // Procesar la respuesta
                                procesarRespuestaEliminar(response);

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

                ) {
                    @Override
                    public Map<String, String> getHeaders() {
                        Map<String, String> headers = new HashMap<String, String>();
                        headers.put("Content-Type", "application/json; charset=utf-8");
                        headers.put("Accept", "application/json");
                        return headers;
                    }

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

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

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

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

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

    }

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

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

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

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

    }

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

    /**
     * Muestra un diálogo de confirmación, cuyo mensaje esta
     * basado en el parámetro identificador de Strings
     *
     * @param id Parámetro
     */
    private void mostrarDialogo(int id) {
        DialogFragment dialogo = ConfirmDialogFragment.
                createInstance(
                        getResources().
                                getString(id));
        dialogo.show(getFragmentManager(), "ConfirmDialog");
    }

}

Este código es un poco largo debido a que tenemos la implementación de diálogos y comunicaciones de datos. Por lo que a continuación te explico la esencia de las peticiones de información.

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.  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().

Inmediatamente los datos conseguidos en la petición, los seteamos en cada view del formulario.

Manejar los eventos en cada action button: Para lograr esta tarea se implementó el método onOptionsItemSelected(), donde se creó una estructura switch que permitiera la ejecución del método correspondiente a la acción. Recuerda usar onHasOptionMenu() en onCreate() para que el fragmento pueda escuchar los eventos de la action bar.

Implementar la inserción, eliminación y borrado de las metas: Cada operación en la base de datos tiene un método asignado para su realización. Estos son: guardarMeta() y borrarMeta(). El primer método realiza una petición POST con la respectiva URL del servicio de actualización, usando los valores actuales del formulario.

Similarmente borrarMeta() envía el id de la meta que se desea eliminar hacia la dirección correspondiente.

En cuanto a los diálogos, simplemente usamos el formato clásico de ACEPTAR|CANCELAR para permitir o no el efecto de los métodos en la base de datos. Puedes encontrar la implementación completa descargando el código en la parte superior del artículo.

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. Haremos exactamente lo mismo que hemos venido haciendo.

Iniciaremos el fragmento y estaremos a la espera de que el usuario guarde los datos o los descarte. Veamos:

InsertActivity.java

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

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

public class InsertActivity extends AppCompatActivity
        implements DatePickerFragment.OnDateSelectedListener,
        ConfirmDialogFragment.ConfirmDialogListener {


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

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

        // Creación del fragmento de inserción
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new InsertFragment(), "InsertFragment")
                    .commit();
        }
    }

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

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

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

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

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

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

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

Ahora el fragmento de inserción tiene las siguientes características:

InsertFragment.java

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.herprogramacion.iwish.R;
import com.herprogramacion.iwish.tools.Constantes;
import com.herprogramacion.iwish.web.VolleySingleton;

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

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


/**
 * Fragmento que permite al usuario insertar un nueva meta
 */
public class InsertFragment extends Fragment {
    /**
     * Etiqueta para 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);
        map.put("categoria", categoria);
        map.put("prioridad", prioridad);

        // Crear nuevo objeto Json basado en el mapa
        JSONObject jobject = new JSONObject(map);

        // Depurando objeto Json...
        Log.d(TAG, jobject.toString());

        // Actualizar datos en el servidor
        VolleySingleton.getInstance(getActivity()).addToRequestQueue(
                new JsonObjectRequest(
                        Request.Method.POST,
                        Constantes.INSERT,
                        jobject,
                        new Response.Listener<JSONObject>() {
                            @Override
                            public void onResponse(JSONObject response) {
                                // Procesar la respuesta del servidor
                                procesarRespuesta(response);
                            }
                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.d(TAG, "Error Volley: " + error.getMessage());
                            }
                        }

                ) {
                    @Override
                    public Map<String, String> getHeaders() {
                        Map<String, String> headers = new HashMap<String, String>();
                        headers.put("Content-Type", "application/json; charset=utf-8");
                        headers.put("Accept", "application/json");
                        return headers;
                    }

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

    }

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

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

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

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

    }

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

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

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

    /**
     * Muestra un diálogo de confirmación
     */
    public void mostrarDialogo() {
        DialogFragment dialogo = ConfirmDialogFragment.
                createInstance(
                        getResources().
                                getString(R.string.dialog_discard_msg));
        dialogo.show(getFragmentManager(), "ConfirmDialog");
    }

}

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. 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 validación de los campos del formulario que requieren texto escrito por parte del usuario. Para ello se creó el método 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 diálogo si ya ha escrito algún dato.

Ejecutar Proyecto Completo En Android Studio

Recuerda que puedes descargar el proyecto completo con el botón que tienes en el inicio del artículo. Al final, luego de haber seguido todos los pasos la aplicación se verá así:

Aplicación Para Guardar Metas Creada Con Material Design

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 comunicación elegante como REST es un excelente complemento para estructurar una buena API.

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

Añade caching de información a través de SQLite para evitar realizar gran cantidad de operaciones de red.

Usa la clase ContentProvider para completar tu patrón MVC de Red y añadir restricciones RESTful en tu aplicación. Esto te permitirá independizar tus clases y evitar problemas en tu hilo principal de forma sencilla.

¿Necesitas Otro Ejemplo De Servicio Web?

Hace unos días lancé un tutorial detallado para crear un servicio web REST para productos, clientes y pedidos. Donde consumo sus recursos desde una aplicación llamada App Productos. Échale un vistazo a todo lo que incluye (tutorial PDF, código completo en Android Studio, código completo PHP, script MySQL con 100 productos de ejemplo).

Comprar app productos

  • Pingback: Conectar Aplicación Android Con Php, Mysql y Json | Maestruli()

  • Marco Antonio Morales Hernande

    en esta linea de codigo, en tu proyecto usas I%20Wish…. a que se refire, a lo que le entiendo creo que es el nombre de la crpeta que contiene los archivos php…. o es una intruccion de android con java…. yo lo use asi, suponiendo que la instrucion es el nombre de la carpeta
    public static final String GET = IP + PUERTO_HOST + “/appRemota/obtener_metas.php”;

  • Marco Antonio Morales Hernande

    en esta linea de codigo, en tu proyecto usas I%20Wish…. a que se refire, a lo que le entiendo creo que es el nombre de la crpeta que contiene los archivos php…. o es una intruccion de android con java…. yo lo use asi, suponiendo que la instrucion es el nombre de la carpeta
    public static final String GET = IP + PUERTO_HOST + “/appRemota/obtener_metas.php”;

  • Jhose Hernandez

    Hola James, gracias por compartir el código, pero presento un problema, al importar el proyecto a Android Studio, me marca este error. “Can’t start Git: git.exe

    Probably the path to Git executable is not valid. Fix it. ” . ¿Cómo lo soluciono?. GRACIAS

  • Jhose Hernandez

    Hola James, gracias por compartir el código, pero presento un problema, al importar el proyecto a Android Studio, me marca este error. “Can’t start Git: git.exe

    Probably the path to Git executable is not valid. Fix it. ” . ¿Cómo lo soluciono?. GRACIAS

  • Jose

    Primeramente te felicito por tus excelentes tutoriales, estoy siguiente este tutorial descargue tu proyecto pero me sale varios errores soy nuevo en desarrollo android, la api mas baja que tengo es la 16 pero utilizo la 18 con genymotion dejo unas capturas de antemano muchas gracias.

  • Jose

    Primeramente te felicito por tus excelentes tutoriales, estoy siguiente este tutorial descargue tu proyecto pero me sale varios errores soy nuevo en desarrollo android, la api mas baja que tengo es la 16 pero utilizo la 18 con genymotion dejo unas capturas de antemano muchas gracias.

  • Mosco Mx

    Al fin pude hacerla andar localmente. Me marcaba error por la ip, no se conectaba. Lo que hice fue corregir la configuracion del apache en “Listen localhost:80” en vez del localhost puse la ip de mi maquina y en el puerto el que en mi caso es el 8080. Estos datos deben configurarlos en Constantes.java. Espero les sirva mi experiencia.

  • Mosco Mx

    Al fin pude hacerla andar localmente. Me marcaba error por la ip, no se conectaba. Lo que hice fue corregir la configuracion del apache en “Listen localhost:80” en vez del localhost puse la ip de mi maquina y en el puerto el que en mi caso es el 8080. Estos datos deben configurarlos en Constantes.java. Espero les sirva mi experiencia.

  • Ivonne

    Me urge. Soy novata y necesito conectar la app a una base de datos que tengo en un hosting, ¿Qué debo modificar?

  • Ivonne

    Me urge. Soy novata y necesito conectar la app a una base de datos que tengo en un hosting, ¿Qué debo modificar?

  • jc Cruz

    Buen post james, me funciono de maravilla y aprendí muchisimo sobre el tema, me gustaría aprender como recuperar imágenes desde el servidor con tipo de datos blob tendrás algún post sobre el tema ?

  • Wandy Hernandez

    Primero muchisimas gracias para @JamesRevelo:disqus por su excelente Post, realmente aprecio un mundo tu generosidad de compartir tus conocimientos.
    Necesito ayuda para conectarme a la base de datos, estoy usando XAMPP tal y como lo muestra la explicacion del tutorial.
    Para correr la App utilizo mi celular, toda la App corre bien pero el problema esta al cargar las metas a mi aplicacion me manda el siguiente error en la clase Volley.
    com.android.volley.NoConnectionError: java.net.UnknownHostException: host == null

    He intentado con varios host y nada, no se realmente como resolver el problema.
    Gracias de antemano por la ayuda.! :)

  • Hernandez Estrambotico

    problema con
    Error Volley: com.android.volley.NoConnectionError: java.net.ConnectException: failed to connect to /127.0.0.1 (port 80) after 2500ms: isConnected failed: ECONNREFUSED (Connection refused)

    No me conecta a mi localhost, ya cambie puerto pero sigue sin poder conectar a localhost.

    y gracias por la atencion, excelente pagina

  • Guillermo Vargas

    Hola James oye como le haces para convertir la fecha de string a date?

  • Guillermo Vargas

    Tengo una duda, he notado que no cierras la etiqueta < ? php en: actualizar_meta.php, borrar_meta.php, insertar_meta.php, obtener_meta_por_id.php, obtener_metas.php hay algun motivo o simplemente se te olvido cerrarla?

  • Pingback: PHP Creación de Web Services RESTful | E-Force Blog()

  • jc Cruz

    buen aporte james, saludos me sale este error en todos los get, me podrian ayudar

  • Roger Aguilar

    Hola, muchas gracias por el tutorial, está genial.
    Tengo una pregunta, ¿que software usaste para los mockups del app?

  • Jefferson Almache Montoya

    Da gusto encontrarse un post tan bien hecho y tan bien explicado(de los que casi no hay). Un saludo y gracias por compartir

    • Excelente que te esté sirviendo Jefferson!, saludos y gracias por tu apreciación

  • juan

    Buenos días.
    Alguien está tenendo el siguiente error al realizar en insert? “jsonexception.. end of input at character… ” hace una semana que intento solucionarlo pero nada, aparentemente es porque el response esta vacío o es null. pero debería ser TRUE ya que estoy verificando que las inserciones se hacen correctamente en la base de datos, no entinedo donde esta el error. Gracias, y muchas gracias por el post, es un salvavidas ..

  • Daniel Arteaga Iriarte

    Buenas. Disculpen, tengo un problema. Realice mi servicio Web en ASP, pues mi base de datos se encontraba en SQL Server. Cambie todo lo necesario (creo), y cuando lo hago correr me da este error:

    BasicNetwork.performRequest: Unexpected response code 400 for http://10.0.2.2:59629/api/Clientes

    Queria saber si es un problema del cliente o del servidor? Pues en el localhost me corre normalmente. Y queria saber que otros pasos deberia cambiar ya que yo lo estoy realizando en ASP. Muchas gracias

  • JJesetBC

    Hola chicos que tal, @JamesRevelo:disqus como puedo guardar la hora además de la fecha en la base de datos, y si quiero guardar datos mediante un ckeckpoint en android qué debería hacer?

  • JLopez Mostro

    Hola como estas? excelentes tus tutoriales, son geniales, voy a aprender mucho de android nativo…
    Una consulta, yo tengo un hosting y tengo mi pagina con wordpress… podria usar el mysql y hacer una app php para usar como servicio rest y conectar mi app android a ese webservice?

  • Jesus Adiel

    Hola espero que me puedan aclar este error que me arroja Error Volley: org.json.JSONException: End of input at character 0 of—- No se a que se deba, el registro se guarda en el servidor pero no me lo notifica y al regreesar a la pantalla anterior no me refresca el ListView

  • Alberto Montero

    Buenas estoy intentando crear un registro a partir de este tutorial, sin embargo me surge el siguiente error:

    04-11 16:56:44.513 3843-3843/? D/InsertFragment: {“usuario”:”sdf”,”contrasena”:”sdf”,”correo”:”sdf”,”nombre”:”sdf”,”apellido1″:”sdf”,”apellido2″:”sdf”,”sexo”:”f”,”telefono”:”45″,”edad”:”4″,”imagen”:”sdf”,”direccion”:”sdf”,”localidad”:”sdf”,”provincia”:”sdf”,”pais”:”sdf”,”descripcion”:”sdf”,”rating”:”4″}

    04-11 16:56:52.014 3843-3843/? D/InsertFragment: Error Volley: null

    04-11 16:56:52.014 3843-3843/? D/Volley: [1] Request.finish: 7502 ms: [ ] http://192.168.56.1:8008/phphanidcapp/insertar_usuario.php 0x676b18e3 NORMAL 2

    Porfavor espero una respuesta, les puedo facilitar todo el codigo si es necesario.

    • Plana17

      Hola Alberto, como has solucionado este error al final?

  • Gon Her

    James, necesito un favor. Necesito seleccionar una meta segun el time y no segun el idMeta. Es decir que necesito seleccionar lo ultimo insertado en la tabla. Segun este identificador, si ponga el valor 1, me va a seleccionar el primer row por decirlo de alguna manera. Yo necesito seleccionar el ultimo siempre. Se me ocurrio un CURRENT_TIMESTAMP pero no se como aplicarlo. Me ayudas?

  • Pingback: Crear Un Web Service Para Android Con Mysql, Ph...()

  • Cristian

    Hola james muy buen tutorial. muchas gracias. pero me presenta este error:

    04-04 23:35:03.560 2116-2116/? E/RecyclerView: No adapter attached; skipping layout
    04-04 23:35:03.572 2116-2116/? E/RecyclerView: No adapter attached; skipping layout

    Lei todos los comentarios pero no encontré respuesta clara. alguna idea.. sld

    • Cristian

      img

  • julian

    hola buenos dias, me funciona todo perfecto, pero quisiera que al agregar una nueva meta quede al inicio de la lista y no al final… Gracias

  • Llulian

    Saludos.
    ¿El archivo Database.php debo guardarlo en la ruta C:xamppphpMyAdminsetupframes? Esa es la que me da por defecto Sublime Text 2 cuando voy a guardarlo. Muy bueno el tutorial. Paso a paso y bien explicado.

    • Hernandez Estrambotico

      Guardalo en htdocs.

  • Jonny

    Hola, quisiera preguntarte cómo realizar el web service si tu base de datos está instalada en un servidor remoto contratado (tienes la contraseña y password pero no es localhost)?

    Gracias

    • Hola Jonny, solo cambia la dirección ip por el dominio o url donde hayas determinado el servicio web.

      Creería que eso lo soluciona, pero en si, cuál es tu situación actual?

  • Mario German

    Hola A todos: En la Base de datos que llamas mysql_login.php tienes argumentos:

    define(“HOSTNAME”, “localhost”);

    define(“USERNAME”, “root”);

    define(“PASSWORD”, “”);

    define(“DATABASE”, “metas”);

    Pero en mi caso estoy manejando:

    $host = “localhost”;

    $username = “juanmanueal”;

    $password = “xxxxx”;

    $dbname = “life”;

    cual dato debería colocar en el Database en la función:

    public function getDb()

    {

    if (self::$pdo == null) {

    self::$pdo = new PDO(

    ‘mysql:dbname=’ . DATABASE . //

    ‘;host=’ . HOSTNAME . //

    ‘;port:63343;’, // Eliminar este elemento si se usa una instalación por defecto

    USERNAME,

    PASSWORD,

    array(PDO::MYSQL_ATTR_INIT_COMMAND => “SET NAMES utf8”)

    );

    Agradezco la colaboración de todos ustedes.

    Quedo atento.

  • User X

    Con que herramienta hiciste el diseño del flujo de activities?

  • Oscar Perdomo

    Buenas noches.

    Excelente aporte, despejé muchisimas dudas que tenia. SIn embargo, tengo un problema muy grande con los datos SQL: no me cargan automaticamente al insertarlos, modificarlos o eliminarlos. Para esto, tengo que forzar la detención de la app y volver a ingresar para ver los resultados. ¿Qué puedo hacer para que carguen automaticamente?

    Les agradezco mucho.

    • ¿Para que quieres saber eso?, jaajaja saludos :v

      No mentiras. Oscar hasta donde me acuerdo dejé los datos recargándose cada vez que se realiza una operación, ya que se refrescan los elementos de la lista.

      Sin embargo la mejor forma de hacerlo es con Loaders y un Content Provider a través del método Cursor.setNotificationUri(). Puedes ver más aquí compañero:

      http://www.hermosaprogramacion.com/2015/12/consumir-un-servicio-web-rest-desde-android/

      Saludos y gracias por seguir el blog!

      • Oscar Perdomo

        Debo realizar un proyecto para la universidad y me esta costando mucho configurar la actualización automática (o tiempo real) . La verdad no se si sean configuraciones de mi xampp o el código android :(

  • Ferney Alejandro Gonzalez

    Hola James, muchas gracias por compartir tu conocimiento y felicitaciones por la labor que realizas, soy un nuevo programador en android, algo entusiasta, por lo que estoy usando este tuto para una app propia que estoy desarrollando, tengo un problema y despues de darle bastantes vueltas encuentro que no me permite hacerle el parsing en el objeto gson, es decir, recupera la información de la DB y la carga sin ningún problema hasta el Json array “mensaje”, pero en la linea: Cliente[] clientes = gson.fromJson(mensaje.toString(), Cliente[].class); me lleva todos los values = null por cada key y me muestra un error en el objeto Gson como se puede apreciar en la imagen adjunta , te anexo la imagen con los valores en cada variable, te agradezco en lo que me puedas colaborar, mil gracias.

    • Hola compañero. ¿Los campos que vienen en el Json tienen el mismo nombre que los atritos Java de la clase Cliente?

    • Hola compañero. ¿Los campos que vienen en el Json tienen el mismo nombre que los atritos Java de la clase Cliente?

      • Ferney Alejandro Gonzalez

        Muchas gracias James, en efecto tenia un error en un nombre de atributo.

  • Ferney Alejandro Gonzalez

    Hola James, muchas gracias por compartir tu conocimiento y felicitaciones por la labor que realizas, soy un nuevo programador en android, algo entusiasta, por lo que estoy usando este tuto para una app propia que estoy desarrollando, tengo un problema y despues de darle bastantes vueltas encuentro que no me permite hacerle el parsing en el objeto gson, es decir, recupera la información de la DB y la carga sin ningún problema hasta el Json array “mensaje”, pero en la linea: Cliente[] clientes = gson.fromJson(mensaje.toString(), Cliente[].class); me lleva todos los values = null por cada key y me muestra un error en el objeto Gson como se puede apreciar en la imagen adjunta , te anexo la imagen con los valores en cada variable, te agradezco en lo que me puedas colaborar.

  • Marcos Ramírez

    Hola buen dia, disculpe en donde usted pone la ip “http://10.0.3.2:” puedo poner la direccion web del hosting donde tengo alojados los script php ? Gracias

  • Julio Ruiz

    AMIGO… MUCHAS GRACIAS POR SU APORTE….. esta muy completo….. y me va a servir de mucho…. lo felicito por su blog y le deseo lo mejor en sus proyectos…… este es un material muy valioso y de gran ayuda para los que iniciamos en la programación para moviles.

  • Julio Ruiz

    AMIGO… MUCHAS GRACIAS POR SU APORTE….. esta muy completo….. y me va a servir de mucho…. lo felicito por su blog y le deseo lo mejor en sus proyectos…… este es un material muy valioso y de gran ayuda para los que iniciamos en la programación para moviles.

  • Johan Nz

    Hola James, se podria hacer que los datos lleguen desde sql server y no desde MySql, de igual manera usando PHP y no ASP? y se podria hacer un campo de texto para que busque por id?

    Saludos desde Cali :)

  • Ruben

    Hola James, primero felicitarte por este gran tutorial, es genial.

    Me gustaria preguntarte una duda/problema que me surge cuando actualizo o inserto un registro nuevo y es que no se me actualiza los datos en la lista de elementos (en la vista principal de la APP) y si en la base de datos… que puedo hacer?

    Gracias por tu colaboracion eres un fenomeno!

  • Marcos Ramírez

    Hola James Revelo Mi pregunta puedo modificar para agregar imagenes ?
    Tendras alguna referencia URL sobre como agregar imagenes que puedas comparti por favor.
    Gracias

  • andersson

    tengo una gran duda donde coloco los php ya que estoy utilizando wamp y la verdad no se donde colocarlos. ademas de donde saco mi ip para poder ingrasarla en la app desde android gracias por la respuesta.

  • Abraham

    Muy buena James! De primeras darte las gracias por compartir tu información. Tengo una duda, las metas de cada persona solo las visualiza la persona que las crea? O pueden verlas todo el mundo? Gracias de antemano.

  • Mario German Agudelo

    Hola James, estoy muy interesado en realizar una aplicación móvil la cual le brinde información a los usuarios, el usuario deberá loguearse en la app, posteriormente según el login registrado se le brindará la información relacionada al login, el usuario solo podrá recibir la información de las diferentes actividades pero no podrá modificarlas.

    Me podrían por favor ayudar con este tema.

    Les agradezco, Gracias.

  • Mario German Agudelo

    Hola, Soy nuevo en esto y quiero crear una aplicación móvil en la cual el usuario en una primera instancia se logue y con este login le muestre solo la información alusiva al login, es decir se logue e ingresa a otra venta y de ahí en adelante la información que trae de la base de datos solo sera la relacionada al login colocado.

    Agradezco de toda la colaboración que me puedan dar.

    Gracias.

    • Hola Mario. Puedes leer este artiuclo sobre crear un servicio REST: http://www.hermosaprogramacion.com/2015/10/servicio-web-restful-android-php-mysql-json/

      Allí te muestro de que forma interactuaría una tabla usuarios para el login.

      Sin embargo aún no tengo un artículo donde se cree el login. Espero sacarlo este mes por si deseas estar atento. Saludos compañero!

      • Mario German Agudelo

        Hola James: Estuve leyendo la información, pero no encontre como conectar un login, un registro con toda la aplicación.
        Así como poderme comunicar con mi base de datos desde mi aplicacion.
        Te agradezco si me puedes ayudar.
        Gracias.

  • Limbert Lopez

    xq me sale este error. ya googlee y no encontre la solucion… ya coloque compile ‘com.melnykov:floatingactionbutton:1.3.0’ en el build.gradle
    alguien me ayude.. xfa.

  • Óscar Daniel

    array(PDO::MYSQL_ATTR_INIT_COMMAND => “SET NAMES utf8”)

    esta linea que hace?

    • Hola Oscar, en el articulo puse esto: “Adicionalmente debes añadir al cuarto parámetro del constructor de PDO la indicación SET NAMES UTF-8
      para el servidor. Esto permite que los datos de la base de datos vengan
      codificados en este formato para evitar problemas de compatibilidad.”

      Es para decirle al servidor que la comunicación con el cliente será con el sistema UTF-8 para el texto.

  • Jhonny Ruiz

    hola disculpen soy nuevo en esto y lo que hice fue copiar los códigos para crear y llenar la base de datos y valió y en ese archivo de dropbox que es un rar copie los .php en my localhost uso xamp y me sale este error {“estado”:2,”mensaje”:”Ha ocurrido un error”} al ejecutar el obtener_metas.php no sé si deba cambiar algo de ante mano gracias por su ayuda

  • Jairo BEltran

    Disculpa James, es que estoy teniendo el error End of input at character 0,cuando realizo el Update, me podrias decir como lo soluciono

  • Nuria

    Buenos días, me encanta esta publicación ayuda mucho!

    Una pregunta, el boceto del comienzo de la publicación, del apartado Wireframing De La Aplicación

    , ¿con que herramienta lo has hecho?
    Espero la respuesta gracias puesto que estoy realizando mi proyecto de fin de carrera y queda bastante claro y explicado la funcionalidad de la aplicación

  • Edwin

    Hola alguien podria conseguirme el codigo para analizarlo y probralo ya que soy nuevo por fa se lo agraecria de antemano, en el boton de descarga al parecer se elimino el archivo… :(

  • Juann

    Holaa muy buen tutorial, pero tengo un problema en mi caso tengo que hacer la busca de GetById con el parametro LIKE =?” como puedo implementarlo ??, este serial el codigo que tengo que modificar :

    public static function getById($idMeta)
    {
    // Consulta de la meta
    $consulta = “SELECT idMeta,
    titulo,
    descripcion,
    prioridad,
    fechaLim,
    categoria
    FROM meta
    WHERE idMeta LIKE = ?”;

    try {
    // Preparar sentencia
    $comando = Database::getInstance()->getDb()->prepare($consulta);
    // Ejecutar sentencia preparada
    $comando->execute(array($idMeta));
    _____________________________________________________________________

    Lo que deberia conseguir es hacer un idMeta LIKE ‘MetaDeJu%’ y me arroje todas las metas que comienzen con MetaDeJu por ejemplo.

  • Gabriel Vela Ortiz

    buenas tardes compañero, bueno esta pregunta va enfocada mas al diseño previo que hiciste.. Quisiera saber en que herramienta realizaste los wireframes de tu app ya que algo así es lo que necesito para incluir en mi tesis. Gracias de antemano por la info y muy bueno tu post

    • Hola Gabriel. Usé ninjamock.com y las lineas las puedes realizar dentro de canva.com.

      • Gabriel Vela Ortiz

        gracias compañero muy buenas tus publicaciones

  • Brujita Linda

    AYUDA CON ESTE PROBLEMA POR FAVOR se los agradecería mucho

  • Brujita Linda

    ayuda con este problemos porfavor……!!!!!

  • Julian Velandia

    Hola, tengo una pequeña pregunta si deseo mostrar ademas de texto plano, una imagen como seria el procedimiento para parsear la ruta de dicha imagen ?

  • alarcon

    Hola, he seguido el manual, por cierto bastante bueno, y estoy teniendo problemas cuando hago el post para insertar los datos.

    el error lo he conseguido acotarlo y resultar ser que al servidor no le llegan los datos cuando

    $body = json_decode(file_get_contents(‘php://input’),true);

    es decir, $body no tiene nada.

    el objeto que envio desde android es –>
    jobject: {“ventosas”:”r”,”manometros”:”r”,”cubierta”:”r”,”puerta_acceso”:”r”,”ventilacion_superior”:”r”,”comentario”:””,”contadores”:””,”foto”:””,”distancia_reglamentaria_elementos”:”r”,”param_verticales_ext”:”r”,”param_verticales_int”:”r”,”perimetro_arqueta”:”r”,”acceso_ubicacion”:”r”,”pates_escalera”:”r”,”ventilacion_lateral”:”r”,”juntas_carretes”:”r”,”direccion_arqueta”:”r”,”fecha”:”r”}

    A priori todo esta bien porque no recojo los datos en el servidor???

  • Elberth Agreda

    Muchas gracias James Revelo, me sirvió de mucho esté tutorial, queria pedirte un favor e realizado paso a paso lo que elaboraste todo me funciona correctamente, el problema que tengo es que al insertar o editar lo hace en la base de datos pero tengo que cerrar y volver a abrir la aplicación para poder ver los cambios, que podria estar haciendo mal?

    • Ali Lopez Galaviz

      Tengo el mismo problema amigo

      • Elberth Agreda

        Lo lograste solucionar?

        • Ali Lopez Galaviz

          Parece que ya fue solucionado, me puse a leer entre los comentarios, y lo unico que hice fue revisar los archivos php y quizar acentos, actualmente estoy trabajando con mi maquina de servidor con Xampp, no se si tenga conflicto con caractes especiales, pero prueba ello de quitar acentos y me dices.

          Saludos

          • Elberth Agreda

            Aún tengo el mismo problema amigo, inserta, actualiza, elimina y carga todos los datos correctamente pero al momento de hacer una accion como insertar, para que me aparezcan los datos tengo que cerrar y volver a abrir la app

          • Elberth Agreda

            Listo ya lo solucione, gracias a los comentarios pasados. Excelente Post

  • ivanAndro117

    Hola muy buen día, disculpa tengo una duda con respecto al fetch que haces en el archivo metas.php pues hay un campo al que necesito aplicar base64_encode ya que no tengo mucho conocimiento sobre este tema no tengo idea de como cambiar el fetch para aplicar lo que necesito, ¿Me podrías ayudar?

  • Fernando

    Hola a todos.

    He programado un web service en php que comunica java con mysql. Todo funciona correcto desde el emulador pero cuando lo prueba en un dispositivo real no funciona. He cambiado la IP por la local (192.168.X.X) y desactivado el firewall de windows.

    ¿Alguien me puede ayudar?

    Muchas gracias de antemano.

    Un saludo.

    • Fernando

      Ya lo he solucionado, es un problema en el fichero de configuración de apache.

      Un saludo.

      • Marco Antonio Pérez Hernández

        y que cambiaste en el fichero de configuracion de apache?

  • Juan Carlos Diego Quispe

    Hola tengo un problema no puedo visualizar la informacion me sale el siguiente error

  • Stuart Cornelio Garcia

    Hola me esta arrojando esta excepcion controlada por la clase UpdateFragment : Error Volley: org.json.JSONException: End of input at character 0 of Espero me puedas ayudar pronto

  • Brujita Linda

    Alguna solución a este problema ?

    D/Insert Fragment: Error Voley: org.json.JSONException: End of input at character 0 of

    cuando Inserto un registro no me devuelve a la lista y ese error me sale

    • Parece que estás obteniendo una respuesta en blanco, te has fijado que la base de datos tenga registros?

    • Jairo

      Disculpa me podrias decir que hicistes para solucionar ese error
      ?

  • Hola James, estoy adaptando esto a una ListView xq tengo una app que necesita tener version desde la API 9 tengo éste Adapter, lo he corrido y me llena los listview pero vacios como si no hubiera texto me falta algo?

    package marcosdavalos.json.adapter;

    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.ArrayAdapter;
    import android.widget.TextView;
    import android.widget.Toast;

    import java.util.List;

    import marcosdavalos.json.R;
    import marcosdavalos.json.model.Noticias_obj;

    /**
    * Created by marcosdavalos on 09-10-15.
    */
    public class NoticiasAdapter extends ArrayAdapter{

    private List noticias;
    private Context context;
    public NoticiasAdapter(Context context, List items) {
    super(context, R.layout.item_noticias, items);
    this.noticias = items;
    }

    @Override
    public int getCount() {
    return noticias.size();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;

    if (v == null) {
    LayoutInflater li = LayoutInflater.from(getContext());
    v = li.inflate(R.layout.item_noticias, null);
    }
    Noticias_obj noticia = noticias.get(position);
    if (noticia != null) {
    TextView titulo = (TextView)v.findViewById(R.id.titulo);
    TextView contenido = (TextView)v.findViewById(R.id.contenido);

    titulo.setText(noticia.getTitulo());
    contenido.setText(noticia.getContenido());
    }

    return v;

    }

    }

    • Hola amigo. Has probado usar Log.d() para ver el contenido?, si sale en blanco es porque la info no existe aún y debes corregir ese proceso. Si sale contenido, entonces debe ser por el tipo de variable que es el resultado de noticia.getTitulo(). Incluso puede ser por los acentos que estas usando en el string. ¿hay tildes, simbolos de interrogación?

      • Jame, sí lo solucioné. El problema era tenia q ser el mismo nombre en la tabla del DB

  • yonatan arroyave

    hola, otra pregunta queria poner el web service en 000webhost pero me surgido la duda.
    en el archivo database.php en el puerto que deberia poner? yo lo estoy usando con el 3306 pues localmente. hay deberia poner?

    • No uses puerto compañero, dejalo en blanco

      • yonatan arroyave

        gracias, mi hermano otra consulta para este servidor, en constantes la ip que debo colocar seria la ip que me sale al hacer un ping o la ip que me el hostin (ver imagen), y que puerto también el 80?.

        muchas gracias por la ayuda, ecxelente aporte

  • Galuz Gonzalez

    a mi me sale este error :/ me hace las modificaciones en la base de datos, pero en la app no me actualiza nada

    • Ese error puedes solucionarlo al inicializar el adaptador en blanco antes de comenzar a buscar datos.

  • Galuz Gonzalez

    alguien ya soluciono el problema que generaba el metodo cargarAdaptador()?

  • Fernando

    Por favor me pueden ayudar con este error de Android Studio: Error:Connection timed out: connect. If you are behind an HTTP proxy, please configure the proxy settings either in IDE or Gradle.

  • Work Ok

    hola quería preguntar por q la opción o la barra de menú para eliminar no funciona

    • Hola amigo, te sale un error o simplemente no surge efecto en la base de datos?

  • Carlos Alexis

    Buenas tardes soy nuevo en programacion android estoy queriendo integrar servicios web en un mismo ejemplo que dejaste en uno de tus turiales anteriores pero primero quiero qiue me funcione este ejemplo pero al correrlo me marca este error estoy corriendolo en mi local host y uso mi celular para ejecutarlo te mando una imagen del error que es Error Volley: null. no e modificado nada del codigo tal cual lo descarge esta tan solo cambie la ip y puerto y quite el charset UTF-8 y puse windows-1252
    le agardeceria una pronta respuestas Gracias!!

    • Hola compañero, quitaste el puerto de la conexión a la bd del archivo php?, si no usas ningun puerto debes eliminar ese pedazo.

      • Carlos Alexis

        Esta es la manera correcta como quedaria al final mi archivo php

        public function getDb()
        {
        if (self::$pdo == null) {
        self::$pdo = new PDO(
        ‘mysql:dbname=’ . DATABASE .
        ‘;host=’ . HOSTNAME .
        USERNAME,
        PASSWORD,
        array(PDO::MYSQL_ATTR_INIT_COMMAND => “SET NAMES utf8”)
        );

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

        return self::$pdo;
        }

    • Galuz Gonzalez

      la ruta esta mal, checa eso

  • Cristopher

    Tengo un problema he realizado un web service en C# ( ASMX ) y estoy retornando un objecto (EnAlumno) pero tengo un problema, cuando realizo la consulta al web service, este se cae en la parte de:

    ransporte.call(Constante.SOAP_OBTENER_DATOS_PERSONALES, envelope);

    el catch indica: “java.lang.RuntimeException: Cannot serialize: [Ljava.lang.String;@41000bd28”

    Y no se ha que se deba, ya verifique este correctamente la URL, NAMESPACE y nada :(.

    Ya he probado el web service directamente y funciona normal

    Nota:

    Cuando consulto el web service, este devuelve la siguiente estructura

    CRISTOPHER JUAN

    GRETA QUISPE

    MASCULINO

    ruta_imagen_foto

    32522

    correo@dominio

    A

    OK

  • Juan

    Hola, estuve viendo el código y lo que necesito hacer es parecido, y mas simple, pero no se como adaptarlo y si se puede reutilizar parte de este código, mi aplicación debería conectarse a la base de datos, y pedir que traiga una lista especifica, por ejemplo: la aplicación envía un parámetro “Comida2” , el php recibe ese parámetro y lo usa para buscar en la tabla “Comidas” de mysql el id “Comida2” cuando lo encuentra recibe todos los datos de “Comida2” y los guardaría en un Json, y que la aplicación lea ese json y lo guarde, pero no se como hacer que el php reciba el parámetro y lo busque en la tabla “Comidas” como un id, si me tiras una idea de como lograrlo lo agradecería mucho!, sigan subiendo tutoriales están muy buenos !

    • Hola compañero. Tu te refieres a que no envias el id como parámetro si no otro campo distinto?, bueno eso depende de la consulta que le hagas a la base de datos mysql, dependiendo del tipo de la columnas así mismo usas las clausulas sql que sean necesarias para extraer los registros y luego retornarlos como json.

  • Buenas tardes, olle no entiendo bn en el momento que pasas a ver cada detalle de cada meta, no se si estoy bn pero lo que haces es en mainActivity poblar la lista y cuando le das en un elemento del recycler pasas el id de la meta y en la actividad detalle recibes ese id y llamas un servicio con ese id ???? . te agradecería esa aclaración :)

    • Hola Kenny. Si eso es lo que hago. Luego consulto de nuevo el detalle al web service con ese identificador. Eso era con el fin de probar el archivo php del servicio web. Sin embargo podrías pasar todos los valores a través del Intent.

      • osea , podría pasarle el array con un putExtra y enviárselo a la otra activity y en la otra mostrarlo todo, che!!! Muchas gracias , ya siento que les debo xD

  • Elar Hancco Quispe

    genial, gracias por el aporte, no hay opcion para descargar el ejm:

    • Gracias por comentar Elar. Claro que es posible. Ve debajo del video al inicio y verás una cajita para compartir el articulo. Cuando lo compartas se mostrará un botón de descargar :D

  • Kryancelt Belmont

    Donde Agrego la libreria que se lista al incio

  • Jaime Tellez

    Buen día James, de nuevo yo molestando por aqui. tengo una consulta, espero me puedan ayudar. uso un Asynctask para hacer la consulta a la webService usando la libreria Volley. el asunto es que en doInBackground que es donde se hace la consulta, me llama a onPostExecute antes de haber terminado la consulta. este es mi Asynctask.

    class ActualizarDatos extends AsyncTask {

    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    pDialog = new ProgressDialog(Ajustes.this);
    pDialog.setMessage(“Actualizando…”);
    pDialog.setIndeterminate(false);
    pDialog.setCancelable(true);
    pDialog.show();

    }

    @Override
    protected Boolean doInBackground(String… params) {

    final String nombreUsuario = names;
    final String TelefonoUsuario = tels;
    final String PassUsuario = passwords;
    final String EmailUsuario = emails;

    HashMap map = new HashMap();

    map.put(“email”, EmailUsuario);
    map.put(“password”, PassUsuario);
    map.put(“name”, nombreUsuario);
    map.put(“tel”, TelefonoUsuario);

    JSONObject jobject = new JSONObject(map);

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

    VolleySingleton.getInstance(Ajustes.this).addToRequestQueue(
    new JsonObjectRequest(
    Request.Method.POST,
    Constantes.UPDATE_USUARIOS,
    jobject,
    new Response.Listener() {

    @Override
    public void onResponse(JSONObject response) {
    try {
    String estado = response.getString(“estado”);
    switch (estado) {
    case “1”:
    update = true;
    Log.d(“Successful!”, response.toString());

    break;
    case “2”:
    update = false;
    String mensaje2 = response.getString(“mensaje”);
    Toast.makeText(
    Ajustes.this,
    mensaje2,
    Toast.LENGTH_LONG).show();
    break;

    }

    } catch (JSONException e) {
    e.printStackTrace();
    }
    }
    },
    new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
    Log.d(TAG, “Error Volley: ” + error.toString());
    Toast.makeText(Ajustes.this, “Respuesta incorrecta, verifica tu acceso a internet”, Toast.LENGTH_LONG).show();
    }
    }

    ) {
    @Override
    public Map getHeaders() {
    Map headers = new HashMap();
    headers.put(“Content-Type”, “application/json; charset=utf-8”);
    headers.put(“Accept”, “application/json”);
    return headers;
    }

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

    return update;

    }

    @Override
    protected void onPostExecute(Boolean result ) {
    super.onPostExecute(result);
    pDialog.dismiss();
    if(result){
    switch (TAG_Vactualizado){
    case “usuario”:
    datos.setNameUsuario(names);
    AlertDialog.Builder dr = new AlertDialog.Builder(Ajustes.this);
    dr.setTitle(“Nombre Actualizado”);
    dr.setMessage(“Tu nombre se ha actualizado correctamente. “);
    dr.setCancelable(false);
    dr.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();

    }
    });

    dr.show();
    break;
    case “telefono”:
    AlertDialog.Builder dt = new AlertDialog.Builder(Ajustes.this);
    dt.setTitle(“Teléfono Actualizado”);
    dt.setMessage(“Tu número de teléfono se ha actualizado correctamente. “);
    dt.setCancelable(false);
    dt.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();
    }
    });

    dt.show();
    break;
    case “contra”:
    AlertDialog.Builder dc = new AlertDialog.Builder(Ajustes.this);
    dc.setTitle(“Contraseña cambiada”);
    dc.setMessage(“Tu contraseña se ha actualizado correctamente. “);
    dc.setCancelable(false);
    dc.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();

    }
    });

    dc.show();
    break;
    }
    }else{
    AlertDialog.Builder de = new AlertDialog.Builder(Ajustes.this);
    de.setTitle(“Error”);
    de.setMessage(“Tus Datos no se han podido actualizar, intenta nuevamente.”);
    de.setCancelable(false);
    de.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();

    }
    });

    de.show();
    }
    }

    }

    al verlo se darán cuenta que como no espera a que termine la consulta, me afecta en el resultado, ya que update sera falso siempre. alguna solución? muchas gracias!

  • ruben pech

    disculpen, alguien sabe de un tutorial para que la aplicación muestre notificaciones en la barra de estado??por favor

  • cristian eugenio ferrazzano

    Hola una pregunta. En el caso de comunicar mi app android con un servidor que no sea local. Debería dar a conocer la ip pública del celular? como hago eso? gracias.

    • Aquí puedes ver una clase de utilidades para obtener la ip de tu dispositivo: http://stackoverflow.com/questions/6064510/how-to-get-ip-address-of-the-device

      • cristian eugenio ferrazzano

        Muchas gracias James. De todas maneras el problema era mio de comprensión. Pense que la IP que se colocaba de forma estatica era la del celular, ahora veo que tiene que ser la IP del servidor php.

        En cuanto al funcionamiento, te cuento que solo pude traerme las metas pero cuando quiero agregar una nueva me aparece esto.

        Desde ya muchas gracias, y como bien sabes estoy muy agradecido. Encontre aquí el santo grial ya que la parte de comunicación esta muy bien explicada y con ejemplos. Te felicito por tal trabajo.
        Un abrazo,

        Cristian.

        • COn gusto compañero

          • Brujita Linda

            Tengo este mismo problema no en lista despues de actualizar o insertar sabes a que se debe este problema ???? o alguien que le encontro el problema ???

          • La lista no se actualiza pero los elementos en la base de datos si?

          • Brujita Linda

            si la actualización es exitosa pero no afecta a la lista sigue mostrando los datos anteriores y no tendras un tutorial de mostrar imagenes desde Mysql usando la libreria volley ?

  • Jaime Tellez

    hola amigos, primero q todo felicitar a james por su gran trabajo, segundo, hacer una consulta acerca de este error q me aparece, espero me puedan ayudar.

    • Jaime Tellez

      aquí mas detallado mi error

      • Hola Jaime.

        Me parece que estás usando Volley con la librería de github. ¿Has probado poner (String)null en el tercer parámetro de la petición?

        Parece que este constructor no acepta directamente el valor de null, debe ir ese casting.

        • Jaime Tellez

          Si, uso la libreria de Volley: compile ‘com.mcxiaoke.volley:library:1.0.+’ , una dato mas, en otras clases también utilizo Json pero mediante POST , usando HttpURLConnection esto afecta con la compactibilidad?

        • Jaime Tellez

          si, era eso, muchas gracias amigo, disculpa la molestia, pero me salta una duda, en tu caso el id de las metas es autoincremental, pero si yo trabajo un id q no auto incrementa, si no que lo da el usuario, ¿como verifico que ese id ya existe en la base de datos? gracias de antemano!

          • El id autoincremental es necesario tenerlo en la base de datos SQLite. Siempre debe existir una columna llamada “_id”.

            Así que debes crear otra columna para el identificador particular. No hay de otra compañero :D

          • Jaime Tellez

            es que en mi caso, el id es un correo, esa es la llave de la base de datos, y sera irrepetible, pero lógicamente no se auto incrementa. y logre solucionarlo de esta manera, para los que deseen implementar algo similar:

            dentro de la clase que esten trabajando, en mi caso Usuarios, en el de este ejemplo fue Metas, agrego un metodo exist:

            public static function exist($Email)
            {
            $consulta = “SELECT email,
            password,
            name, –> esta consulta la hacen dependiendo de la base de datos de su proyecto
            tel
            FROM users
            WHERE email = ?”;

            try {
            $comando = Database::getInstance()->getDb()->prepare($consulta);
            $comando->execute(array($Email));

            // Capturar primera fila del resultado
            $row = $comando->fetch(PDO::FETCH_ASSOC);

            return $row;

            } catch (PDOException $e) {

            return -1;
            }
            }

            y ahora en la clase insertar_usuario(insertar_meta) pregunto si existe.:

            $parametro = $body[‘email’];
            $existe= Usuarios::exist($parametro);
            if ($existe) {
            print json_encode(
            array(
            ‘estado’ => ‘3’,
            ‘mensaje’ => ‘El E-mail ya existe’
            )
            );
            } else {

            si no existe, proceden a insertarlo……..

            y así fue como lo solucione, espero les sirva.

          • A ok, entonces los que tengan un problema similar ya tienen como guiarse con tu codigo.

          • Maleja Andrade

            Hola James.

            Una pregunta, a lo largo de esta discusión se ha nombrado errores en la ejecución del php obtener_metas… a mi se me presenta este error: Error Volley: org.json.JSONException: Value <br of type java.lang.String cannot be converted to JSONObject ::: se me presenta justo cuando estoy en la parte de actualizar la meta… le investigado tanto como he podido, pero ha sido infructuoso, si tu o alguien mas conoce la razón y la solución de este error seria GENIAL!! Y me la comunicaran …

          • Hola Maleja. ¿puedes pegar un pantallazo de la definición completa del error?

          • Maleja Andrade

            https://uploads.disquscdn.com/images/63c5f5fea9fc2f698d46c3ef42470622bffed6012def24d696da4745afce72c3.png

            Error Volley: org.json.JSONException: Value <br of type java.lang.String cannot be converted to JSONObject

            este error aparece cuando intento actualizar.

          • Mmm, analiza todo el código Json que sale arriba del error, ¿no ves alguna inconsistencia de sintaxis?

          • Maleja Andrade

            Lo que se encuentra en rojo es un Log.e que coloque vació para saber si llegaba a ese punto y verificar que información llegaba… la verdad he revisado y lo que mi corta experiencia me dice es que esta bien… sin embargo el error esta :( ! … volveré a revisar :D

          • :v , usa esta herramienta y pega el Json. Si al hundir el botón de procesar te muestra un buen resultado, es porque está bien: https://jsonformatter.curiousconcept.com/

          • Maleja Andrade

            James exactamente como funciona la herramienta ?.. :( :( no se utilizarla :( :(

          • Ingresa tu formato en el campo de área blanco y luego dale Process.

          • Maleja Andrade

            revise tu sugerencia… el problema esta en que no se esta convertiendo el en json.. he buscado posibles soluciones pero aun no consigo una… si tienes alguna idea de porque el JSONObject no esta creando un object basado en el map, seria genial :D!.. el problema radica ahí!

          • Mmm… sería bueno que hagas pruebas del servicio web en tu navegador para ver las respuestas que está dando.

          • Paul Rugel

            Jaime, James por favor ayúdenme con ese detalle que tengo el mismo problema y no se como solucionarlo, exactamente en donde debo hacer el cambio y si el cambio es en ese único lugar. Soy nuevo esto en nivel “sin nivel” se les agradece la ayuda. Saludos!!

  • jeanpier echevarria

    Hola alguien me puede ayudar, cuando guardo o edito un registro, la pantalla se queda en el mismo activity no cambia a la pantalla del listado ni muestra el mensaje de la acción realizada, trate de usar Intent para cambiar de actividad pero no me funciona.

  • Hola compañeros.

    Para todos aquellos que tengan el problemas de los caracteres  al momento del parsing en la aplicación, la solución ya está. Todo gracias al amigo @maximilianodoate:disqus.

    Resulta que el IDE que estoy usando ha encodificado todos los archivos del proyecto web en el formato UTF-8. Los caracteres raros que se agregan se les denomina BOM. Estos permiten marcar el orden de los bytes que se verán en el archivo. Si el servidor que usan no tiene habilitado el charset para UTF-8, entonces surgirán los problemas.

    La solución está en cambiar el charset al formato que más se te ajuste en tu editor. O si quieren, intentar copiar y pegar el texto de cada archivo en nuevos ficheros.

    Sin embargo acabo de convertir todos los ficheros a windows-1252. Descarguen de nuevo el proyecto desde el botón superior del articulo y pruebenlo.

    Espero que con ello todos puedan solucionar el problema. Si encuentran otros problemas me lo hacen saber.

    Saludos :D

    • Julio Vergara

      Saludos James, ya funciona, sin embargo, el nuevo IWish de Android que subiste tiene algunas cosas que hay que modificar en “Constantes” para que funcione bien…. en la línea private static final String IP = “http://10.0.120.75”; hay que eliminar el http:// así mismo en las líneas en donde defines las constantes para cada uno de los php del web service tienes I%20Wish, sin embargo el nuevo proyecto que subiste no tiene ese espacio en blanco, por lo que simplemente borramos en cada linea el %20 y listo….otra cosa es que hay que editar el puerto a 80 para las instalaciones por defecto (para los que usan mysql en una instalación por defecto, en el archivo Database.php cambien el puerto a 3306 en la línea 58) Saludos.

      • Ok Julio. Acabo de cambiar la IP en el proyecto para descargar. Gracias por tus comentarios. Al fin se solucionó el dilema :D

      • Santiago Orozs

        Saludos Julian y Jamas, tengo un problema en la petición del GET obtener_metas.php , lo corro ha paso a pasa y cuendo llega el metodo public void cargarAdaptador() el se sale ps creo que la peticion del GET esta mal o la verdad no se me pueden colaborar por favor ?

        Corro el AP en Genymotion.

        • Julio Vergara

          Saludos, sería muy útil si pones una captura de pantalla o específicas bien el error que te da

          • Santiago Orozs

            Saludos, Primero disculparme por cambiarle los nombre a los dos “Julio” y “James”, y segundo gracias por contestarme =). Les adjunto las captura de pantalla.

            Son 4 imágenes.

            Una muestra el panel de control de xampp.

            Otra el web servicies que funciona.

            Y la modificación de Database.php como dijo Julian.

            Y la ultima es la de android donde esta haciendo la petición GET en el método cargarAdaptador el cuando llega a la linea de codigo se”getInstance(getActivity()).

            addToRequestQueue” se salta =( no la corre toda.
            Como que la petición no estuviera mal y por lo tanto el

            “public void onResponse(JSONObject response) {
            // Procesar la respuesta Json
            procesarRespuesta(response);
            }”

            Por lo tanto response no tiene nada. por fa colabore me.

            Nota:Que pena no puede subir las imágenes no se por que me salia error antes.

          • Julio Vergara

            Saludos, manda una captura de pantalla del archivo Constantes.java que está en la app android por favor

          • Santiago Orozs

            Gracias Julio pude con el inconveniente solo cambie los String de las peticiones

            public static final String GET = “http://” + IP + PUERTO_HOST + “/I_Wish/obtener_metas.php”;

            …………………………………..

            la tenia
            public static final String GET = “http://” + IP + PUERTO_HOST + “/I Wish/obtener_metas.php”;

            Solo en el I Wish le puse I_Wish ya que mire Logcat fue donde pude mirar el error.

            Gracias Julio.

          • Julio Vergara

            De nada Santiago, que bueno que te funcionó…

          • Santiago Orozs

            Muchas Gracias.

            Otra Pregunta como puedo comprobar los WebServicies.
            Por ejemplo hacerles pruebas antes de hacer el código de la petición de post en android.

          • Julio Vergara

            Fácil, usando otro archivo php que le envíe a tu webservice los datos que enviarias con tu app android; sería una especie de simulador. También usando una app de chrome que se llama Advanced Rest Client…hay varias alternativas para hacer eso

          • Jaime Tellez

            puedes añadir este pequeño código en tu php servicie, para q introduzcas en este caso tu id o lo que necesite el webservice y testearlo.

            ejemplo con el de consultar meta por id

            ?>
            Consultar por ID

            ID de la meta:

            <?php

            eso lo hace con un if(!empty(variable_método)){} para qe preguntes si el método no esta vació, pues haces el código de arriba, si no, pues sigue su curso ya que vienen los datos de la app.

          • Jaime Tellez

            espues del from va < ? p h p, pegado, si lo pongo junto no sale

          • Que bien compañero. Esas direcciones pueden confundir ya que las instalaciones de todos no son igual.

        • Como dice Julio, insertar una imagen con la opción en la parte inferior izquierda de tu caja de comentario disquss.

      • Ali Lopez Galaviz

        Soy Novato en esto, estoy trabajando desde un dispositivo porque mi computadora no tiene los suficientes recursos, entonces mi pregunta es, si subo los archivos php a un servidor, como me comunico hacia tal server, tambien tengo duda en Constantes.java en la parte de String IP, creo esa ip es para cuando trabajamos con AVD, y si trabajo con un dispositivo real, que debo de hacer? espero sus respuestas.

        Saludos

        • Usa los datos reales de tu dispositivo compañero

          • Ali Lopez Galaviz

            Muchas gracias, el tutorial me sirvio mucho, y cada uno me ha ayudado a nutrir mis conocimientos, gracias!

          • Jose H.

            Que tal amigo, y que ip utilizaste para emular el ejemplo en tu movil?

          • Ali Lopez Galaviz

            Mira, si estas trabajando en local, la ip que use es la de mi ordenador, en mi caso fue en Windows, por lo cual use el comando ip config para obtener la ip de mi ordenador, el problema aquí es que la ip va cambiando, por lo cual cada vez que inicies el ordenador, puede dar el caso que tu ip cambie, espero haberte resuelto tu duda. Saludos.

        • Guillermo Vargas

          Hola una pregunta los archivos php los colocaste en la carpeta htdocs de xampp??

          • Si compañero. Si vas a usar un sevicio de hosting incluyelos en la carpeta public_html (frecuentemente se llama asi)

          • Guillermo Vargas

            ok gracias entendido, oye tengo un problema espero me puedas orientar, llevo dias tratando de adaptar unicamente la parte de insertar de este tutorial en un proyecto que estoy realizando, pero en mi formulario no utilizo fragments (aunque visualmente la parte de insertar es muy similar al tuyo, utilizo EditText del tipo parson name, de tipo date y spinners), bueno voy al grano yo lo unico que quiero que haga mi aplicacion es insertar pero como tu proyecto tambien, actualiza, elimina, muestra, me marca muchos errores en la parte de java, en php ya logre dejar unicamente el codigo para insertar, espero me puedas orientar ya busque en la red y no encuentro ningun tutorial parecido a lo que necesito mas que el tuyo, por cierto lo de insertar en mi proyecto ya lo puedo hacer desde una pagina web pero desde una aplicacion android hay muchas cosas diferentes y soy novato en android…. Saludos

      • Alex Ticse

        gracias man tu comentario me ayudo mucho me ayudaste con las dudas que tenia

        • Julio Vergara

          Me alegra que te haya servido, suerte

      • abrkof

        hola Julio… tengo un problema… soy novato en todo esto de android y estoy tratando de probar el IWish y no me funciona… solo lo trato de correro pero me tira el siguiente mensaje de error.

        podrias despejarme esa duda? ya le di click donde dice sync project pero luego me tira otro error.

        gracias de ante mano!

        • Julio Vergara

          Saludos, según lo que puedo ver en las capturas, no tienes definido un sdk por defecto en Android Studio, tienes instalado el java jdk? Si lo tienes al parecer Android Studio no lo encuentra

      • Bladimir

        Estimado, tiene que dejar los archivos en utf-8 y agregar la siguiente linea al Archivo Database.php charset=utf8 por ejemplo quedaría así

      • Bladimir

        Estimado, tiene que dejar los archivos en utf-8 y agregar la siguiente linea al Archivo Database.php charset=utf8 por ejemplo quedaría así

    • Hola.
      Muchas gracias por el tutorial.
      ¿Respecto al diagrama que aparece en el punto (3) usaste alguna aplicación en particular?

    • Josee Naava

      amigo una pregunta: se supone que al guardar te deberia de mostrar el mensaje Creación éxitosa y mandarte a otro fragment? porque ni me muestra el mensaje ni me manda a otro fragmento

  • Maximiliano Doñate

    Hola James, veo que están teniendo un inconveniente con caracteres “raros”:

    Error Volley: org.json.JSONException: Value  of
    type java.lang.String cannot be converted to JSONObject

    Noté que cuando subo el archivo “obtener_metas.php” a un Servidor, y luego lo edito como texto, aparece lo que puede verse en la imagen adjunta. Justamente estos caracteres raros al comienzo del archivo.
    Pueden editar el texto y quitar estos caracteres.
    Un saludo desde Buenos Aires

    • Maximiliano Doñate

      Un detalle más, también deben corregirlo en el archivo: mysql_login.php
      A mi ya me funciona de maravilla, cualquier duda me consultan. Excelente el tutorial amigo.
      Un abrazo

      • Richard Daniel

        Si la verdad a muchos nos sucede este error de los carácteres extras que devuelve el web service… una pregunta yo como la mayoría la estamos probando en un servidor local, pero no se como editarlo como texto, es decir abrirlos así como lo haces y muestras en tu imagen, si me podrías guiar te estaría muy agradecido

        • Maximiliano Doñate

          Con el notepad no pude detectarlos. Se me ocurre que lo mas simple sería crear nuevos archivos de texto y pegarles el código PHP. Es algo que quedó mal en los dos archivos mencionados. Si eso no funciona, te paso por acá o por mail los PHP ya modificados, saludos.

          • ¡Excelente Maximiliano!

            Esa es la solución, espero todos puedan verla. Gracias por tomarte el tiempo de corregirlo.

            Tal vez esto sucedió porque estoy usando phpstorm como editor, quizás el encodificado que tienen los archivos del proyecto estaba molestando. Y claro, al único que le estaba funcionando era a mi.

            Voy a notificar arriba para todos. Gracias amigo y saludos!

          • Maleja Andrade

            a mi se me presenta este error: Error Volley: org.json.JSONException: Value <br of type java.lang.String cannot be converted to JSONObject

            he leido todos los comentarios que tiene este sitio web al respecto… podrias ayudarme ?

      • victor obando

        Volví a crear el archivo obtener_metas.php y la aplicación funciono perfecto. gracias!!

    • programador2015

      ok, si quedo, no queria, porque copie los nuevos archivos de james pero aun asi no queria, mejor pegar el codigo de los php en notepad para que se pierdan esos simbolos raros y por fin funcionno…Gracias a james y a max, y seguimos programando

  • José Ramón Castaño de Paz

    Hola James. Tengo una duda y quiero ver si me la puedes aclarar. Soy principiante en Android Studio y quiero hacer algunas modificaciones en tu código. Por ejemplo, he asignado nuevos campos al fromulario de Nueva Meta y se muestran a la hora de ejecutar la aplicación y se agregan los nuevos datos a la base de datos, pero el layout es estático y no me muestra todo por lo que lo quiero hacer desplazable verticalmente. ¿Qué me recomiendas? Saludos desde Cuba. Jose

    • Hola José, puedes usar en tu layout una etiqueta . Recubre con este elemento el segmento donde deseas implementar scrolling.

      Algo como:

      TU CONTENIDO

      • José Ramón Castaño de Paz

        Gracias, viendo algunos ejemplos y usando algo de lógica lo resolví…
        en el fragment_form.xml luego del RelativeLayout principal puse el ScrollView, dentro de este otro RelativeLayout y dentro de este último puse el contenido del formulario y funcionó… ahora estoy viendo sobre la lista al principio… Saludos…

  • Julio Vergara

    Saludos de nuevo…alguien sabe si se resolvió el problema de la aplicación? a muchos nos pasa que la aplicación no lista las metas que ya se han almacenado en la base de datos, solo podemos ingresar nuevas metas…

  • Moises Mauricio Tejada Montes

    hola James! cuando quiero generar el apk me da este error, que podria ser?

    • Moises Mauricio Tejada Montes

      parte de abajo

      • Hola Moises. Creo que esto sucede porque no has creado los certificados para tu app.

        Ve a Build > Generated singed APK… y luego crea tus credenciales.

        Este artículo habla sobre ello: http://developer.android.com/intl/es/tools/publishing/app-signing.html

        También puedes encontrar la apk de prueba que genera Android Studio al usar el comando Build>Make Project dentro de TUPROYECTOappbuildoutputsapk.

        Me avisas como te va.

        • Moises Mauricio Tejada Montes

          Hola amigo James! he intentado crear los certificados y no lo puedo hacer, la verdad soy nuevo en esto! el detalle es que tengo que hacer una tarea similar! si me puedes hechar una manita, gracias de antemano!

  • Moises Mauricio Tejada Montes

    me podrian ayudar porque me da este error? cuando abro el proyecto..

    • No es un error amigo. Dice que si deseas configurar el proyecto con Git. Dale Ignore

  • Roberto Martinez

    no me lista las metas ingresadas, por lo mismo no puedo editar ni elimnar.
    alguna solucion que nos puedas dar.

    7068-7068/com.herprogramacion.iwish E/RecyclerView﹕ No adapter attached; skipping layout

    7068-7068/com.herprogramacion.iwish D/MainFragment﹕ Error Volley: org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject

    • Julio Vergara

      A varios nos pasa lo mismo

      • Que tal amigos. Acabo de probar el proyecto para ver si estaba mal implementado o con algún error, pero todo está bien.

        Tal vez pueda ser la forma en que crearon la base de datos en Mysql.

        Me gustaría saber si al abrir el archivo obtener_metas.php en su navegador, les sale el formato Json

        • Julio Vergara

          Saludos James.

          Te comento que yo cree la base de datos ejecutando un comando SQL en PhpMyAdmin con la sintaxis que colocaste en el artículo y si, al ejecutar el php se ve el JSon en pantalla con la información de las metas que están en la base de datos

          • Me parece super extraño. ¿Estás usando la librería volley desde github?

            ¿usas genymotion o los avd del sdk?

          • anonimato2010

            Como es eso?

          • Hablo de si usas los emuladores de Android o el de genymotion. Y si has quitado el modulo llamado “volley” del proyecto y lo has remplazado por la librería volley de github

          • Roberto Martinez

            no, tal cual se descargo de tu pagina, nadamas cambiando las direcciones de los webservices

          • Tienes teamviewver?

          • Julio Vergara

            Saludos James, igualmente, uso Genymotion y corro la app tal cual se bajó de tu página, tengo instalado git y también he configurado la ruta del ejecutable y mi cuenta de github en Android studio, sin embargo tengo el mismo problema, puedo insertar nuevas metas en la base de datos pero al abrir la aplicación no se muestran las metas en la pantalla principal

          • Hola Julio.

            Estaba probando a ver si era el encode de la base de datos, pero al parecer ese no es el problema.

            Tendría que ver el problemas mas de cerca. Por lo que necesitaría que alguien use teamviewer

          • Roberto Martinez

            yo deja te doy acceso en un momento, mm creo que no podras checarlo desde mi teamviewer, no tengo emulador de android, no puedo virtualizar y por eso lo hago directo en usb por smartphone

          • José Ramón Castaño de Paz

            Lo mismo que a Roberto Martínez me pasa a mi. Uso el teléfono para correr la app y puedo crear nuevas metas pero no me deja ver la lista al principio. El JSON está bien. En mi caso estoy usando un web hosting y todo bien hasta que al ejecutar la app en el teléfono no me sale nada en el RecyclerView…

          • Hola José.

            Ayer estabamos probando en el PC de @juliovergara:disqus y descubrimos que está llegando el Json pero con dos carácteres adicionales al inicio. Justo esos dos carácteres están molestando. Pensamos que es el encodificado de los datos que está haciendo volley.

            Sin embargo todo esta bien. Espero poder solucionar este problema que se le presenta a muchos para ver que sucede con los datos en el viaje.

            Saludos!

          • Richard Daniel

            Ojalá puedas solucionarlo James se te agradecería mucho, estaré pendiente para ver las novedades, en un comentario anterior dije que ese podría ser el posible error, pero dónde solucionarlo eso si ya sale de mis conocimientos. Se te agradece el esfuerzo por tratar de resolverlo y ayudar a la comunidad

          • Dale Richard, esperemos que esto dure poco.

          • José Ramón Castaño de Paz

            Estaré pendiente. Realmente me interesa este contenido. Espero lo soluciones pronto.
            Saludos.

          • Ok José, espero todo salga bien. Envíenme energía positiva

          • programador2015

            ok, animo James, eres un as de android tu puedes, esperamos solucion, por favor nos lo compartes, saludos

          • ajajaj eso espero, gracias

          • Julio Vergara

            Yo uso teamviewer, avisa y te mando los datos por privado

          • Roberto Martinez

            alguna novedad?

          • Ok, si puedes en este momento sería genial.

          • Julio Vergara

            dame tus datos para mandarte los detalles de la conexion por privado por favor

          • escribeme por el inbox de la página de facebook. https://www.facebook.com/hermosaprogramacion

          • Hola Julio.

            ¿Has probado quitando el módulo Volley de la app y usar mejor la librería de github?

            https://github.com/mcxiaoke/android-volley/

          • Julio Vergara

            La verdad no

          • Julio lee el primer comentario, ya encontramos la solución.

          • Julio Vergara

            Ok, leo y pruebo, gracias

          • Julio Vergara

            Esto es lo primero que sale en el logcat al correr la app en Genymotion

        • anonimato2010

          {“estado”:1,”metas”:[{“idMeta”:”1″,”titulo”:”Comprar Mazda 6″,”descripcion”:”Deseo adquirir un auto mazda 6 para mi desplazamiento en la ciudad. Debo investigar como conseguir mas fuentes de ingresos”,”prioridad”:”Media”,”fechaLim”:”2015-11-20″,”categoria”:”Material”},{“idMeta”:”2″,”titulo”:”Obtener mi tu00edtulo de ingenieria de sistemas”,”descripcion”:”Ya solo faltan 2 semestres para terminar mi carrera de ingenieria. Debo prepararme al maximo para desarrollar mi tesis de grado”,”prioridad”:”Alta”,”fechaLim”:”2016-06-17″,”categoria”:”Profesional”},{“idMeta”:”3″,”titulo”:”Conquistar a Natasha”,”descripcion”:”Natasha es la mujer de vida. Tengo que decu00edrselo antes de que acabe el semestre”,”prioridad”:”Alta”,”fechaLim”:”2015-05-25″,”categoria”:”Espiritual”},{“idMeta”:”4″,”titulo”:”Tener un peso de 70kg”,”descripcion”:”Actualmente peso 92kg y estoy en sobrepeso. Sin embargo voy a seguir una rutina de ejercicios y un regimen alimenticio”,”prioridad”:”Baja”,”fechaLim”:”2016-05-13″,”categoria”:”Salud”},{“idMeta”:”5″,”titulo”:”Incrementar un 30% mis ingresos”,”descripcion”:”Conseguire una fuente de ingresos alternativa que representen un 30% de los ingresos que recibo actualmente.”,”prioridad”:”Media”,”fechaLim”:”2015-10-13″,”categoria”:”Finanzas”},{“idMeta”:”77″,”titulo”:”oki”,”descripcion”:”doki”,”prioridad”:”Alta”,”fechaLim”:”2015-05-17″,”categoria”:”Salud”},{“idMeta”:”78″,”titulo”:”inserta”,”descripcion”:”ok”,”prioridad”:”Alta”,”fechaLim”:”2015-05-17″,”categoria”:”Salud”},{“idMeta”:”79″,”titulo”:”otro”,”descripcion”:”mas”,”prioridad”:”Alta”,”fechaLim”:”2015-05-17″,”categoria”:”Salud”},{“idMeta”:”80″,”titulo”:”saludos”,”descripcion”:”otra mas ya”,”prioridad”:”Alta”,”fechaLim”:”2015-05-17″,”categoria”:”Salud”},{“idMeta”:”81″,”titulo”:”sal”,”descripcion”:”agria”,”prioridad”:”Alta”,”fechaLim”:”2015-05-17″,”categoria”:”Salud”},{“idMeta”:”82″,”titulo”:”hot”,”descripcion”:”29 de julio”,”prioridad”:”Alta”,”fechaLim”:”2015-05-17″,”categoria”:”Salud”}]}

          • Mmm…o sea que funciona el servicio web.

            De casualidad están usando la librería volley que se encuentra en github?

        • anonimato2010

          este es el resultado obtener_metas.php

          probe
          {“estado”:1
          y
          {“estado”:1″

          y nada, lo estoy compilando en android studio 1.2.2 por si es problema de versiones, pruebalo en esta version si te funciona igual James y me avisas porfa, no se en que version lo creaste o lo compilas

        • Miguel Angel –

          Hola a todos especialmente a James:
          Primeramente enhorabuena por el tutorial.
          Por otra parte decir que estoy igual que estos compañeros y con el mismo error que:

          7068-7068/com.herprogramacion.iwish E/RecyclerView﹕ No adapter attached; skipping layout

          7068-7068/com.herprogramacion.iwish
          D/MainFragment﹕ Error Volley: org.json.JSONException: Value  of
          type java.lang.String cannot be converted to JSONObject

          ¿Alguna idea?

          • Hola Miguel.

            Estoy viendo que podría ser, ya que yo ejecuto la app en los emuladores y telefonos reales y funciona muy bien.

            Espero encontrar la solución.

        • yonatan arroyave

          Amigo amigo ya me funciona todo, me muestra, me inserta, y se elimina, pero lo hace en la base de datos, pero no me muestra ningun tipo de notificacion, ejemplo inserto algun registro y le doy al chulo pero no me sale ingun error ni nada solo se queda alli, pero miro en la bd y si lo inserto, igual cuando lo edito y elimino.

          • A que te refieres con notificacion? ¿ a los Toasts?

          • yonatan arroyave

            si no sale nada, pero si realiza las acciones (insertar, eliminar, actualizar.)

  • Moises Mauricio Tejada Montes

    hola amigo he seguido el tutorial y me da este error

  • Rafa

    Hola , James, te agradeceria que me ayudases con lo siguiente:
    He seguido el tuto, y todo bien, me salen los items, los actualizo, los borro, los edito, etc, todo bien, pero quisiera que me orientases con lo siguiente: quiero añadir a los item una imagen de cada uno, que estaria alojada en el servidor. He tratado de hacerlo siguiendo tu tutorial de Volley, pero no doy con la solucion, si te es posible, te estaria inmensamente agradecido si me dieses una orientacion de como hacerlo. (Tambien pretendo que en UpdateFragment, poner un boton para activar la camara del dispositivo y subir imagenes al servidor). Muchas gracias por compartir tus conocimientos. Saludos.

    • Hola Rafa.

      Pues lo más sencillo es incluir la URL en cada elemento de la base de datos mysql para que puedas enviarlo a través del Json como atributo.

      En el artículo de volley se muestra como se hace. Solo que allí simplemente se pone el nombre y no toda la url.

  • Daniel

    Hola buenas tardes, disculpa me podrias decir como puedo solucionar este error, ya estuve investigando y no encuentro nada, es con getAdapterPosition()

    • Hola Daniel.

      Si situas tu cursor en el método getAdapterPosition() y presionas ALT+ENTER ¿que mensaje te sale?

      Pega un pantallazo

  • Daniel

    Hola amigo me gusto tu tutorial, pero tengo un error en el AndroidManifest.
    Espero me puedas ayudar, la verdad es que apenas estoy aprendiendo y no se que es lo que esta mal

    • Hola Daniel.

      Te has fijado que el paquete java que tienes en el proyecto está referenciado en la etiqueta ?

  • julio castañeda saire

    buenas tardes james, felicitarte por un excelente aporte y una motivacion de querer aprender a programar.
    estoy empezando a aprender a programar y como mi falta de conocimiento en este campo tan amplio
    te pediria que me expliques por favor en la parte de conexion con el localhost, he cambiado la url, he puesto los puerto en blanco y lo que he conseguido es insertar metas en la base datos, pero no puedo visualizar las metas ni la que he creado.
    solo puedo verlo en la base de msql.

    te muestro algunas imagenes, y si es correcto que este en blanco cuamdo habro borrar meta, insertar meta,
    te atenmano muchas gracias

    • Hola Julio.

      Claro que es correcto. Veo que el archivo que obtiene el detalle está funcionando, ya que te arroja el objeto json de error.

      Para eliminar y borrar se necesita una petición con el método POST. Cuando tu abres los achivos desde el navegador, estás usando un método GET, por eso hay información.

      Sin embargo, si en el código haces esto:

      if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) {

      ….

      }else echo ‘Método no reconocido’;

      Verás como imprime el mensaje.

      Puedes probar tu petición POST con esta extensión de Chrome: https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo

  • Compañero una consulta, cómo hago para filtrar los datos por id desde la url, por ejemplo para obtener todo era localshost…./getAll.php? pero para el getByID? Cómo escribiría la URL?

    • Hola Marcos.

      Te refieres a esta línea:

      String newURL = Constantes.GET_BY_ID + “?idMeta=” + extra;

      Ese tipo de dirección luciría como: …/I%20Wish/obtener_meta_por_id.php?idMeta=2

  • OMAR BARRERA

    Tengo el siguiente error, No me enlista los datos, pero si puedo agregarlos
    Error Volley: org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject

    • Hola Omar.

      Puedes pegar un pantallazo de ese error por aquí?

      • OMAR BARRERA

        podrias conectarte a mi Pc via teamweaver

        • Se me dificulta usar teamviewer amigo por el tiempo.

          La imagen que pegaste no enfoca el error muy bien, ¿podrías bajar un poco más y subrayarlo?

          • Julio Vergara

            Saludos James, tengo el mismo problema, puedo insertar las nuevas metas pero no se listan las que ya están en MySql, el error es el de línea :14.775

          • Hola Julio.

            Parece que la respuesta del servicio web está retornando en error. Te fijaste en cambiar las url, los puertos, usuario y contraseña del proyecto?

          • Julio Vergara

            Saludos.-

            Si claro, como te comenté, desde la aplicación en el emulador puedo tranquilamente registrar nuevas metas y éstas se graban sin problemas en la tabla de MySql, pero al momento de abrir la aplicación en el emulador NO se listan las metas que ya están en la base de datos y en el log sale el error de la imagen anterior…que raro no? Te agradezco tu orientación, ya que no ubico lo que pueda estar pasando

          • O sea, al insertar o eliminar los items, phpMyAdmin cambia su información y todo bien hasta allí.

            ¿Pero cuando cierras la app y la vuelves a abrir, se presenta un error al cargar la info guardada?

            ¿O sea que no se muestra la lista?

            Tocaría probar el archivo php que obtiene las metas en el navegador, ¿que te sale?

          • Julio Vergara

            Saludos James.-

            Sólo puedo insertar, y todo bien, phpMyAdmin refleja los cambios…no puedo eliminar porque la lista no se muestra, al abrir la aplicación (no importa si es la primera vez o veces sucesivas) la aplicación no muestra la lista…al ejecutar el archivo obtener_metas.php en el navegador, éste refleja el JSon con la información de las metas almacenadas en la base de datos

          • Roberto Martinez

            me pasa lo mismo…que hacemos?
            saludos

          • Amigos, añadan un punto de control para depurar donde les indico en la imagen. Luego depuran y ven el contenido de la variable mensaje para ver que trae consigo.

            Adicionalmente cambien el print del stack por el log que les muestro. Tal vez este mensaje traigo algo mas de info del error.

          • Roberto Martinez

            TRUENA AQUI, NO ENTRA A PROCESAR Y SE EJECUTA COMPLETO LA APP

            public void cargarAdaptador() {
            // Petición GET
            VolleySingleton.
            getInstance(getActivity()).
            addToRequestQueue(
            new JsonObjectRequest(
            Request.Method.GET,
            Constantes.GET,
            null,
            new Response.Listener() {

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

            )
            );
            }

          • Y el log que mensaje te arroja?

          • Así

          • anonimato2010

            ahi esta, es igual

          • Richard Daniel

            Un saludo compañeros y para tí James, bueno la verdad a mí también me sucede lo mismo que ha ustedes, no se me listan las metas, solo puedo agregarlas y ver como se actualiza la base de datos con las nuevas metas, pero verlas en lista como se ve en la práctica no, igual que ustedes si me devuelve el Objeto tipo JSON en el navegador, como veo que a algunos nos ocurre lo mismo estuve queriendo hallar la solución al problema y en muchos se indica que aveces obteniendo los datos del servidor se añaden caracteres al redactar la cadena. http://w3facility.org/question/android-json-parsing-issue-java-lang-string-cannot-be-converted-to-jsonobject/
            Tal ves esa sea la solución pero en dónde sería de implementarla?? si no, pues James podrías resubir el código no se para intentar otra vez

          • Hola Richard.

            Fijate que descargo el código que les comparto para ejecutarlo y me funciona. Yo creo que tiene que ver con el encode de los datos desde mysql, voy a hacer unas pruebas para ver que sucede.

          • Roberto Martinez

            LO EJECUTAS EN UN CELULAR SMARTPHONE O VIRTUALIZADO YO LO EJECUTO EN UN LG PRO LITE DIRECTAMENTE AL CELULAR POR USB EN EL VIDEO VEO QUE LO EMULAS EN UN VIRTUAL, PRUEBAL EN UN CEL..
            SALUDOS

          • Richard Daniel

            Si lo ejecuto en una tab3 y en el AVD propio de android, en ambos el resultado es el mismo, en Genymotion probaré ahora a ver el resultado

          • Dale Richard.

            Yo también ando probando las versiones. Aunque para mi debe ser la base de datos

          • Presiona ctrl+click para que te dirijas a la definición del método amigo

          • Roberto Martinez

            me dirige al archivo
            JsonObjectRequest.java……………………..
            —————————————————–
            import android.content.Context;

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

            /**
            * 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
            */
            private 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 Resultado final de tipo T
            */
            public void addToRequestQueue(Request req) {
            getRequestQueue().add(req);
            }

            }

          • MMm… definitivamente debo usar teamviewer.

            Ese error no me sale a mí, ya he probado android 5 y 4.4 con distintos emuladores y todo sale bien.

            Tal vez creo que es la base de datos

          • Roberto Martinez

            NO LLEGA A ESA PARTE DEL CODIGO.

          • Roberto Martinez

            SOLO LLEGA AQUI Y SE BRINCA TODO LO DEMAS

            public void cargarAdaptador() {
            // Petición GET
            VolleySingleton.

          • Julio Vergara

            Saludos James, creo que ya vi el error, fijate en la imagen que te mandé del JSon, el 1, en “estado”, no está dentro de comillas

          • Gus Cortez

            Hola Julio, lograste solucionar el problema solo al poner el 1 dentro de las comillas? Saludos

          • Julio Vergara

            Pues no, ya que no he logrado dar con la sentencia que construye el JSon, lo curioso es que el autor (James) dice que a él le funciona, pero habemos muchos con el mismo problema

          • José Ramón Castaño de Paz

            logcat me da el siguiente error:

            E/RecyclerView﹕ No adapter attached; skipping layout
            D/MainFragment﹕ Error Volley: org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject

          • José Ramón Castaño de Paz

            Esta es la imagen del error…

          • Si compañero. Ese es el error, esos dos caracteres se están adicionando. Al depurar podrás ver que viene el json que vemos en el navegador solo que por algún motivo se están anteponiendo esos dos caracteres marcados.

  • Hola buenas noches, no entiendo como puedo crear el servicio en local o no se si estas usando un hosting ? tengo un hosting pero no tengo mucho conocimiento de como crear este servicio en el .

    • James Revelo

      Hola compañero.

      Este tutorial usa nuestra propia PC como un servidor. Por eso se le dice servidor local. Sin embargo primero debes instalar XAMPP para que tu pc actue de esta forma.

      Tal vez este tutorial te sirva: https://www.youtube.com/watch?v=nZyn-7S8ivc

  • MArcos

    Hola, para diseñar el diagrama con esos smartphones usaste alguna programa especial?

    • James Revelo

      Hola Marcos. Usé ninjamock

      • MArcos

        Gracias por tu pronta respuesta, estoy tratando de seguir el tutorial. Pero te comento.. Lo que e realidad estoy buscando es sincronizar el mysql de mi servidor con el sqlite de android , como tu mismo lo dices para evitar el uso de red varias veces. He estado buscando algún tutorial en español sobre eso. Tendrás algún link? :) Muchas Gracias de Antemano

        • James Revelo

          Hola Marcos. Precisamente estoy creando un artículo sobre eso que dices. Es un sencillo ejemplo para sincronizar los datos con un sync adapter.

          Aunque no es una aplicación completa, creo que puede servirte para que te guíes.

          Saludos!

          • MArcos

            UFFFFFFF

            Buenísima! Estaré esperando…

          • James Revelo

            :D

  • gabriel villarreal

    solucione el problema que tenia como vos decías. tenia mal la url y deje sin puerto. lo único k no me salio con la base creada en xampp. hice una en un host y subí los php allí y me anduvo.

    me surgió la duda de como hacer para traer de una base de datos información y fotografía almacenadas alli., la foto seria la url donde esta almacenada la foto para no hacer tan pesada la db. y mostrar esa info en un listview. ojala me soluciones este dilema existencial que me parece una tontera pero no me sale aun. muchas gracias amigo.

    • James Revelo

      Hola Gabriel.

      Si, el atributo para los items no sería los bytes de la foto, si no la url para descargarlas. Si deseas que queden almacenadas en cache, puedes usar la librería Glide o Picasso para que lo haga automáticamente.

      Saludos!

  • Aaron

    Hola mira tengo una duda agregue todo tal como esta en el tutorial pero cuando la aplicacion me da error el MainActivity

    • James Revelo

      Hola Aaron.

      ¿Que tipo de error te da?

  • Andrés Felipe Muñoz

    Buenas Noches al intentar importar la libreria floating button me genera el siguiente error:
    configuration with name ‘default’ not found.
    podrían ayudarme no encuentro la solución en miles de tutoriales he buscado y nada.

    Gracias y quedo atento.

    • James Revelo

      Hola Andrés.

      Puedes mostrarme el código que tienes en tu archivo build.gradle?

      • Andrés Felipe Muñoz

        Hola James este es el archivo build.gradle del proyecto:

        apply plugin: ‘com.android.application’

        android {
        compileSdkVersion 22
        buildToolsVersion “22.0.1”

        defaultConfig {
        applicationId “com.example.andres.deseos”
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName “1.0”
        }
        buildTypes {
        release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
        }
        }
        }

        dependencies {
        compile fileTree(include: [‘*.jar’], dir: ‘libs’)
        compile ‘com.android.support:appcompat-v7:22.2.0’
        compile ‘com.melnykov:floatingactionbutton:1.3.0’
        compile project(‘:Libreria:FloatingActionButton-master’)
        }

        Y este el archivo settings.gradle:

        include ‘:app’
        include ‘:Libreria:FloatingActionButton-master’

        quedo atento a tu ayuda, gracias.

        • James Revelo

          Hola compañero.

          ¿Por que tienes ese segundo módulo include ‘:Libreria:FloatingActionButton-master’ en tu archivo settings.gradle?

          Hasta donde se simplemente necesitamos incluir compile ‘com.melnykov:floatingactionbutton:1.3.0′ y con eso ya tenemos el floating action button funcionando.

          Prueba quitando esa linea y la que dice compile project(‘:Libreria:FloatingActionButton-master’) a ver que tal.

  • gabriel villarreal

    Hola, felicitaciones por el detallado tutorial, me encanto la pagina, aunque soy nuevo programando en android studio. Te consulto porque no puedo conectarme a la base de datos, ya la cree, puse los archivos php en htdocs de la carpeta xampp, pero al ejecutar en el emulador Genymotion me aparece la aplicacion en blanco, y no me muestra nada al principio y si quiero agregar algo, tampoco me permite hacerlo. Desde ya muchas gracias y disculpa mi ignorancia. Saludos!!

    • James Revelo

      Hola Grabriel.

      ¿Te fijaste cambiar el usuario y la contraseña de tu archivo mysql_login.php?

      También debes cambiar las constantes de la clase Constantes para que se refieran a la URL de tu localhost.

      Si no usas puerto, dejalo en blanco.

      Acabo de cambiar la clase Constantes para que sea mas personalizada. De seguro esos datos de configuración son los que te están molestando.

      Saludos!

  • Hola que tal, leyendo casi todo sobre android que as subido es super interesante, quisiera pedirte algo de ayuda, o un consejo de como hacer lo siguiente.

    hacer un web service y en android offline-online

    que tenga mi base de datos en android, para escribir todo el formulario sin internet, pero cuando quiera subir todos los registros que haya hecho se suban y haga update en el server, ya que sin internet no funsionaria, no doy con eso y no soy un master ando en aprendizaje aun, quizas puedas ayudarme a mi y a varios, saludos.

  • Ricardo

    Hola primero que todo excelente post!, tengo un problema, resulta que cogi varias partes del code como lo del volley y eso para hacer mi programa, la cosa es que al llenar el adaptador siempre me tira error, dirigiendose siempre al Response.ErrorListener y no se por que no la hace. Tengo varias hipótesis, no se si sea por que mi base de datos (Es una externa, el server esta en USA y hago el hosting por medio de HostDime) se haria de otra forma ya que tu usas un localhost. Otro no se si el link que puse en la clase Constantes sea el correcto. Ayúdame por favor que no se bn que pasa ya que el logcat solo me dice “Error Voley” y estoy seguro que el code esta bn.

    PD: Lo del manifest de la conexión de Internet también lo revise y esta bn. Espero tu respuesta de nuevo excelente post.

    • James Revelo

      Hola Ricardo.

      Bueno, en la clase para las constantes obviamente debes cambiar todo para que se ajuste a tus características de conexión.

      Sería bueno saber que dice el error de Volley. Si quieres pega el error aquí para verlo.

      • Ricardo

        Hola la estructura de la costante es esta: “http://ip_de_mi_server:puerto/nombre_de_la_BD” lo otro es que no entiendo bn como usar los scripts de PHP tambien…

        El error en el Volley es este: 07-04 10:19:10.556 23147-23147/com.example.ricardo.biblo D/Prueba﹕ Este es el errorcom.android.volley.NoConnectionError: java.net.ProtocolException: Unexpected status line: J������.

        • Ricardo

          Hola la estructura de la costante es esta: “http://ip_de_mi_server:puerto/nombre_de_la_BD” lo otro es que no entiendo bn es donde colocar los scripts de PHP tambien siendo que uso un servidor externo y no el xampp…

          El error en el Volley es este: 07-04 10:19:10.556 23147-23147/com.example.ricardo.biblo D/Prueba﹕ Este es el errorcom.android.volley.NoConnectionError: java.net.ProtocolException: Unexpected status line: J������.

          • James Revelo

            Los scripts php normalmente se ponen en la carpeta htdocs de la instalación de Xampp, ¿allí los estás ubicando?

  • enmanuel

    Buenas Noche

    Estoy probando este ejercicio cuando ejecuto la aplicacion en el metodo cargarAdaptador , me esta dando el siguiente Error com.android.volley.ParseError: org.json.JSONException: Value <br of type java.lang.String cannot be converted to JSONObject

    • James Revelo

      Hola Enmanuel

      Ese tipo de error se debe a que no se está obteniendo un formato JSON en la respuesta de la petición que estás haciendo.

      Fíjate bien que el archivo php al que estás enviando la petición retorne siempre un formato JSON.

  • Salvador Sánchiz

    Un abrazo desde Valencia, España, no te imaginas lo que me has ayudado a poder conectarme con una base de datos desde Android. Sobre un mes he estado guarreando por el internet.. Había estado viendo tutoriales con clases de Android y PHP deprecated y gracias a tu artículo actualizado Y COMENTADO (para su entendimiento) por fin he conseguido conectar Android con MySQL a través de PHP con las herramientas vigentes hasta el momento y además sabiendo lo que hago en cada momento. Solo puedo decirte que gracias, gracias por todo tu blog/web, a favoritos que va. Espero que no se te vayan las ganas de ayudar y compartir tus conocimientos si el tiempo y trabajo te lo permite. En serio.

    Pregunta: en el archivo obtener_metas.php, el último print en caso de no haberse realizado correctamente la querry.. no sería ¿ json_encode(array( […] )); ? Para poder codificar el array en formato JSON y enviarlo al dispositivo Android. Si me estoy confundiendo, me gustaría que me comentaras en dos lineas por qué.

    Un saludo!
    – Salva

    • James Revelo

      Gracias por tu gran comentario Salvador.

      Y si, tienes razón es encode. Me falló la mente allí.

      Saludos amigo!

  • Daniel

    Tengo el mismo problema que Juan, me sale error pidiendo el metodo getAdapterPosition() que se pide en MetaAdapter.
    Ya compare por si me faltaba algo de lo que incluiste en el codigo para bajar, pero nada de nada, no encuentro ese metodo.

  • Giovanni

    Alguien que haya resuelto el problema de actualizar y eliminar, que pueda compartirnos el codifgo porfavor

    • James Revelo

      Hola Giovanni. Acabo de cambiar el código.

      El error estaba en la etiqueta del argumento IDEXTRA.

      Solo tienen que usar la etiqueta almacenada en la clase Constantes llamada EXTRA_ID.

  • Eric Vargas

    Que tal un saludo.
    Antes que nada un excelente Tutorial para los que como yo comenzamos en esto de android, espero no molestar demasiado pero será que me puedan compartir cual es el error del codigo que hace que la app se salga de la ventana de editar y que no borre la Meta ? se los juro que llevo un buen rato con esto, seria de mucha ayuda.

    Gracias de antemano por la info.

    • James Revelo

      Hola Eric. Gracias por comentar.

      ¿El error que dices se produce en el proyecto de android studio que dejé para que descargarán?
      ¿Que tipo de error es? ¿ Que dice el logcat?

  • Giovanni

    Hola que tal excelente tutorial me funciono , pero quisiera hacer una version donde solo se pueda ver los items y sus detalles mas no insertar o editar trate quitando el boton pero me da errores al momento de ver el detalle de algun item

    • James Revelo

      Hola Giovanni.
      ¿que dice el logcat?

  • Teo

    Hola James, que tal, magnifico post, bueno te comento un poco… todo ok, el problema que tengo es con UpdateFragment, no me recupera los datos del DetailFragment, y claro esta, no hace nada, es mas si pulso en descartar me echa de la app. Espero que me puedas ayudar. Gracias y salu2.

    • James Revelo

      Hola Teo, ¿te fijaste haber creado el fragmento desde la actividad con su método estático createInstance()?, Recuerda que con este método enviamos el ID hacia el fragmento.

      • Teo

        Gracias James, he estado cambiando algunas cosillas en UpdateActivity, y ahora todo ok. Perfecto. Solo una preguntita, a ver si me puedes dar una idea de como hacerlo. Supongamos que el idMeta es una referencia de geolocalizacion, ¿como podriamos filtrar los datos a mostrar en el RecyclerView, en funcion de la proximidad del terminal en donde estaria instalada la app, y en relacion a las distintas metas? De nuevo, gracias.

        • Giovanni

          Teo Podrias ayudarme con la modificacion que hiciste en updateActivity para cachar los datos del detailfragment, y poder hacer la edicion y eliminacion de la meta porfavor!

          • Teo

            Intentalo con esto (en UpdateFragment):

            public static Fragment createInstance(String extra) {

            UpdateFragment updateFragment = new UpdateFragment();
            Bundle bundle = new Bundle();
            bundle.putString(Constantes.EXTRA_ID, extra);

            updateFragment.setArguments(bundle);

            return updateFragment;
            }

  • Daniel

    Buenisimo tutorial, una pregunta, ya que aun voy medio verde con el asunto Android, y estoy utilizando Eclipse, no empece con android studio, tendria posibilidad de realizar todo el tutorial en Eclipse?, o tengo que pasarme a Android studio de plano?

    • James Revelo

      Gracias por comentar Daniel.

      De poder usar eclipse se puede, solo que este tutorial se basa en Android Studio, por lo que tendrías que averiguar como realizar las acciones equivalentes.

      Pero si te recomiendo que uses Android Studio, ya que Google lo tiene como IDE preferido. Puedes ver más aquí: http://developer.android.com/intl/es/tools/sdk/eclipse-adt.html

      Saludos!

  • Santiago Jara

    Hola amigo, estuve buscando como loco una pagina que explique algo sobre el consumo de webservice mediante android y te dire que no lo encontre, antes que nada te agradezco por compartir tu conocimiento y de una forma muy detallada y con capturas de pantalla lo cual solo mejora tu explicación, espero que me puedas ayudar si me surge alguna duda o inconveniente, sin mas muchas gracias.

    Felicidades y sigue adelante.

    • James Revelo

      Gracias Santiago :D

  • Rafalucor

    Hola James, aqui sigo dandole vueltas a el asunto. He seguido el tutorial, tal y como aparece, pero me da el siguiente error: No adapter attached; skipping layout. He mirado por stackoverflow, a ver si me aclaraba algo, pero nada. Te agradeceria que me comentases a que puede ser debido esto. Por otra parte, si no es mucha molestia, me gustaria que aclarases (para los torpes como yo) un poco el origen de los datos, pues no se si dejando los que tu tienes, y usando el emulador de Genymotion, estaria correcto. Gracias por tu ayuda y un saludo.

  • Hugo (Portu)

    JAMES REVELO, yo tengo un pequeno problema, cuando llamo la updateactivity para modificar los dados, la misma no optine los dados antetiores da detailactivity :(
    Por eso no consigo modificar y borrar los dados da bd mysql.
    ayudame a solucionar este problema. Gracias

  • Marco

    Hola bro! primeramente te felicito por tus grandisimos tutoriales, de lo mejor que he visto ultimamente.
    Me gustaría que hicieras algún tutorial acerca de notificaciones en tiempo real, haciendo uso de un webservice, estaría genial.

    Gracias, y saludos sigue así :D

    • James Revelo

      Gracias Marco.

      Ese tema ya está planificado, pero puede que demore :D

      Saludos!

  • Rafalucor

    Estimado James, magnifico tutorial, es lo que llevaba ya tiempo buscando para una idea que tengo en mente. Una pequeña consulta ¿los archivos php para crear la conexion a la base de datos, en donde deben estar ubicados? porque llevo todo el dia intentado averiguarlo y no doy con la solucion. Te agradeceria que me indicaras en donde deberian estar. Perdon por la torpeza, y mil gracias por tu tiempo.

    • James Revelo

      Hola Rafalucor.

      Los archivos php van en la carpeta htdocs de la instalación de XAMPP. Alli pones todos los scripts.

      • Rafalucor

        De nuevo, mil gracias.

  • Juan

    Excelente tutorial, lo que me falla por ahora es en el archivo MetaAdapter, especificamente en el punto donde dice:

    @Override
    public void onClick(View v) {
    listener.onItemClick(v, getAdapterPosition());
    }

    en getAdapterPosition me da error diciendo que no encuentra ese metodo.

    Ademas tengo problemas con JSON donde me da este error al momento de compilar:

    Error:(116, 17) error: reference to JsonObjectRequest is ambiguous
    both constructor JsonObjectRequest(int,String,String,Listener,ErrorListener) in JsonObjectRequest and constructor JsonObjectRequest(int,String,JSONObject,Listener,ErrorListener) in JsonObjectRequest match

    te seria inmensamente agradecido si me respondes esto y me ayudas a solucionar este problema.

    • James Revelo

      Hola Juan. En el primer error no se que podría ser, fijate si tu viewholder está heredando de RecyclerView.ViewHolder.

      En el segundo caso me parece que estás usando la librería no oficial del usuario de github. En este caso debes revisar que los parámetros del constructor coincidan con los que estamos usando, ya que yo uso la librería volley original.

      Puedes ver la definición del constructor aquí:https://github.com/mcxiaoke/android-volley/blob/master/src/main/java/com/android/volley/toolbox/JsonObjectRequest.java

    • Juan

      Ya solucione el problema de JSON, lo que sigo sin conocer aun es cual es el metodo getAdapterPosition() que se pide en MetaAdapter.

      Saludos

  • Juan

    En el activity DetailActivity.java se hace un llamado a CodigoInterraccion

    En donde esta o que tiene? porque en el zip que subiste tampoco esta

    • James Revelo

      Hola Juan.

      No te preocupes, esa es la clase Constantes que se encuentra en el paquete tools.

      Lo que pasa es que refactoricé y no corregí ese código. Ya mismo lo cambio, no te preocupes.

      • Juan

        He igual me pide el layout activity_detail… ese que layout vendria a ser?

        Gracias por la respuesta

        • James Revelo

          En ese lugar usa el activity_main.xml, para todas las actividades usé el mismo layout.

          Gracias por avisarme de estos problemas. Como el articulo es tan extenso se me olvidaron actualizar algunas cositas. :D

  • Sara

    Hola, gran articulo, me he enamorado de ti.
    Dame un hijo que tenga un celular android con carcasa de iphone

    • James Revelo

      ajjajajajaja ya cambie la imagen, no me hagas bullying por favor

  • lo descargue y me sale dos carpetas una en adroid y otro que esta todo php como lo integro para que funcione
    estoy interesado en prenderlo si por for una ayuda ….esta muy interesante y te deseo que te vaya de maravilla en todo…..

    • James Revelo

      Hola Pacheco.

      El proyecto Android solo lo abres desde android studio normal. El proyecto Php simplemente lo integras a la raíz de tu servidor para que estos funcionen. Obviamente instala XAMPP primero u otra herramienta similar. Saludos

  • Qué herramienta utilizastes para hacer los wireframes? Gracias.

    • James Revelo

      Hola Jose. Es Ninjamock

      • Jose Delgado

        Hola para las conexiones del workflow usa solo esa herramienta?, es una opción única con plan PRO? He dado clic por todas partes, ayuda por favor porque no me sale.

  • Muy buen articulo, la parte de pasar las urls por inyeccion sql es recomendable ? … se supone que la clase JsonObjectRequest tiene un parametro para pasar parametros por GET/POST pero no funciona, más si con StringRequest (http://stackoverflow.com/questions/30226026/problems-with-volley-passing-parameters).
    Sigue así compartiendo lo que sabes, ayudas mucho. Gracias.

    • James Revelo

      Gracias Jose.

      Pues yo envío los parámetros con un objeto JSONObject en el cuerpo de la petición y hasta donde veo ha funcionado.

  • Simplemente FABULOSO!! felicidades

    • James Revelo

      Gracias por tus palabras Alejandro :’D

  • JULIAN QUIROZ

    Muy completo, muchas gracias

    • James Revelo

      Gracias Julian. :D

      Cualquier error que veas me lo haces saber para mejorarlo.

  • Gon

    Faaaaaaa. me lo estab perdiendo por que los avisos de nuevos articulos me llegan al correo no deseado de google mierrrrrrrrrrrr!. Que tutorial hermano!! Por Dios!! Muchas gracias por todo lo que haces por la comunidad. Te mereces lo mejor! Ahora mismo estoy con unos problemitas en mi CursorAdapter. pero apenas lo resuelva le meto manos a este. Hermoso.
    Abrazo James.

    • James Revelo

      Gracias Gon :D

      No he podido responder tus correos, pero cuando me desocupe te ayudo.

      • Ferley Lara

        Buenas noches James. Gracias por compartir tu conocimiento. tengo el problema que cuando abro la aplicación sale un mensaje que dice Ha ocurrido un error. que Es el que está en obtener_metas.php . veo que si esta comunicándose con el servidor porque cambie el contenido del mensaje y lo mostró. como corregir este caso. gracias por tu atención.

        • Hola Ferley, creería que sucede porque aun no has cambiado bien los datos del server y la base de datos, fijate bien en si coinciden con todo.