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:- Petición de objeto JSON (JsonObjectRequest)
- Política de reintentos (RetryPolicy)
- Cancelar una petición
- Petición de Bitmap (ImageRequest)
- Extender una petición de JSON para poder incluir datos en el header
- Contador de peticiones
0. Compilar la biblioteca
Añadimos en el gradle la siguiente dependencia:
1 2 3 |
dependencies{ compile 'com.mcxiaoke.volley:library:1.0.+' } |
<application>
:
1 |
<uses-permission android:name="android.permission.INTERNET"/> |
“ANDROID.PERMISSION”
con mayúsculas y entonces no funcionará, produciendo el siguiente error:
1 2 |
NetworkDispatcher.run: Unhandled exception java.lang.SecurityException: Permission denied (missing INTERNET permission?) |
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:
1 |
RequestQueue requestQueue = Volley.newRequestQueue(context); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
final int SIMPLE_REQUEST = 1; String url = "http://laquesea"; JsonObjectRequest request = new JsonObjectRequest(url, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { //Procesar JSONObject } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { //Procesar VolleyError } }); request.setTag(SIMPLE_REQUEST); |
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.
1 |
requestQueue.add(request); |
Response
La respuestaresponse
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.
1 2 3 |
@Override public void onSuccess(JSONObject response) { textView.setText(response.toString()); |
Error
Si se ha producido un error, las respuesta llegará a este callback. El objeto VolleyError dispone del métodogetMessage()
, 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
AuthFailureError | Ha habido un problema con la autenticación al realizar la petición. |
NetworkError | Se ha producido un error en la red al realizar la petición. |
NoConnectionError | No se ha podido establecer la conexión. |
ParseError | El error se ha producido al intentar procesar la respuesta producida por el servidor. |
RedirectionError | La url introducida redirige a otra página en lugar de dar una respuesta. |
ServerError | El servidor ha emitido un error como respuesta. |
TimeoutError | Se ha agotado el tiempo establecido para realizar la conexión sin que se haya podido conectar. |
1 2 3 4 5 6 7 8 |
@Override public void onFailure(VolleyError error) { if (error instanceof AuthFailureError){ Log.e("VOLLEY", "Se ha producido un fallo con las credenciales. " + error.getMessage() ); } else if (error instanceof NetworkError) { Log.e("VOLLEY", "Se ha producido un fallo fallo en la red. "+ error.getMessage()); } //Etc |
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:
1 |
request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 3, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); |
- 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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
ImageRequest imageRequest = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { //Hacer algo con el Bitmap } }, 0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888, new Response.ErrorListener(){ @Override public void onErrorResponse(VolleyError error) { //Procesar el error de la manera oportuna } } ); |
- La url a la imagen.
- Un Listener para recibir la imagen en caso de respuesta correcta.
- 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.
- 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.
- 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. - Un ErrorListener donde recibir el error en caso de que la petición falle.
1 2 3 4 |
@Override public void onResponse(Bitmap response) { imageView.setImageBitmap(response); } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class JSONHeaderRequest extends JsonObjectRequest{ private HashMap<String, String> headerParams; public JSONHeaderRequest(String url, HashMap<String, String> headerParams, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) { super(url, listener, errorListener); this.headerParams = headerParams; } @Override public Map<String, String> getHeaders() throws AuthFailureError { return headerParams; } } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
HashMap<String, String> headerParams = new HashMap<>(); headerParams.put(“key”. “45646B”); headerParams.put(“base_number”, “14”); JSONHeaderRequest jsonHeaderRequest = new JSONHeaderRequest(url, headerParams, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { //Aquí se procesa el JSON obtenido } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { //Aquí se procesa el error producido } }); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public class MiClase { private static int contador; private static List<Bitmap> bitmaps; private int totalPeticiones = 6; private RequestQueue requestQueue; private String[] urls; public MiClase(Context context, String[] urls){ this.requestQueue = Volley.newRequestQueue(context); this.urls = urls; } public void hacerPeticiones(){ bitmaps = new LinkedList<>(); final int[] contador = {0}; for (int i = 0; i < 6; i++){ ImageRequest imageRequest = new ImageRequest(urls[i], new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { bitmaps.add(response); contador[0]++; if (contador[0] == totalPeticiones) procesarResultados(); } }, 0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888, new Response.ErrorListener(){ @Override public void onErrorResponse(VolleyError error) { contador[0]++; if (contador[0] == totalPeticiones) procesarResultados(); } }); } requestQueue.add(imageRequest); } public void procesarResultados(){ //Hacer algo con los bitmaps } } |
final String[] letra = {a}
), ya que al declararse como conjunto, se reserva un espacio en memoria.
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.
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
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.
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
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;
}
Buenos días:
Gracias por tus aportes.
Por otro lado comentarte que aunque bien explicado y entendido por mi parte, me es imposible ensamblar el código en una aplicación.
Mi desconocimiento de volley, y mi nivel de iniciación en android imposibilitan crear un programa con dicha implementación.
¿Sería posible un ejemplo, básico pero completo?
Un saludo
Vas a tener que ser más concreto. El ejemplo es muy básico. Lo único que no se explica es cómo tratar un JSON de respuesta. ¿Puedes indicar qué te falta o dónde te pierdes?
Buen día,
Quiero agradecer por tan excelente explicación, gracias por compartir tu conocimiento con nosotros que empezamos en Android.
Saludos desde Colombia.
Gracias a ti también. Sois la pieza que hace que esto tenga sentido. 😉
Hola estoy usando Volley para realizar una consulta a un webservice que hice en php y mysql. el problema es que al momento de realizar una realizar la consulta me trae todo lo de la BD. ejemplo tengo una tabla productos e intento traer todo los productos de nombre HP y categoria Laptops (solo hay 1) sin embargo, me trae todos los de ese nombre sin importar la categoria, cosa rara porque sin la categoria el php no me manda nada ya que no hay coincidencias… Espero me puedas contestar…
Buen aporte JJ
Tengo una duda, que consideraciones debo tener para correr desde un celular me devuelve el siguiente el error al tratar de realizar un insert :com.android.volley.AuthFailureError
Cabe indicar que desde del emulador de android studio me inserta el registro.
Estoy usando una conexion simple y es la primera vez que estoy usando Volley.
Es indistinto que me conecte por wifi o datos desde el celular ?
Desde ya gracias por compartir informacion
Saludos desde Lima-Peru.
Manuel Rivera
Buena tarde, gracias por el aporte pero tengo el siguiente error y la verdad no se como solucionarlo ya que a penas llevo unos dias programando en android:
En esta linea de codigo : request = new JsonObjectRequest(Request.Method.POST, url,new Response.Listener()
me sale el siguiente error:
Error:(75, 55) error: incompatible types: int cannot be converted to String
espero me colaboren. Gracias
tengo un json que contiene nombre, sexo,fecha nacimiento,edad, pero este puede contener desde 1 hasta «n» cantidad de datos, como podría enviar por post todos esos datos o como podría hacer el Map para enviarlos el objeto json con toda esa información en una sola petición
La respuesta la encuentras aquí: https://androidclarified.com/android-volley-example/.
Consiste en que utilices una JsonObjectRequest o una JsonArrayObjectRequest, a las cuales, en su constructor, se le indica el método (GET o POST) y se le puede pasar un HasMap con los parámetros que irán en el body:
JSONObject postparams = new JSONObject();
postparams.put(«nombre», «Pedro»)
postparams.put(«sexo», «hombre»)
[…]
JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST,
url, postParams, new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
// Success Callback
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Failure Callback
}
});
Si quieres enviar cabeceras, sobrescribes el método getHeaders() de la petición para que devuelva un HashMap con las cabeceras que quieres enviar.