Volley, web services para principiantes

Una vía rápida al JSON

¿El mundo de los servicios web siempre te ha parecido algo complicado? Ya es hora de perder ese miedo con la biblioteca Volley para Android. Gracias a esta biblioteca podremos hacer peticiones a servidores web que nos devuelvan un JSON o una imagen. Además, podremos hacer varias consultas simultáneas y preocuparnos solo de recibirlas, pudiendo olvidarnos de tener que crear un cliente http o de tener que crear nuevos hilos para la ejecución en segundo plano, ya que todas las peticiones se realizarán en segundo plano automáticamente.

Paso a paso

En este tutorial vamos a aprender a hacer peticiones GET para obtener JSON e imágenes y veremos cómo extender una petición para que podamos incluir datos en el header de la misma. Aquí podéis descargar un proyecto de ejemplo en el que se realizan peticiones para obtener una cadena JSON y un Bitmap.

Estos son los pasos que seguiremos:

  1. Petición de objeto JSON (JsonObjectRequest)
  2. Política de reintentos (RetryPolicy)
  3. Cancelar una petición
  4. Petición de Bitmap (ImageRequest)
  5. Extender una petición de JSON para poder incluir datos en el header
  6. Contador de peticiones

 

0.   Compilar la biblioteca


Añadimos en el gradle la siguiente dependencia:

No hay que olvidar tampoco que vamos a hacer uso de Internet, por lo que debemos añadir al AndroidManifest.xml el permiso necesario antes del elemento <application>:

Cuidado porque, si escribimos el permiso nosotros mismos, el autocompletado puede escribirnos “ANDROID.PERMISSION” con mayúsculas y entonces no funcionará, produciendo el siguiente error:

1. Realizar una petición de JSON básica


Para realizar una petición (Request), Volley necesita crear una cola de peticiones (RequestQueue). Utilizaremos una RequestQueue básica:

Así de fácil creamos una cola de peticiones básica. Esta cola de peticiones trae por defecto una memoria caché y una conexión de red básica. Para modificarla, puedes ver el tutorial de Android Developers si necesitas crear una RequestQueue personalizada (https://developer.android.com/training/volley/requestqueue.html). Es muy conveniente que hagamos una clase singleton para obtener siempre la misma cola de peticiones (en el proyecto descargable en el apartado “Paso a paso” se hace de esta forma).

Lo siguiente es crear una Request con la url a la que queremos realizar la consulta. Esta se ejecutará en segundo plano sin que tengamos que hacer nada, y además lo hará de forma asíncrona, por lo que debemos asignarle un par de callbacks para recibir tanto la respuesta afirmativa como un posible error:

Recordad que en las url los espacios se escriben como ‘%20’, por lo que si String url viene desde otra parte del código y no estamos seguros de si se le han podido añadir espacios, conviene realizar una sustitución con replaceAll(" ", "%20").

Y, por último, solo tenemos que enviar la petición añadiéndola a la cola de peticiones.

Cuando la petición se haya completado, recibiremos la respuesta en uno de los callbacks. La petición se realizará en un nuevo hilo automáticamente y la respuesta de los callbacks llegará en el hilo principal.

Response

La respuesta response que se recibe en el primer callback será un JSONObject que debe convertirse en el/los objetos pertinentes. Esta conversión puede hacerse mediante la biblioteca Gson de Google o manualmente como una especie de parcelización de objetos. En la próxima entrada explicaremos cómo tratar un JSON para convertirlo en nuestro propio objeto de estas dos maneras. De momento nos contentaremos con ver cómo obtenemos la cadena JSON mostrándola en pantalla en un TextView.

Error

Si se ha producido un error, las respuesta llegará a este callback. El objeto VolleyError dispone del método getMessage(), el cual nos informará de la causa. Una buena forma de saber qué está pasando es hacer una llamada a Log.e(“VOLLEY”, error.getMessage()), lo cual nos mostrará por Logcat cuál es el problema. Otra solución es considerar el tipo de error. VolleyError es una instancia de algún tipo de objetos de error. Podemos usar instanceof para comprobar qué tipo de objeto es y actuar de una forma o de otra en consecuencia.

Tipos de error

AuthFailureErrorHa habido un problema con la autenticación al realizar la petición.
NetworkErrorSe ha producido un error en la red al realizar la petición.
NoConnectionErrorNo se ha podido establecer la conexión.
ParseErrorEl error se ha producido al intentar procesar la respuesta producida por el servidor.
RedirectionErrorLa url introducida redirige a otra página en lugar de dar una respuesta.
ServerErrorEl servidor ha emitido un error como respuesta.
TimeoutErrorSe ha agotado el tiempo establecido para realizar la conexión sin que se haya podido conectar.

 

2. Política de reintentos


Con Volley podemos establecer una política de reintentos (RetryPolicy) que defina cuántas veces se va a reintentar enviar una petición y cuánto tiempo esperar entre cada vez. Para ello, el objeto Request cuenta con el método setRetryPolicy(), el cual recibe el tiempo de espera entre reintentos, la cantidad máxima de reintentos y el multiplicador del retroceso exponencial:

En cristiano, esta política de reintentos establece lo siguiente:

  • Tiempo de espera entre reintentos: si la conexión falla, este será el tiempo (en milisegundos) que se dejará transcurrir antes de reintentar la conexión. La constante DEFAULT_TIMEOUT_MS vale 2500.
  • Cantidad máxima de reintentos: 3 reintentos de conexión como máximo.
  • Multiplicador del retraso exponencial: en caso de que el primer reintento falle, el tiempo de espera para volver a intentarlo se multiplicará por esta cantidad. Así, si hemos establecido un tiempo de espera de 5 segundos y un multiplicador de 2, el primer reintento se hará a los 5 segundos, el siguiente a los 10, el siguiente a los 20, el siguiente a los 40 y así sucesivamente este tiempo se irá multiplicando por dos hasta que la petición se realice o se agoten los intentos. En este caso, la constante DEFAULT_BACKOFF_MULT vale 1, por lo que el tiempo entre reintentos será siempre el mismo.

La política de reintentos debe definirse antes de poner la petición en la cola, es decir, antes de llamar al método requestQueue.add(request).

3. Cancelar una petición


Con Volley podemos cancelar todas las peticiones que queden en la cola o una petición concreta utilizando la etiqueta (tag) que se le asignó y se hace en una sola línea de código:

requestQueue.cancelAll();Cancela todas las peticiones.
requestQueue.cancel(SIMPLE_REQUEST);Cancela las peticiones a las que se les haya asignado la etiqueta SIMPLE_REQUEST.

 

4. Petición para obtener un Bitmap


Una de las ventajas de utilizar Volley, además de su capacidad para realizar varias peticiones simultáneas, es el hecho de que dispone de un método que permite obtener una imagen de un servidor como un objeto Bitmap sin mayores complicaciones que las anteriores. En lugar de utilizar la clase JsonObjectRequest, utilizaremos la clase ImageRequest, la cual funciona exactamente de la misma manera:

Los parámetros que tiene este método son:

  1. La url a la imagen.
  2. Un Listener para recibir la imagen en caso de respuesta correcta.
  3. Una altura y una anchura máximas (en píxeles) a las que se descargará la imagen en caso de que las dimensiones originales sean mayores. Indicando 0 se descargará al tamaño original.
  4. La forma en que se recortará la imagen en caso de que la altura y anchura máxima indicadas sean menores que las originales. Esta forma se indica con una constante de la clase ImageView.ScaleType.
  5. La configuración de descodificación de la imagen que determinará la calidad y cantidad de bits utilizados para cada color. Se indica con una constante de la clase Bitmap.Config.. De las formas disponibles, ARGB_8888 es el que ofrece mayor calidad y permite transparencia, pero es aconsejable hacer pruebas para encontrar la mejor calidad consumiendo menos recursos.
  6. Un ErrorListener donde recibir el error en caso de que la petición falle.

Podemos cargar la imagen obtenida en un ImageView:

5. Petición extendida para añadir datos en el header


Hay páginas web que requieren recibir datos en el header de la petición, y para poder enviarlos no nos sirve una petición simple como las anteriores, sino que debemos extender una para asignarle los datos al header. Según los datos que deba devolver nuestra consulta, extenderíamos una clase u otra, es decir, si necesitamos enviar datos en el header para obtener una imagen, extenderemos ImageRequest y, si vamos a obtener un JSON, extenderemos la otra. En cualquier caso, esta es una forma de hacerlo:

Como veis, consiste en pasarle un objeto de tipo Map, como un HashMap con pares clave-valor en los que el nombre de los parámetros es la clave y el valor de los parámetros es el valor del par, y sobrescribir el método getHeaders() para que los devuelva.

Ejemplo:

Si los parámetros del header son:

key: 45646B;
base_number: 14

La petición se realizaría de la siguiente manera:

Y listo, esta es la manera de enviar parámetros en el header.

6. Contador de peticiones


Lo que vamos a explicar a continuación puede parecer una tontería, pero hay que caer en la cuenta o si no, estaremos vagando eternamente por nuestra propia inopia, y un programador sabe perfectamente lo que es eso. En ocasiones querremos realizar más de una petición al mismo tiempo y no devolver el resultado hasta que todas las peticiones hayan terminado. Y el problema es que, tratándose de una ejecución asíncrona, ¿cómo podemos guardar todos los resultados y estar seguros de que el código se ejecute cuando todas hayan terminado?

La solución consiste en poner un contador y aumentarlo cada vez que se reciba un resultado en cualquiera de los callbacks. Este contador debe estar declarado de manera que su espacio en memoria esté garantizado, como una variable de la clase o como un final(mirad la explicación más abajo), y debe reiniciarse cada vez que se manden las peticiones:

Aunque pongamos las peticiones en la cola en un determinado orden, unas se procesarán más rápido que otras y esto es completamente impredecible, por lo que no podemos establecer si se habrán procesado todas cuando llegue la respuesta de la última. Es por eso que debemos usar un contador al que sumaremos 1 cada vez que se haya completado una petición. Sin embargo, no podemos saber si las peticiones finalizarán con un resultado correcto o incorrecto, por lo que debemos aumentar en uno el contador en los dos callbacks.

El problema, además, viene al tener que utilizar variables externas dentro de un callback, como es el caso. El programa necesita asegurarse de que las variables que utilizamos dentro de un callback tiene un espacio asegurado en la memoria. Si son atributos de entrada del método se pueden convertir en variables finales. Si no, podemos declararlas como variables de la clase (estáticas o no a nuestro gusto). Si no queremos declararlas como variables de la clase, sino del método, Android Studio sugiere (al pulsar Alt+Intro) que declaremos la variable como un array final de una sola posición del tipo que sea la variable (p.ej. final String[] letra = {a}), ya que al declararse como conjunto, se reserva un espacio en memoria.

Al pulsar 'Alt + Intro', Android Studio ofrece la posibilidad de convertir la variable en un conjunto constante.

Al pulsar ‘Alt + Intro’, Android Studio ofrece la posibilidad de convertir la variable en un conjunto constante de una posición.

En este caso utilizamos una List<Bitmap>, declarada como una variable de la clase y una variable como final int[] contador, cuya única posición con valor inicial 0 será la que vayamos aumentando en 1.

Por último, una vez tengamos las variables correctamente declaradas, tendremos que contemplar el hecho de que se haya completado la última petición en ambos callbacks, ya que no sabemos si fallará o se completará correctamente. Por eso se han incluido la línea if (contador == totalPeticiones) al final de los dos callbacks llamando al siguiente método.

4 Comentarios
  1. Alejandro 9 meses

    Buenas estoy intentando crear un registro, 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

    • Imagen de perfil de Juan José Melero Autor

      Sí, por favor, copia y pega la clase en la que haces la llamada con Volley y explica cuándo se hace la llamada y qué debería devolverte el servicio.

      Si no cabe aquí, mándala a mi correo.

      Aunque así, a bote pronto, se me ocurre que puede que estés intentando insertar un registro con una clave primaria idéntica a uno que ya existe en el servidor y en las clases php no estás contemplando respuesta para este tipo de fallo SQL y devuelves nulo.

      En cualquier casoe no parece ser un error relacionado con Volley, sino con la parte servidora.

  2. Gabriel Betancourt 7 meses

    Hola, alquien sabe en el caso de una peticion GET incluir el HasMap de los paramentros? Pues con la sobreescritura de getParams() me funciona para POST mas no para GET.
    Se agradece cualquier sugerencia. Saludos

    • Imagen de perfil de Juan José Melero Autor

      Si te refieres a los parámetros que van en la ruta, Volley no parece disponer de un método para ello. He estado mirando los métodos de la clase Request de Volley y no parece haber ninguno para que concatene automáticamente los parámetros recibidos en un HashMap a la ruta. A fin de cuentas, es tan fácil como meterlos en la ruta:

      public void enviarPeticionGETConParametros(){
           String url = "http://www.mywebservice.call.service";
           HashMap<String, String> parametros = new HashMap<>();
           parametros.put("parametro1", "valor1");
           parametros.put("parametro2", "valor2");

           String urlConParametros = incluirParametrosAUrl(url, parametros);

           JsonObjectRequest peticion= new JsonObjectRequest(urlConParametros,
               new Response.Listener() {
                   @Override
                   public void onResponse(JSONObject response) {
                       // Hacer algo con el json obtenido
                   }
               },
               new Response.ErrorListener() {
                   @Override
                   public void (VolleyError error) {
                       // Hacer algo con el error.
                   }
               });

           peticion.setTag(MY_TAG);
           requestQueue.add(peticion);
      }

      public String incluirParametrosAUrl(String url, HashMap parametrosGET) {

           if (parametrosGET != null && !parametrosGET.isEmpty()) {
               url += "?";

               Iterator<Map.Entry<String, String>> it = parametrosGET.entrySet().iterator();

               while (it.hasNext()) {
                   Map.Entry<String, String> parametro = it.next();
                   url += parametro.getKey() + "=" + parametro.getValue();
                   if (it.hasNext()) {
                      url += "&";
                   }
               }
           }
           return url;
      }

Contesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*

©2017 Codictados Comunidad libre para el aprendizaje de codigo Online

o

Inicia Sesión con tu Usuario y Contraseña

o    

¿Olvidó sus datos?