Realizar Peticiones Http Con La Librería Volley En Android

Volley es una librería desarrollada por Google para optimizar el envío de peticiones Http desde las aplicaciones Android hacia servidores externos.

Este componente actúa como una interfaz de alto nivel, liberando al programador de la administración de hilos y procesos tediosos de parsing, para permitir publicar fácilmente resultados en el hilo principal.

En este artículo veremos la guía completa para implementar Volley en una aplicación Android de ejemplo llamada MySocialMedia.

La aplicación que desarrollaremos se compone de una lista de ítems referentes a posteos realizados en un blog imaginario de Social Media, donde se implementará un ListView.

Aplicación Android con un ListView poblado con ayuda de la librería Volley

Descargar Código Del Proyecto Final

Si deseas desbloquear el link de descarga del proyecto en Android Studio sigue estas instrucciones:

¿Qué es y para qué sirve la librería Volley?

Como se dijo al inicio del articulo Volley es un cliente Http creado para facilitar la comunicación de red en las aplicaciones Android. A diferencia de la interfaz HttpURLConnection que vimos anteriormente, Volley está totalmente enfocado en las peticiones, evitando la creación de código repetitivo para manejar tareas asíncronas por cada petición o incluso para parsear los datos que vienen del flujo externo.

Lee también Operaciones HTTP en Android con el cliente HttpURLConnection

Entre sus características mas potenciadoras podemos encontrar:

  • Procesamiento concurrente de peticiones.
  • Priorización de las peticiones, lo que permite definir la preponderancia de cada petición.
  • Cancelación de peticiones, evitando la presentación de resultados no deseados en el hilo principal.
  • Gestión automática de trabajos en segundo plano, dejando de lado la implementación manual de un framework de hilos.
  • Implementación de caché en disco y memoria.
  • Capacidad de personalización de las peticiones.
  • Provee información detallada del estado y flujo de trabajo de las peticiones en la consola de depuración.

Evitar usar Volley en descargas de datos pesados: Aunque Volley posee ventajas enormes para el procesamiento de peticiones, no significa que se debe usar en cada petición que hagamos. Esta librería tiene limitaciones con la descarga de información demasiada extensa, ya que su operación se basa en salvaguardas en cache, lo que haría lento el proceso con datos voluminosos.

Volley posee varios componentes que optimizan la administración de las peticiones generadas desde las aplicaciones Android. La gestión comienza en una Cola de Peticiones que recibe cada una de las peticiones generadas, donde son previamente priorizadas para su realización.

Luego son seleccionadas por un elemento llamado Cache Dispatcher, cuya función es comprobar si la respuesta de la petición actual puede ser obtenida de resultados previos guardados en caché. Si es así, entonces se pasa a parsear la respuesta almacenada y luego se presenta al hilo principal. En caso negativo, se envía la petición a la Cola de Conexiones Pendientes, donde reposan todas aquellas peticiones que están por ejecutarse.

Luego entra en juego un componente llamado Network Dispatcher, el cual se encarga de seleccionar las peticiones pendientes de la cola, para realizar las respectivas transacciones Http hacia el servidor. Si es necesario, las respuesta de estas peticiones se guardan en caché, luego se parsean y finalmente se publican en el hilo principal.

Aunque esta es una definición básica, es muy concisa y significativa para comprender el proceso que realiza Volley al surgir peticiones. A continuación se muestra una ilustración que resume el flujo:

Diagrama de Arquitectura lógica de la librería Volley para Android
La imagen muestra los pasos descritos y además establece una subdivisión por líneas punteadas. La primer parte representa el hilo de UI en la aplicación, la parte intermedia es un hilo de procesamiento en caché que Volley crea para la gestión de cache y la tercera parte representa uno o varios hilos referentes al pool de conexiones realizadas para las peticiones.

Esta implementación elimina la implementación de hilos manualmente con Tareas asíncronas como era el caso del uso del cliente HttpURLConnection.

1. Añadir la librería Volley al proyecto Android Studio

1.1 Clonar Repositorio Git y añadir JAR

El primer paso a realizar es incluir Volley en el proyecto. Una de las formas de hacerlo es clonar el repositorio con la herramienta Git y luego construir un archivo .jar con base en este. La idea es ubicar el archivo en la carpeta libs, la cual permite referenciar librerías.

Librería jar en la carpeta libs de Android Studio
Puedes generar el jar con la herramienta jar del JDK de Java. Puedes encontrar una guía aquí:

http://docs.oracle.com/javase/tutorial/deployment/jar/build.html

También puedes usar el sistema de construcción Ant de Apache para generarlo o incluso el mismo Gradle.
Cuando ya esté listo, solo agrega la siguiente línea a tu archivo build.gradle y tendrás acceso a la librería:

compile files('libs/volley.jar')

1.2 Usar versión no oficial de Volley desde Maven Central

Nuestro amigo MCXIOAKE (usuario de Github) ha creado un mirror del repositorio original para Maven Central. Lo que significa que podemos obtener el .jar a través de una regla de de construcción externa en build.gradle. Para ello solo agrega la siguiente línea de dependencia:

compile 'com.mcxiaoke.volley:library:1.0.+'

El autor asegura que esta copia está en constante sincronización con el repositorio original de Google, por lo que no deberíamos preocuparnos por su actualización.

1.3 Importar los archivos fuente de Volley como un módulo

Importar como módulo los archivos de Volley es otro camino que se puede seguir. En este caso tendremos más cercanos los archivos de composición.

Para importar el nuevo módulo dirígete a File > Import New Module… y selecciona la ubicación de tus archivos.

Importar un Modulo en Android Studio
Seleccionar Carpeta del Módulo en Android Studio
Con ese movimiento la carpeta volley se construirá una sola vez, por lo que no debemos preocuparnos de trabajo extra de compilación. Para construir el modulo debes añadir a build.gradle la siguiente línea (asumiendo que nombraste al modulo :volley):

compile project(':volley')

Cuando se le dicta a Android Studio que importe un nuevo módulo automáticamente se añade al archivo settings.gradle una referencia del nuevo módulo, indicando que debe construirse un nuevo segmento.

include ':app', ':volley'

También podemos solo copiar y pegar los archivos fuente en la carpeta src como si se tratasen de código interno y luego ejecutar la construcción sin ningún problema.

2. Añadir el permiso para conexiones en el Android Manifiest

Por obvias razones debemos solicitar a Android que le permita a nuestra aplicación conectarse a la web, para ello añades la etiqueta <uses-permission> referente:

<uses-permission android:name="android.permission.INTERNET"/>

3. Diseñar Interfaz de la Aplicación Android

3.1. Diseñar layout de la actividad principal

Debido a que solo usaremos una lista en la interfaz, añadiremos como nodo raíz un ListView al layout de la siguiente forma:

<ListView 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=".MainActivity"
    android:id="@+id/listView"/>

3.2 Diseñar aspecto de los ítems de la lista

El diseño se basa en un formato de fragmento enriquecido, donde se muestra una miniatura que presenta el post (ImageView), un encabezado (TextView Normal) y una pequeña metadescripción (TextView Small) asociada al contenido del artículo.
Veamos la definición del layout post.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="wrap_content"
    android:padding="10dp">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="150dp"
        android:id="@+id/imagenPost"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="false"
        android:scaleType="centerCrop" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Medium Text"
        android:id="@+id/textoTitulo"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@+id/imagenPost"
        android:textStyle="bold"
        android:layout_marginLeft="10dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="Medium Text"
        android:id="@+id/textoDescripcion"
        android:layout_below="@+id/textoTitulo"
        android:layout_alignLeft="@+id/textoTitulo" />
</RelativeLayout>

3.3 Crear Adaptador personalizado para el ListView

Este paso ya lo hemos realizado una gran cantidad de veces, pero sin embargo para la implementación de Volley tenemos que dar un trato especial a la fuente de datos, que en este caso se encuentra en la web.

Crearemos un adaptador que extienda de la clase ArrayAdapter llamado PostAdapter, donde por el momento se dejará expresado en comentarios las acciones que se relacionen con Volley, las cuales se completarán a medida que entendamos las funcionalidades lógicas.

El adaptador quedaría de la siguiente forma:

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


public class PostAdapter extends ArrayAdapter {

    // Atributos
    private String URL_BASE = "http://servidorexterno.site90.com/datos";
    private static final String TAG= "PostAdapter";
    List<Post> items;

    public PostAdapter(Context context) {
        super(context,0);

        // Gestionar petición del archivo JSON
    }

    @Override
    public int getCount() {
        return items != null ? items.size() : 0;
    }

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

        LayoutInflater layoutInflater= LayoutInflater.from(parent.getContext());

        //Salvando la referencia del View de la fila
        View listItemView = convertView;

        //Comprobando si el View no existe
        if (null == convertView) {
            //Si no existe, entonces inflarlo
            listItemView = layoutInflater.inflate(
                    R.layout.post,
                    parent,
                    false);

            // Procesar item

        return listItemView;
    }
}

Por el momento existen 3 atributos. El string URL_BASE contiene la ubicación base de los archivos que accederemos, lo que significa que se debe concatenar esta cadena con la dirección relativa del recurso. Se declaró una constante TAG cuyo fin es gestionar actividades de log. Y también se encuentra una lista con elementos de tipo Post, la cual es una clase que encapsula los datos de los elementos de la lista.

3.4 Entender el origen de los datos del adaptador

La aplicación MySocialMedia obtiene la información de cada post desde un servidor externo a través de un archivo con formato JSON llamado social_media.json. Este archivo contiene un array de objetos con los atributos: titulo, descripción e imagen. Todos los atributos son de tipo string como se ve en el siguiente resumen (Verás el código expandido pero en realidad está minimizado).

{
 "items":
 [
        {
            "titulo": "Difusión de contenidos",
            "descripcion": "Aprende nuestra metodología para generar un plan de marketing de contenidos",
            "imagen": "/img/social1.png"
        }, {
            "titulo": "La Magia de las Campañas dirigidas",
            "descripcion": "¿Tus ventas están por el suelo?, entonces este articulo es para ti",
            "imagen": "/img/social2.png"
        }, {
            "titulo": "Posicionar tu marca con contenido audiovisual",
            "descripcion": "El objetivo de esta guía es desvelarte los secretos para el posicionamiento con videos ilustrativos",
            "imagen": "/img/social3.png"
        }, {
            "titulo": "¿Como Crear un Plan de Email Marketing?",
            "descripcion": "Descubre la importancia de capturar la información de aquellos usuarios que pueden ser tus futuros clientes",
            "imagen": "/img/social4.png"
        }, 
            ...
 ]
}

Como ves, las imágenes se representan con la ruta relativa dentro del servidor. De esta forma podremos realizar una petición hacia su ubicación concatenando con la url base.

Con esto en mente creamos la clase Post:

public class Post {
    // Atributos
    private String titulo;
    private String descripcion;
    private String imagen;

    public Post() {
    }

    public Post(String titulo, String descripcion, String imagen) {
        this.titulo = titulo;
        this.descripcion = descripcion;
        this.imagen = imagen;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    public String getDescripcion() {
        return descripcion;
    }

    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }

    public String getImagen() {
        return imagen;
    }


    public void setImagen(String imagen) {
        this.imagen = imagen;
    }
}

Teniendo en cuenta esta estructura, se puede deducir que a través de Volley debemos realizar una petición para obtener el archivo JSON. Luego, basados en los objetos obtenidos desde ese archivo, se debe realizar una serie de peticiones para obtener cada imagen alojada en el servidor.

Adicionalmente debe parsearse la respuesta JSON y el flujo binario de cada imagen para que nuestra aplicación pueda comprender dicha información.

4. Crear una nueva Cola de Peticiones

El uso de Volley comienza con creación de una cola de peticiones. La representación lógica de este elemento es la clase RequestQueue. Este objeto se encarga de gestionar automáticamente el envió de las peticiones, la administración de los hilos, la creación de la caché y la publicación de resultados en la UI.

Este elemento será parte de nuestro adaptador, por lo que lo añadiremos como atributo. Para crear una nueva instancia usaremos el método estático newRequestQueue() de la clase Volley:

...
private RequestQueue requestQueue;
...
// Crear nueva cola de peticiones
requestQueue= Volley.newRequestQueue(context);

Con esta declaración ya se tiene el camino abierto para añadir peticiones.

5. Generar y Añadir peticiones

Para realizar peticiones en Volley podemos acudir a ciertos tipos que ya están estandarizados para uso frecuente. Esto quiere decir que el desarrollador no debe tomarse tiempo en estructurar la petición, añadiendo hilos y generando procesos de parsing extenso.

Existen cuatro tipos de peticiones estándar:

  1. StringRequest: Este es el tipo más común, ya que permite solicitar un recurso con formato de texto plano, como son los documentos HTML.
  2. ImageRequest: Como su nombre lo indica, permite obtener un recurso gráfico alojado en un servidor externo.
  3. JsonObjectRequest: Obtiene una respuesta de tipo JSONObject a partir de un recurso con este formato.
  4. JsonArrayRequest: Obtiene como respuesta un objeto del tipo JSONArray a partir de un formato JSON.

5.1 Realizar petición para un archivo JSON

La primera petición que haremos será del tipo JsonObjectRequest, ya que tenemos un objeto JSON con un atributo llamado “ítems” de tipo array. El nombre del archivo es social_media.json.

Veamos la implementación:

// Nueva petición JSONObject
jsArrayRequest = new JsonObjectRequest(
        Request.Method.GET,
        URL_BASE + URL_JSON,
        null,
        new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                items = parseJson(response);
                notifyDataSetChanged();
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.d(TAG, "Error Respuesta en JSON: " + error.getMessage());

            }
        }
);

El primer parámetro del constructor es el método empleado en la petición. Como ves se usa la interfaz Method perteneciente a la clase Response, la cual contiene los métodos necesarios, como en este caso, donde se usa GET. Luego sigue la URL del recurso JSON, la cual se compone de dos strings concatenadas. El tercer parámetro son los pares clave-valor si se fuese a realizar una petición POST.

El cuarto parámetro es una clase anónima del tipo Response.Listener para definir una escucha que maneje los resultados de la petición.
Se debe sobrescribir el método onResponse() para codificar las acciones con la respuesta en el hilo de UI, en este caso parseamos el contenido del JSONObject y se le asigna el resultado al atributo ítems de nuestro adaptador.

Adicionalmente se le indica al adaptador que actualice su contenido con notifyDataSetChanged(), ya que no sabremos en qué momento la petición terminará exitosamente.

El quinto parámetro es una escucha que maneja los errores ocurridos en la transacción Http. Para ello se usa la clase Response.ErrorListener, la cual requiere dictaminar las acciones en su método onErrorResponse(). En este caso usamos el método estático d() de la clase Log para registrar en consola que ocurrió un error.

Finalmente usamos el método add() para añadir la petición a la cola de peticiones:

// Añadir petición a la cola
requestQueue.add(jsArrayRequest);

5.2 Realizar petición para las imágenes

En este situación usaremos el tipo ImageRequest para obtener cada imagen. Solo necesitamos concatenar la url absoluta que fue declarada como atributo, más la dirección relativa que cada imagen trae consigo en el objeto JSON:

// Petición para obtener la imagen
ImageRequest request = new ImageRequest(
        URL_BASE + item.getImagen(),
        new Response.Listener() {
            @Override
            public void onResponse(Bitmap bitmap) {
                Log.d(TAG, "ImageRequest completa");
                imagenPost.setImageBitmap(bitmap);
            }
        }, 0, 0, null,
        new Response.ErrorListener() {
            public void onErrorResponse(VolleyError error) {
                imagenPost.setImageResource(android.R.drawable.ic_lock_power_off);
                Log.d(TAG, "Error en ImageRequest");
            }
        });

// Añadir petición a la cola
requestQueue.add(request);

El código anterior es similar a la primera petición solo que esta vez usamos una escucha de tipo Listener<Bitmap>, la cual asignamos al ImageView del ítem procesado actualmente. En la escucha del error se asigna un drawable cuyo contenido representa visualmente un error de carga. Esto mostrará que las cosas no salieron como lo esperábamos.

El tercer, cuarto y quinto parámetros hacen referencia a una configuración adicional para la descarga de la imagen, pero ese tema no es necesario abordarlo debido al alcance de este artículo.

Como puedes notar, las peticiones con Volley no requieren el uso de tareas asíncronas, tampoco se necesita especificar que los resultados se deben publicar en el hilo UI y mucho menos parsear los flujos de información. La escucha Listener nos proporciona en onResponse() la respuesta completamente lista para usarse, ya sea un objeto JSONArray, un String o un Bitmap.

5.3 Acoplar peticiones Volley en el Adaptador personalizado

Una vez entendidas y creadas las peticiones, podemos completar nuestro adaptador PostAdapter de la siguiente manera, veamos:

import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;


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

import java.util.ArrayList;
import java.util.List;


public class PostAdapter extends ArrayAdapter {

    // Atributos
    private RequestQueue requestQueue;
    JsonObjectRequest jsArrayRequest;
    private static final String URL_BASE = "http://servidorexterno.site90.com/datos";
    private static final String URL_JSON = "/social_media.json";
    private static final String TAG = "PostAdapter";
    List<Post> items;

    public PostAdapter(Context context) {
        super(context,0);

        // Crear nueva cola de peticiones
        requestQueue= Volley.newRequestQueue(context);

        // Nueva petición JSONObject
        jsArrayRequest = new JsonObjectRequest(
                Request.Method.GET,
                URL_BASE + URL_JSON,
                null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        items = parseJson(response);
                        notifyDataSetChanged();
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d(TAG, "Error Respuesta en JSON: " + error.getMessage());

                    }
                }
        );

        // Añadir petición a la cola
        requestQueue.add(jsArrayRequest);
    }

    @Override
    public int getCount() {
        return items != null ? items.size() : 0;
    }

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

        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());

        // Referencia del view procesado
        View listItemView;

        //Comprobando si el View no existe
        listItemView = null == convertView ? layoutInflater.inflate(
                R.layout.post,
                parent,
                false) : convertView;


        // Obtener el item actual
        Post item = items.get(position);

        // Obtener Views
        TextView textoTitulo = (TextView) listItemView.
                findViewById(R.id.textoTitulo);
        TextView textoDescripcion = (TextView) listItemView.
                findViewById(R.id.textoDescripcion);
        final ImageView imagenPost = (ImageView) listItemView.
                findViewById(R.id.imagenPost);

        // Actualizar los Views
        textoTitulo.setText(item.getTitulo());
        textoDescripcion.setText(item.getDescripcion());

        // Petición para obtener la imagen
        ImageRequest request = new ImageRequest(
                URL_BASE + item.getImagen(),
                new Response.Listener<Bitmap>() {
                    @Override
                    public void onResponse(Bitmap bitmap) {
                        imagenPost.setImageBitmap(bitmap);
                    }
                }, 0, 0, null,null,
                new Response.ErrorListener() {
                    public void onErrorResponse(VolleyError error) {
                        imagenPost.setImageResource(R.drawable.error);
                        Log.d(TAG, "Error en respuesta Bitmap: "+ error.getMessage());
                    }
                });

        // Añadir petición a la cola
        requestQueue.add(request);


        return listItemView;
    }

    public List<Post> parseJson(JSONObject jsonObject){
        // Variables locales
        List<Post> posts = new ArrayList();
        JSONArray jsonArray= null;

        try {
            // Obtener el array del objeto
            jsonArray = jsonObject.getJSONArray("items");

            for(int i=0; i<jsonArray.length(); i++){

                try {
                    JSONObject objeto= jsonArray.getJSONObject(i);

                    Post post = new Post(
                            objeto.getString("titulo"),
                            objeto.getString("descripcion"),
                            objeto.getString("imagen"));


                    posts.add(post);

                } catch (JSONException e) {
                    Log.e(TAG, "Error de parsing: "+ e.getMessage());
                }
            }

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


        return posts;
    }
}

5.4 Cancelar una petición

Si deseas evitar que una petición iniciada no publique su respuesta en el hilo principal usa el método cancel() del objeto Request, el cual cancela la visualización de los resultados obtenidos:

requestEjemplo.cancel();

También es posible cancelar un grupo de peticiones asociadas a una etiqueta impuesta. Para ello primero debes asignar la etiqueta a las peticiones elegidas con el método setTag(), el cual recibe un String que representa la etiqueta:

public static final String TAG = "Petición extenuante";
...

// Añadir etiqueta
requestEjemplo.setTag(TAG);
...

Y luego puedes usar el método cancelAll() de la cola de peticiones:

// Cancelar todas aquellas peticiones con dicha etiqueta.
if (mRequestQueue != null){
 requestQueque.cancelAll(TAG);
}

Con eso evitarás que todas las peticiones clasificadas por ese tipo publiquen resultados en la interfaz.

6. Crear Adaptador y asociarlo a la lista

Con toda la maquinaria creada es hora de poner en marcha nuestra aplicación, para ello crearemos una nueva instancia de PostAdapter y luego lo asociaremos a la instancia del ListView que tenemos en el layout de la actividad principal.

El código quedaría de la siguiente forma:

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;




public class MainActivity extends ActionBarActivity {

    // Atributos
    ListView listView;
    ArrayAdapter adapter;

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

        // Obtener instancia de la lista
        listView= (ListView) findViewById(R.id.listView);

        // Crear y setear adaptador
        adapter = new PostAdapter(this);
        listView.setAdapter(adapter);

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Al correr la aplicación tendrías el siguiente resultado:

Captura de una aplicación construida con Volley

7. Características adicionales de Volley

7.1 Creación de patrón Singleton para alcance global dentro de la aplicación

MySocialMedia es una aplicación que usa las funcionalidades de Volley en solo una localidad, pero normalmente una aplicación basada en servicios web necesita realizar peticiones a lo largo de todas sus actividades y componentes. Esta situación haría que muchos programadores declararan colas de peticiones por todos lados o incluso pasar como parámetro la cola entre clases, lo cual llega  a ser repetitivo, poco eficiente y confuso.

Para desmontar este complejo enfoque, Google recomienda crear un Patrón Singleton que encapsule las funcionalidades necesarias de Volley. Recuerda que este patrón se caracteriza por limitar el alcance de la clase a un solo objeto, es decir, solo puede existir un solo objeto controlador que represente la existencia de la clase, restringiendo la instanciación de nuevos elementos.

Con esta solución, el singleton será omnipresente en todo el proyecto Android y se podrán usar las funcionalidades en cualquier lugar. Básicamente esta clase debe contener como atributo la cola de peticiones y el contexto de la aplicación (ojo, no el contexto de la actividad, ya que es necesario establecer independencia de la interfaz, por si en algún momento existe un cambio de configuración, como la rotación de pantalla).

Veamos la definición que se usaría para el proyecto actual:

import android.content.Context;
import android.graphics.Bitmap;
import android.util.LruCache;

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



public final class MySocialMediaSingleton {
    // Atributos
    private static MySocialMediaSingleton singleton;
    private RequestQueue requestQueue;
    private static Context context;

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

    public static synchronized MySocialMediaSingleton getInstance(Context context) {
        if (singleton == null) {
            singleton = new MySocialMediaSingleton(context);
        }
        return singleton;
    }

    public RequestQueue getRequestQueue() {
        if (requestQueue == null) {
            requestQueue = Volley.newRequestQueue(context.getApplicationContext());
        }
        return requestQueue;
    }

    public  void addToRequestQueue(Request req) {
        getRequestQueue().add(req);
    }

}

Como ves la clase está declarada como final para evitar la sobrescritura de métodos. Contiene un objeto reflejo singleton del mismo tipo para representar la única instancia de MySocialMediaSingleton. En cuanto al comportamiento, se crearon los métodos getInstance(), getRequestQueue() y addToRequestQueue().

getInstance() simplemente asigna memoria a la única instancia del singleton, donde se llama al constructor privado de la clase. Este método debe tener la propiedad synchronized, ya que la instancia será accedida desde varios hilos por lo que es necesario evitar bloqueos de acceso. El método getRequestQueue() obtiene la instancia de la cola de peticiones que se usará a través de toda la aplicación.

Para agregar un nueva petición debes acceder al método addToRequestQueue() una vez la instancia esté creada. Importantísimo aclarar que el parámetro del método getInstance() debe ser el contexto de la aplicación, esto protege la existencia de la cola de peticiones de cualquier afectación que sufra la actividad u otro componentes donde sea utilizado el singleton.

Veamos como añadir la petición JsonArrayRequest del adaptador a la cola:

...
JsonArrayRequest jsArrayRequest = new JsonArrayRequest(
        url + "/social_media.json",
        new Response.Listener<JSONArray>() {
            @Override
            public void onResponse(JSONArray response) {
                Log.d(TAG, "Respuesta Volley:" + response.toString());
                items = parseJson(response);
                notifyDataSetChanged();
            }


        },
        new Response.ErrorListener() {

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

            }
        });

// Añadir petición a la cola
<strong>MySocialMediaSingleton.getInstance(getContext()).addToRequestQueue(jsArrayRequest);</strong>
...

7.2 Optimizar la carga de múltiples imágenes con ImageLoader

La clase ImageLoader es un componente de Volley que agiliza la carga de múltiples imágenes provenientes de diferentes URL. El objetivo principal es eliminar el parpadeo que pueda llegar a producirse debido a la ubicación de la cache en disco. Esto se debe a que ImageLoader crea una caché especial en memoria para el proceso de descarga.

Para usar este potencializador, primero debemos incluir una instancia en nuestro Singleton.

...
private ImageLoader imageLoader;

private MySocialMediaSingleton(Context context) {
    ...

    imageLoader = new ImageLoader(requestQueue,
            new ImageLoader.ImageCache() {
                private final LruCache<String, Bitmap>
                        cache = new LruCache<String, Bitmap>(20);

                @Override
                public Bitmap getBitmap(String url) {
                    return cache.get(url);
                }

                @Override
                public void putBitmap(String url, Bitmap bitmap) {
                    cache.put(url, bitmap);
                }
            });
}
...

public ImageLoader getImageLoader() {
    return imageLoader;
}

Cuando se instancia el ImageLoader, se debe asociar la cola de peticiones correspondiente y un objeto del tipo ImageCache. Esta clase representa el acceso a la cache creada en memoria para almacenar las respuestas de las peticiones. Solo se debe sobrescribir anónimamente los métodos getBitmap() y putBitmap() basándose en un atributo del tipo LruCache.

La clase LruCache clase representa una cache gestionada por un algoritmo que prioriza a los elementos más usados. Cuando se instancia se especifica en sus parámetros de plantilla la relación entre pares String-Bitmap para referirse a las imágenes externas que se almacenaran en la porción de memoria. El parámetro entero que recibe en el constructor es el tamaño de la cache en unidades preestablecidas.

Puedes crear tu propia extensión de LruCacheimplementando la interfaz ImageCache, para personalizar la construcción de las unidades de memoria que se usarán en gestión de la caché.

El siguiente paso es llamar al método get() de ImageLoader en vez de usar una petición ImageRequest cada vez que deseemos obtener una imagen desde alguna URL.

Veamos cómo hacerlo para la imagen que se obtiene en el método getView() de PostAdapter:

// Obtener el image loader
ImageLoader imageLoader= MySocialMediaSingleton.getInstance(getContext()).getImageLoader();
// Petición
imageLoader.get(URL_BASE + item.getImagen(), ImageLoader.getImageListener(imagenPost,
         R.drawable.loading, R.drawable.error));

El primer paso fue obtener la instancia del Image Loader a través del Singleton con el método getImageLoader(). Con ese resultado fue posible llamar al método get(), el cual recibe tan solo dos parámetros: la URL de la imagen y un ImageListener. Este último objeto mencionado es una escucha que se construye a través del método estático getImageListener().

El método getImageListener() recibe la instancia del ImageView donde se almacenará la imagen, también recibe un drawable para visualizar un proceso de carga en caso que la petición demore un poco y otro drawable para representar un error en la respuesta de la petición.

7.3 Complementar a ImageLoader con NetworkImageView

Adicionalmente podemos incorporar el componente NetworkImageView para que trabaje en conjunto con el ImageLoader. Este view se puede usar en reemplazo del típico ImageView. La ventaja está en que el contenido de la imagen está directamente relacionado con la petición URL, lo que permite un manejo optimizado en la red y posibilitar cancelar la visualización en cualquier momento.

Para implementar el NetworkImageView en la definición XML, solo debes añadir la etiqueta com.android.volley.toolbox.NetworkImageView, veamos:

<com.android.volley.toolbox.NetworkImageView
        android:layout_width="100dp"
        android:layout_height="150dp"
        android:id="@+id/imagenPost"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="false"
        android:scaleType="centerCrop" />

Lo siguiente es obtener la instancia del NetworkImageView y la del ImageLoader. Con esas referencias se llama al método setImageUrl() del view, el cual recibe la URL de la imagen y la instancia del ImageLoader. Con esa combinación la imagen se infla cuando sea necesario.

// Petición el image loader
ImageLoader imageLoader = MySocialMediaSingleton.getInstance(getContext()).getImageLoader();
// Petición
imagenPost.setImageUrl(URL_BASE +item.getImagen(), imageLoader);

Esta convención reduce en gran cantidad la codificación para la realizar una petición de la imagen.

7.4 ¿Cómo usar el método POST en la librería Volley?

Hasta ahora hemos visto cómo usar el método GET implícitamente a través de dos peticiones del tipo JsonArrayRequest e ImageRequest. Ambas se ejecutan con GET ya que están creadas solo para este método. En cambio las peticiones StringRequest y JsonObjectRequest si permiten usar el método POST debido a la simplicidad del formato de los datos.

A continuación se muestra un ejemplo de envío de los datos de un post hacia el servidor externo:

// Mapeo de los pares clave-valor
HashMap<String, String> parametros = new HashMap();
parametros.put("titulo", "Nuevo Post");
parametros.put("descripcion", "Nuevo Titulo");
parametros.put("imagen", "/img/nuevo_post.png");

JsonObjectRequest jsArrayRequest = new JsonObjectRequest(
        Request.Method.POST,
        URL_BASE + URL_COMPLEMENTO,
        new JSONObject(parametros),
        new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                // Manejo de la respuesta
                notifyDataSetChanged();
            }
        },
        new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                // Manejo de errores

            }
        });

Como ves el constructor de la petición recibe como primer parámetro el atributo POST de la interfaz Method. Los demás parámetros se configuran con las acciones que se desean aplicar en la respuesta y en la ocurrencia de errores. Ahora, para enviar los pares clave-valor en la petición existen dos caminos.

El primero fue usado en el anterior código, donde se creó un mapa para generar los pares que se enviarán. Este mapa se usa en el constructor de un objeto JSONObject que representa el cuerpo de la petición post.

La segunda forma consiste en crear una definición anónima en la instanciación de la petición, de tal manera que se sobrescriba el método getParams() para que retorne en los pares que deseamos.

JsonObjectRequest jsArrayRequest = new JsonObjectRequest(
        <strong>Request.Method.POST</strong>,
        URL_BASE + URL_COMPLEMENTO,
        <strong>null</strong>,
        new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                // Manejo de la respuesta
                notifyDataSetChanged();
            }
        },
        new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                // Manejo de errores

            }
        })<strong>{
            @Override
            protected HashMap<String, String> getParams() {
                // Mapeo de los pares clave-valor
                HashMap<String, String> parametros = new HashMap<>();
                parametros.put("titulo", "Nuevo Post");
                parametros.put("descripcion", "Nuevo Titulo");
                parametros.put("imagen", "/img/nuevo_post.png");

                return parametros;
            }
        };</strong>

Este método retorna en un mapeado de aquellos pares que se enviarán hacia el servidor. En este caso se publican los atributos titulo, descripción e imagen con sus respectivos valores.

7.5 Crear peticiones personalizadas en Volley

Ya sabemos que podemos realizar cuatro tipos de peticiones previamente establecidos, pero en ocasiones el tipo de respuesta que deseamos no estará acorde a estas peticiones estándar. En estos casos debemos recurrir a crear peticiones personalizas con el framework de Volley que se acomoden a las necesidades presentadas.

Para ello debemos extender la clase Request<T>, la cual es una plantilla que requiere un tipo T referente a la respuesta que tendrá la petición. Por ejemplo, ImageRequest se extiende de la implementación Request<Bitmap>, debido a que después del parsing se obtiene una respuesta Bitmap.

La creación de una petición personalizada requiere que se sobrescriban los métodos abstractos parseNetworkResponse() y devilerResponse(), donde el primero debe contener la lógica de parsing del flujo de datos que se obtuvo de la petición y el segundo publica los resultados en el hilo UI. Es un mecanismo similar a los métodos doInBackground() y onPostExecute() de la clase AsyncTask.

Lee también Uso de Hilos y tareas asíncronas(AsyncTask) en Android

7.5.1 Crear una petición personalizada Volley con la librería Gson

¿Recuerdas la librería Gson?, espero que sí, ya que la usaremos para parsear de forma más cómoda los datos JSON de MySocialMedia. La idea es que de la petición Gson se obtengan como respuesta una lista de objetos Post listos para asignar.

Lee También Parsear datos JSON con la la librería GSON

El primer paso a realizar es crear una nueva clase que encapsule la lista de elementos Post que teníamos como atributo, ya que las colecciones de datos en la serialización Gson presenta inconvenientes por su definición de tipos. Así que encapsularemos la lista a manera de truco de la siguiente forma:

import java.util.List;

public class ListaPost {
    // Encapsulamiento de Posts
    private List<Post> items;

    public ListaPost(List<Post> items) {
        this.items = items;
    }

    public void setItems(List<Post> items) {
        this.items = items;
    }

    public List<Post> getItems() {
        return items;
    }
}

Crear la clase GsonRequest

El siguiente movimiento es crear la petición GsonRequest. Su objetivo es recibir cualquier tipo de objeto predefinido, en el cual se realizará un reflejo de parsing con los datos de un formato JSON. La siguiente composición del código es referencia de la documentación oficial de Android para la creación de peticiones personalizadas.

Veamos:

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.toolbox.HttpHeaderParser;

import java.io.UnsupportedEncodingException;
import java.util.Map;


public class GsonRequest<T> extends Request<T> {
 // Atributos
    private final Gson gson = new Gson();
    private final Class<T> clazz;
    private final Map<String, String> headers;
    private final Listener<T> listener;

    /**
     * Se predefine para el uso de peticiones GET
     */
    public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
            Listener<T> listener, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        this.clazz = clazz;
        this.headers = headers;
        this.listener = listener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers != null ? headers : super.getHeaders();
    }

    @Override
    protected void deliverResponse(T response) {
        listener.onResponse(response);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(
                    gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
}

Como ves, esta clase se construye en función de un tipo genérico T. Dentro de los atributos tenemos un objeto Gson para la implementación de la librería, un objeto clazz, el cual representa la clase del objeto en que se depositará la respuesta parseada. Un mapeado clave-valor para los headers de la petición (si es que usarás) y una escucha asociada al tipo T.

En el constructor solo se realizan las asignaciones correspondientes y se preconfigura la petición para que use el método GET. Debido a que la petición GsonRequest puede implementa headers, entonces se sobrescribe el método getHeaders() para su retorno.

Luego se sobrescribe deliverReponse(), donde es enviada la respuesta obtenida del parsing hacia el método onResponse().

En parseNetworkResponse() es donde se encuentra la lógica de parsing del flujo de datos. Recuerda que la petición realizada siempre trae los datos en forma de Stream, al cual se le debe dar un formato que entienda Java. En este caso se crea una cadena llamada json para asignar los datos de la respuesta con el atributo data. Además de eso añadimos al constructor la forma de codificación del flujo con parseCharset().

Este método estático recibe las cabeceras de la respuesta que envió el servidor y nos informa si es UTF-8, UTF-16, etc.

Luego se retorna el resultado del método success(), el cual entrega un resultado parseado y asociado a una caché. El primer parámetro es el resultado de fromJson(), donde se refleja el formato JSON del servidor en objetos del tipo clazz. El segundo usa el método estático parseCacheHeaders() para registrar una entrada de caché para la respuesta.

Instanciar la petición GsonRequest en el Adaptador

Finalmente y ya para terminar, vamos a reemplazar la petición JsonObjectRequest que teníamos por la nueva petición:

...
ListaPost items;

...
// Añadir petición GSON a la cola
MySocialMediaSingleton.getInstance(getContext()).addToRequestQueue(
        new GsonRequest<ListaPost>(
                URL_BASE + URL_JSON,
                ListaPost.class,
                null,
                new Response.Listener<ListaPost>(){
                    @Override
                    public void onResponse(ListaPost response) {
                        items = response;
                        notifyDataSetChanged();
                    }
                },
                new Response.ErrorListener(){
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d(TAG, "Error Volley:"+ error.getMessage());
                    }
                }
        )

);

 

Conclusiones

Hemos visto gran cantidad de potencialidades que tiene la librería  Volley para ejecutar peticiones claras, cortas y rápidas. Es obvio que el alto nivel de su implementación la hace mejor opción que el uso de clientes como el HttpURLConnection.

También comprobamos que existen varias capacidades adaptativas de Volley para la personalización que se desee. Es cuestión de comprender la arquitectura de la librería para explotar sus mayores beneficios y posibilidades.

  • Pingback: ANDROID – Apple Garage()

  • Ramés Danilo

    Buenas Señor

    James Revelo Urrea gracias por esta pagina es de mucha ayuda para los que queremos aprender, si me permite hacerle 2 consultas, quiero pasar parametros a otra actividad con
    onItemClick y como colocar una imagen por si las del internet no hay.
    Gracias de antemano.

  • Ramés Danilo

    Buenas Señor

    James Revelo Urrea gracias por esta pagina es de mucha ayuda para los que queremos aprender, si me permite hacerle 2 consultas, quiero pasar parametros a otra actividad con
    onItemClick y como colocar una imagen por si las del internet no hay.
    Gracias de antemano.

  • Gabriel B.

    Hola James.
    Tengo problemas con pasar parametros en una peticion GET. Alguna sugerencia de como hacerlo?
    Saludos

  • fernanda otalora

    Muchas Gracias, fue de mucha ayuda el articulo, tengo una pregunta un Web Service WSDL se puede consumir con Volley.

  • Hernandez Estrambotico

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

  • Hernandez Estrambotico

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

  • Enrique Flores

    Hola! una pregunta.. si uso picasso me ahorro de escribir esas lineas relacionadas a ImageLoader?

  • Braian Coronel

    EXCELENTE TUTOTIAL. SOS EL MESSI DE LA PROGRAMACION.
    Pero me gustaría que les contesten a todos, todas las preguntas y que den un poco más la cara así en algún momento los remuneramos como agradecimiento de toda esta hermosa programación. Saludos

  • Braian Coronel

    ¿Cómo puedo hacer esto en vez de en un listView en un recyclerView? Gracias

  • Rose Hernandez

    hola que tal?
    al aparecer esto ya esta algo des actualizado, aun así me interesa saber volley es compatible
    para ODBC y Microsoft SQL server R2 2008?
    y si se puede hacer eso como podria implementarle :)

    Gracias!

  • Klenzb

    EXCELENTE TUTORIAL solo tengo una pregunta por que mi consulta funcina bien en Wifi pero en 3g no hace nada, la consulta la hago a un servidor web si alguien puede ayudarme se los agradeceria

  • Gabriel Ortega Rosero

    Excelentes tutoriales. Mil gracias!

  • Sebastian M

    la clase R no la identificaba ahora hice import android.R; y ahora no identifica la parte de activity_main en R.layout.activity_main =( q podra ser?

  • Rafelcf

    ¿Como se haria si empezase con un array?

    [{“items”:
    [{“titulo”:”Difusión de contenidos”,
    “descripcion”:”Aprende nuestra metodología para generar un plan de marketing de contenidos”,
    “imagen”:”/img/social1.png”},
    {“titulo”:”Generar ingresos pasivos con segmentación extrema”,
    “descripcion”:”Monetiza tus talentos y habilidades con la mejor orientación del público que te sigue”,
    “imagen”:”/img/social8.png”}]}]

  • 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

  • Matias

    Hola
    quisiera saber como hacer una precarga (processdialog) del listado sin necesidad de utilizar asynctask. Gracias

  • Matias

    Buen día,
    resulta que debo pasar los datos del listado de noticias, que en mi caso es de productos, a otra activity – Es decir que tengo un listado de productos, clic en reservar > muestra más info en otra activity.

    El problema es que no puedo hace funcionar este activity con las variables, si en modo estático (es decir que el intent funciona ok), pero no cuando quiero tomar un dato del activity del listado ( DetalleProducto.putExtra(“tituloPool”,titulo_extra.getText()); ).

    Espero haberme explicado, te dejo una parte del código:

    // Atributos

    ListView listView;

    ArrayAdapter adapter;

    private TextView titulo_extra , descrip_extra;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    titulo_extra = (TextView)findViewById(R.id.textoTitulo);

    descrip_extra = (TextView)findViewById(R.id.textoDescripcion);

    // Obtener instancia de la lista

    listView= (ListView) findViewById(R.id.listView);

    // Crear adaptador

    adapter = new PostAdapter(this);

    listView.setAdapter(adapter);

    }

    public void onReservar(View v){

    Intent ReservaProducto = new Intent(getApplicationContext(),FormReservaActivity.class);

    //ReservaProducto.putExtra(“nroPool”,”1254″);

    ReservaProducto.putExtra(“nroVta”,titulo_extra.getText());

    ReservaProducto.putExtra(“tituloVta”,”Este es el ttulo de la ve”);

    startActivity(ReservaProducto);

    }

  • Marcelo

    Hola, en mi caso necesito realizar varios request (un total de 12) pero en orden, es necesario que termine el primero para seguir con el siguientes. Por el momento puedo llamar el siguiente request en el método onResponse() del Response.Listener.
    Como veras no es una solución que me guste mucho, pero funciona correctamente.
    Mi pregunta es si esto ¿se puede hacer de alguna otra manera?

  • Eduardo Arg

    Buenas estoy siguiendo el ejemplo y me funciona todo correctamente menos una cosa. Cuando descargo el codigo y lo importo como proyecto en android studio y despues lo ejecuto me muestra correctamente el texto en los textviews. Pero cuando realizo el ejemplo desde cero y lo ejecuto con el mismo codigo en el textview me muestra caracteres extraños como los signos de preguntas y los acentos no se muestran bien y las ñ. Se que es un problema de codificación pero nose porque ocurre esto si es exactamente el mismo codigo. Nose si es una configuracion extra en android studio. Eh cambiado la configuracion en settings en project encoding a windows-1252 y sigue mostrando el texto con los caracteres extraños. Se que la codificacion del json esta en windows-1252 pero nose porque funciona con el ejemplo descargado y cuando lo hago yo no me muestra bien. Espero puedan ayudarme.

    Saludos.

    Gracias por el aporte.

  • Alejandro López Cardo

    Muy buen tutorial, gracias a el he aprendido a usar más o menos la librerai volley ^^
    Ahora me surge una duda, como puedo hacer para obtener el objeto del item pulsado de la lista? Es decir quiero que al pulsar un elemento de la lista abrir un fragment/activity con una vista más detallada del elemento, entonces me gustaria saber como puedo acceder a los datos de ese elemento para pasarlos por un intent. (O algun metodo más efectivo(¿))

    Un saludo y muchas gracias!

  • DINA REYES

    Excelente tutorial solo una pregunta porque las imagenes se reinician cuando muevo el scroll

  • Hola , con el patron singleton y todo lo que optimiza , no me queda claro en que parte le dices que jale los datos del objet items ???

    • Hola Kenny, no se si te refieras al parsing. Esta justo en la sobrescritura del método onResponse(), donde llamo al método parseJson() que se encarga de transformar la respuesta en elementos.

  • Camila

    Hola James , queria saber si me podes ayudar o alguien de tus seguidores, como hago con volley para parsear un objeto embebido, en este caso tamaño , por ejemplo: {

    “items”: [

    {

    “tamaño”: {

    “alto”: 100,

    “ancho”: 200

    },

    “nombre”: pepe,

    “apellido”: “gonzalez”,

    “ciudad”: santa rosa,

    “pais”: “Argentina”

    },

    { “tamaño”: {

    “alto”: 100,

    “ancho”: 200

    },

    “nombre”: pepe,

    “apellido”: “gonzalez”,

    “ciudad”: santa rosa,

    “pais”: “Argentina”

    }
    ]}

  • Matias

    Buenos días,
    no logro que al hacer clic en alguno de los items del listView genere un Toast con un mensaje, les paso una captura de pantalla para ver si me pueden ayudar.
    Saludos!

  • Rafelcf

    como puedo hacer esto si estoy en un Fragment?

    // Crear y setear adaptador
    adapter = new PostAdapter(this);
    listView.setAdapter(adapter);

    • de la misma forma jejeje , quiza pregustas por lo de inflar el layout , mira esto

  • Matias

    Hola. Hay algun tutorial que explique como enviar post a un servidor web? Trabajo.con java y glassfish, tambien.con apache tomcat.

    • Billy Larru

      Usa la aplicación de Google llamada Postman, puedes enviar cualquier tipo de petición y recibir la respuesta en ese mismo programa. Es bastante útil te lo recomiendo.

      • Matias

        Gracias Billy, lo voy a ver. Saludos

  • Salvador Anwart

    ya no se encuentra el código, alguien podría subirlo? de antemano gracias

  • Juan Valdemar Colichon Ramirez

    Si deseo que al hacer click en la imagen pueda enviarme a otra actividad, donde pueda mostrar descripción y mucho mas, actualmente estoy trabajando con la primera versión del tutorial.

    • Itzel RM

      Lo lograste? Quiero hacer lo mismo que mencionas.

    • AnónimoGT

      ¿Probaste definir tu imagen como un ImageButton?
      Luego con Intent hacés las llamadas hacia las nuevas actividades, es lo que se me ocurre en este momento.

    • Matias

      Hola Juan, lo lograste? estoy realizando pruebas con un Intent y me resulta imposible, se detiene la app.

    • Braian Coronel

      Lo que te permite hacer click en los items es un recyclerView pero estoy viendo como llegar a eso jaja

  • Rodolfo morales

    Excelente tutorial muchas gracias por el aporte, me sirvio demasiado, se ve cuando las personas son bastante profesionales. solo que me quedo la duda del porque lo podemos utilizar desde el repositorio central de maven, tambien recomiendo este curso de android http://www.grupocodesi.com/cursos/curso-de-android.html para personas que van iniciando.

  • Juan Carlos Suarez

    Que tal una pregunta, como podria modificar el codigo para guardar imagenes en mi servidor, ya tengo el codigo para tomar fotos desde mi app y/o seleccionarlas desde la galeria, pero el como enviar esa imagen a mi servidor no se me ocurre, ayuda man!

  • Marcos Melgosa Vázquez

    Hola James, tus tutoriales es han sido la base para mi aprendizaje, y de hecho, gracias a tus tutoriales y a otros más es que ahora me dedico a esto para ganarme la vida. Muchas gracias por ello.
    Y aparte quiero preguntarte, mencionas que para el método POST, solo son funcionales los métodos StringRequest y JsonObjectRequest, entonces es mi pregunta es: Si quiero setear parámetros, como se hace en los métodos antes mencionados, no lo puedo hacer con el método GET? O si puedo? Eso no me quedo muy claro…
    En síntesis, quiero enviar múltiples datos del tipo clave-valor y se que voy a recibir un arreglo del tipo Jason. ..Qué debo usar para hacer esto?
    Saludos

    • adrian

      Marcos pudiste encaminar esta duda? Yo intente realizar tanto con GET como con POST peticiones pero no logro que sea exitosa….recibo un onErrorResponse

  • tael

    Hola y gracias por el post. He realizado un ejemplo con un JSON y funciona pero tengo un problema y es que el JSON que utilizo en algunos campos tiene el siguiente caracter por lo que llegados a ese punto la app deja de leer el JSON. como podria hacer para que lea los string “” y tambien lea ????? Espero explicarme y que me entienda alguien. Gracias

    • tael

      el caracter es “”

  • cristian eugenio ferrazzano

    Hola James como estas? te cuento que me ha funcionado correctamente utilizando la libreria de MCXIAOKE. El problema es que funci
    ona solo en debug. Cuando cargo el release en mi celular no funciona, me da un error de volley. Para colmo no puedo “debuguear” ya que las release no puedo hacerlas andar en androidstudio

    • Hola cristian. Qué problema. ¿Que error te sale?

      • cristian eugenio ferrazzano

        James era un error 500, error del servidor. Cuando revisé los logs, estaba llegando vacío el objeto request. En definitiva, el error era por causa del minify, que al hacerlo release te comprime la app y me dejo sin funcionar algunas cuestiones al parecer. Muchas gracias por tu respuesta James. un fuerte abrazo.

  • Edgar Mauricio Gutierrez Chav

    Como se puede tomar informacion de los textview que estan en la lista, por ejemplo para ampliar la informacion en un fragment? como puedo por ejemplo tomar la imagen y pasalar a otro fragment, por favor si alguien sabe mi correo mauro1891@live.com

    • Para enviar datos puedes usar los métodos putExtra*() de la clase Intent. Simplemente empaquetas los datos y los recibes en la otra actividad.

      Te dejo este articulo: http://www.hermosaprogramacion.com/2014/09/desarrollo-android-intents/

      • Edgar Mauricio Gutierrez Chav

        Hola utilizo el siguiente codigo para empaquetar los datos pero me dice que el titulo debe ser public static pero si lo coloco static se me repite por todo el listview el mismo.

        • Claro, la clausula static en la mayoría de casos no permite cambio de valor. Esto lo estás haciendo en un fragmento?

  • Argenis Urribarri

    Saludos!

    Diría que en el ejemplo (apartado 5.1) hay un error que supongo debe ser por la inercia de como escribimos al programar…

    // Nueva petición JSONObject
    jsArrayRequest = new JsonObjectRequest(

    … esta definido el nombre de la instanciacion (variable objeto) pero no se dice el tipo de objeto…

    quedaria:

    // Nueva petición JSONObject
    JsonObjectRequest jsArrayRequest = new JsonObjectRequest(

    … mas que nada por coherencia de codigo

    Un saludo!

    • Hola Argenis. Si claro, tienes razón, tal vez todos los lectores no tengan en cuenta la simplificación. Gracias

  • Sebastian Manriquez Hernandez

    Muy buenos tutoriales, se agradece, pero me surge la duda como seria el codigo php para recibir el objeto json cuando se usa POST,es que estoy tratando de hacer un login y registro, si tienes algun ejemplo te lo agradezco. Saludos

  • Ramés

    Hola señores de Hermosa Programación tengo un problema, esta aplicación en el emulador de android studio funciona pero al pasarla a mi s6 se detiene, sera que puse algo mal? espero me puedan ayudar quiero aprender un poco sobre android ahora que me compre este celular. Gracias de ante mano

    • Hola Ramés. Un excelente celular por supuesto. ¿Puedes detectar que error te sale o que mensaje puedes obtener en Android Studio?

  • jeanpier echevarria

    Hola que tal muy buen tutorial, tengo una pregunta si quiero que la lista se actualice cada vez que hay un nuevo post que es lo que puedo hacer, y tambien como prodria acceder al detalle de los item de la lista, gracias de antemano.

    • Hola Jeanpier

      La actualización puedes realizarla manualmente sobre el adaptador con el método adapter.notifyDataSetChanged(). Sin embargo cuando llamas a los métodos de añadir o eliminar elementos, la lista se actualiza sola.

      Prueba y me dices que sucede.

      • jeanpier echevarria

        Hola gracias por responder, quisera hacerte otra pregunta para traer solo una imagen por ejemplo un logo, es necesario usar las 3 clases que usaste para traer los post ?

        • A cuáles clases de refieres amigo?

          • jeanpier echevarria

            Amigo no se si algun dia haras un login con volley para acceder a una aplicación seria genial ! Gracias por los tutoriales

          • Me gustaría hasta crear una aplicación que acabe con la guerra mundial Jeanpier, sin embargo todo paso a paso, aún faltan tutoriales. Espero que el login con volley llegue pronto.

          • jeanpier echevarria

            Me referia a la clase Post, Listapost, si solo quiero mostrar esa sola imagen sin listview con solo NetworkImageView solo usaria el adapter verdad ?

          • Si es solo una imagen no es necesario crear todas las clases. Simplemente haces la petición y asignas el resultado en el NetworkImageView.

  • David Perez

    Hola James he seguido el tutorial al pie de la letra y funciona pero el json que tengo que usar es Json Array y no un Json Object y me da un error, hice una forma nueva de mostrar los datos con la plantilla por defecto que trae android para las listview: android.R.layout.two_line_list_item
    Pero por alguna razón cuando quiero cambiar este layout por uno propio no me lo muestra pero si me cargan los archivos, así tengo todo por el momento:
    1-Fuente de datos JSON: http://davidperez.hostinazo.com/taberapp/ver_noticia_json.php
    El problema es que no muestra la lista al usar mi layout pero si lo muestra al usar el layout de android, cual crees que podría ser la solución? gracias por la atención.

    • Hola David.

      No se si te haya entendido bien, pero creo que esto puede estar sucediendo debido a que no usas los identificadores android.R.id.text1 y android.R.id.text2 en tu adaptador.

      Recueda que estás usando un layout del sistema, por lo que debes referirte a los identificadores que tiene ese diseño.

      Si presionas CTRL+Click sobre android.R.layout.two_line_list_item verás que allí están los textviews con esos IDs.

      Prueba y me dices amigo.

      • David Perez

        Gracias por responder James, hice lo que me recomendaste y me funcionó el problema estaba en que la lista solo la mostraba una vez es decir la primera vez que se iniciaba la app después de instalarla, lo que hice fue meter el adapter y el lista.setAdapter dentro de la función y me funcionó de perles, muchas gracias por todo y sigue así con tus tutoriales que son de lo mejor, saludos desde El Salvador.

  • Manolo

    Hola, tengo un problema con singleton: E/Volley﹕ [385] BasicNetworkperformRequest: Unexpected response code 500 for mi url, el caso es que accedo a esa dirección en el navegador y no hay problema… estoy usando tipo get.

  • Ronald Velasquez

    Excelente post, estaba usando volley correctamente pero me hacia flata el patron singleton y acoplar gson correctamente, gracias por la ayuda

  • Ana

    Muchas gracias por el articulo me sirvio muchisimo!! sigan asi!!

    • James Revelo

      :D Gracias Ana, que bien que te haya sido de utilidad

  • Jose

    Excelente tutorial amigo, yo lo realice con un recyclerview y cardview pero tengo problemas para poder mostrar la imagen, yo lo intente hacer y solo me muestra una imagen en todas los items, que me recomiendas hacer? desde ya Gracias

  • Pingback: Request HTTP con Volley (Android) | dimaslz.com()

  • Christian

    hola, los cursos son fantastico , yo soy nuevo en la programacion en android , me serviria una conexion que controlle siempre el server (tiempo real si posible) porque los datos en el database cambian constantemente, me recomiendas usare volley para controllar y mandarlo en ciclo o como podria hacer?, espero me puedas responder gracias

    • James Revelo

      Hola Christian.

      Gracias por tu animo.

      Bueno habría que ver sobre que trata tu aplicación. Sin embargo puedes usar Volley para enviar y recibir datos. Solo que debes implementar un patrón MVC de Red con la clase ContentProvider para refrescar la vista y un sincronizador + servicio que compruebe cada cierto tiempo la diferencia de datos.

  • juan

    Hola! excelente tutorial. Una duda: ¿Cómo puedo agregar la librería Volley utilizando Eclipse??? La parte de compile files(‘libs/volley.jar’) no me quedó clara de cómo hacerlo sin Android Studio.
    El tutorial lo puedo realizar en Eclipse? o cambian más cosas además de lo que acabo de preguntar?

    Saludos :)
    Adiós

  • Luan

    Muchas gracias… :) muy buen articulo me sirvió de mucho disculpa tienes algún correo que me puedas dar o proporcionar. cuanto me cobrarias por darme unas clases de android.!!

    • James Revelo

      Que bien que te haya sido de utilidad amigo. Si quieres escribeme a jamesreveu@gmail.com para hablar ese asunto. Saludos!

  • Luan

    Hola buen día excelente articulo oye una pregunta intente hacer el ejemplo pero me marca un error ala hora de realizar el objeto JsonObjectRequest.

    // Nueva petición JSONObject
    jsArrayRequest = new JsonObjectRequest(
    Request.Method.GET,
    URL_BASE + URL_JSON,
    null,
    new Response.Listener() {
    @Override
    public void onResponse(JSONObject response) {
    items = parseJson(response);
    notifyDataSetChanged();
    }
    },
    new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
    Log.d(TAG, “Error Respuesta en JSON: ” + error.getMessage());

    }
    }
    );

    me marca el error

    Error:(80, 26) 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

    • James Revelo

      Hola Luan. Supongo que estás usando la librería del repositorio maven.

      Este error sucede debido a que el constructor requiere un tipo en el parámetro que usamos como null. Intenta casteando el parámetro de la siguiente forma:

      // Nueva petición JSONObject
              jsArrayRequest = new JsonObjectRequest(
                      Request.Method.GET,
                      URL_BASE + URL_JSON,
                      (String)null,
                      new Response.Listener() {
                          @Override
                          public void onResponse(JSONObject response) {
                              items = parseJson(response);
                              notifyDataSetChanged();
                          }
                      },
                      new Response.ErrorListener() {
                          @Override
                          public void onErrorResponse(VolleyError error) {
                              Log.d(TAG, "Error Respuesta en JSON: " + error.getMessage());
      
                          }
                      }
              );
      

      ó usando las comillas simples de vacío

      “”

      Saludos! :D

      • Luan

        Hola gracias si ya me funciono, una ultima pregunta es que soy nuevo en esto y bueno tengo un pequeño error el PostAdapter.java lo tengo en un package llamado “Adapter” el cual realizo la instancia en un archivo llamado HomeFragment.java de la siguiente manera

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

        listView = (ListView)rootView.findViewById(R.id.listView);
        adapter = new PostAdapter(this);
        listView.setAdapter(adapter);

        // Inflate the layout for this fragment
        return rootView;
        }

        pero me marca un error en el el this del new PostAdapter;

        • James Revelo

          El fragmento no puede ser el contexto. Debe ser la actividad o la aplicación como tal. En vez de this usa la función getActivity() que retorna en la actividad host como contexto.

      • chee!! ya van dos veces que me pasa ese error y me deja pensando un buen rato ajajajjaja , gracias por la solución :D