Servicios Web Android
Índice
1
Introducción..................................................................................................................2
2
Ejercicios...................................................................................................................... 2
2.1
Listado de libros a partir de servicios web (0,5 puntos).......................................... 2
2.2
Obteniendo las portadas de los libros (0,5 puntos).................................................. 3
2.3
Añadir un libro (0,5 puntos).....................................................................................5
2.4
Búsqueda de libros (modificar el adaptador de la Base de Datos) (0,5 puntos)...... 5
2.5
Búsqueda de libros (mostrar el diálogo de búsqueda) (0,5 puntos).........................6
2.6
Búsqueda de libros (mostrando los resultados) (0,5 puntos)................................... 7
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
1. Introducción
En esta sesión del proyecto de integración vamos a aplicar lo aprendido durante las
sesiones del módulo de servicios web en el proyecto de Android. Concretamente
utilizaremos servicios web para acceder a una base de datos remota de libros, de tal forma
que desde nuestra aplicación podremos listarlos y añadir libros nuevos. En esta sesión
también veremos cómo implementar la opción de búsqueda en nuestra aplicación.
2. Ejercicios
Una vez terminados los ejercicios podremos obtener el listado de libros y añadir nuevos
libros usando los servicios web de jtech. También será posible buscar libros por el título o
por el ISBN.
2.1. Listado de libros a partir de servicios web (0,5 puntos)
En este primer ejercicio vas a utilizar el código que desarrollaste en el primer ejercicio de
la segunda sesión del módulo de Servicios Web. En dicho ejercicio debías acceder al
servicio de listado de libros y rellenar una tabla. Incorpora el código en tu proyecto de tal
forma que lo que se rellene sea el ListView de la actividad Listado con la información de
los libros en el servidor jtech.
Nota:
Recuerda que en la sesión anterior del proyecto con Android hicimos uso de la persistencia para
que el acceso al listado remoto de libros se hiciera tan sólo la primera vez que se accediera al
listado en nuestra aplicación. Esto se debe mantener en esta nueva versión, pero en lugar de
cargar los libros del objeto singleton lo haremos del servicio web. Conéctate al servicio para
inicializar la base de datos de la aplicación con el listado de libros y usa los libros de la base de
datos para mostrar finalmente el listado en pantalla.
Para comprobar si la BD está vacía, en el método onCreate, tras obtener el cursor,
miraremos si ha devuelto algún registro. En caso de que no haya devuelto ninguno,
deberemos poner en marcha la tarea de descarga de libros del servidor.
if (items.getCount() <= 0) {
// Lanza la tarea del servicio web
...
}
Cuando la tarea haya finalizado de descargar los libros, y los haya insertado en la base de
datos (utilizando el adaptador creado en la sesión anterior), deberemos recargar el cursor
y notificar al adapter que los datos han cambiado y por lo tanto debe recargar la lista en
pantalla:
2
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
items.requery();
la.notifyDataSetChanged();
2.2. Obteniendo las portadas de los libros (0,5 puntos)
En este ejercicio modificaremos nuestro código para mostrar la portada de los libros en la
actividad de listado, sólo para aquellos libros que efectivamente tengan portada. Para
aquellos que no tengan, por defecto mostraremos el drawable logo.png que tenemos
empaquetado en la aplicación.
El servicio utilizado en el ejercicio anterior no nos proporciona la portada de los libros,
así que va a ser necesario ir accediendo a los libros uno por uno. Para hacer esto sin
sobrecargar la red, utilizaremos la descarga lazy de imágenes, tal como vimos en el tercer
ejercicio de la primera sesión del módulo de acceso a servicios web.
Tampoco estamos almacenando las portadas de los libros en la base de datos ni en
ficheros locales, así que tendremos que conectarnos a los servicios jtech cada vez que
accedamos al listado para mostrarlas. No es la mejor solución pero es la que usaremos en
este ejercicio para simplificar (como mejora podríamos guardar las imágenes en un
fichero e intentar recuperarlas de ahí en futuros accesos, pero no es necesario para este
ejercicio).
En primer lugar vamos a añadir un método a la actividad listado que dado un ISBN nos
proporcione un objeto Bitmap correspondiente a la portada del mismo, en el caso en el
que éste efectivamente la tenga. El código de la función podría ser el siguiente:
public Bitmap getPortadaLibro(String isbn) {
Bitmap portada = null;
String urlToSendRequest =
"http://server.jtech.ua.es/jbib-rest/resources/libros/"
+ isbn + "/imagen";
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(urlToSendRequest);
HttpResponse response;
try {
response = httpClient.execute(httpget);
portada = BitmapFactory.decodeStream(response.getEntity()
.getContent());
} catch (Exception e) {
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown();
}
return portada;
}
Obsérvese que estamos accediendo a un servicio específico que nos dará la portada de un
libro a partir de su ISBN. El objeto Bitmap se obtiene a partir de la respuesta del servicio
mediante la clase BitmapFactory. La función getPortadaLibro devolverá null en el
3
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
caso en el que el libro no tenga portada.
Como hemos comentado anteriormente, accederemos al servicio en jtech cada vez que
vayamos a mostrar el libro en la actividad de listado de libros. Por lo tanto, el sitio ideal
para cargar la portada es en el método getView del adaptador que hayamos usado para
poblar el ListView de elementos. Pero en lugar de llamar al método getPortadaLibro
directamente, lo cual podría causar latencia, cargaremos cada portada individual mediante
un AsyncTask. El AsyncTask deberá recibir el ISBN del libro.
class CargarImagenTask extends AsyncTask<String, Integer, Bitmap> {
Bitmap portada;
@Override
protected Bitmap doInBackground(String... params) {
return getPortadaLibro(params[0]);
}
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
this.portada = result;
la.notifyDataSetChanged();
}
}
public Bitmap getPortada() {
return portada;
}
}
En el método doInBackground del AsyncTask será desde donde llamaremos a
getPortadaLibro pasando como parámetro el ISBN. Cuando termine la descarga de
forma correcta, en onPostExecute se guarda la portada descargada en una propiedad de
la tarea, y se notifica al adapter que los datos han cambiado para que recargue y muestre
la imagen.
Deberemos llevar un control de las tareas de descarga activas y completadas, para evitar
poner a descargar la misma imagen dos veces. Esto podemos hacerlo añadiendo un mapa
de imágenes en descarga, y sólo lanzar la tarea de descarga si la imagen no se está
descargando ya, tal como se hizo en el módulo de acceso a servicios web. En este caso,
dado que no tenemos los libros encapsulados en un objeto Libro, sino que estamos
accediendo a ellos mediante un cursor, podríamos indexar el mapa de imagenes en
descarga mediante el ISBN directamente.
Map<String, CargarImagenTask> imagenesCargando;
Por lo tanto, en getView deberemos comprobar si hay una tarea de descarga activa o
completada para el ISBN actual. En caso de no haberla, mostraremos el drawable por
defecto y lanzaremos la tarea, añadiéndola al mapa. En caso de que la haya,
comprobaremos si está activa o completada (si getPortada nos devuelve null o no). En
caso de tener la imagen ya descargada, la mostramos en el ImageView, y en caso
contrario mostraremos el drawable por defecto.
4
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
Nota
Para que el tamaño de las imágenes en la lista sea constante, haremos que el ImageView tenga
una altura y una anchura fija de 100dp. Esto deberemos definirlo en el layout de los items de la
lista.
2.3. Añadir un libro (0,5 puntos)
En este tercer ejercicio vamos a hacer uso una vez más de código desarrollado en sesiones
anteriores, en concreto el código del primer ejercicio de la tercera sesión del módulo de
Servicios Web. Incorpora el código de dicho ejercicio en tu aplicación de tal forma que
desde ella sea posible añadir nuevos libros.
Nota:
Recuerda que al añadir un nuevo libro éste también debe ser insertado en la base de datos local
de la aplicación.
En la clase EditarLibro sólo modificaremos la parte referente a altas, no a
modificaciones. Al pulsar el botón de guardar un nuevo libro lo que haremos es lanzar
una tarea (AsyncTask) que realice el alta del libro, y cuando finalice la tarea (en
onPostExecute), si el código de respuesta es 201 (el libro se ha añadido) lo insertaremos
en la base de datos y finalizaremos la actividad. En caso de haber un error, simplemente
mostraremos un toast con el mensaje de error.
2.4. Búsqueda de libros (modificar el adaptador de la Base de Datos) (0,5
puntos)
En los siguientes ejercicios vamos a implementar las opciones de búsqueda del menú de
la actividad de listado de libros. Para ello haremos uso de una ventana de diálogo
manejada por Android para realizar este tipo de tareas. Para poder activar la búsqueda
necesitamos los siguientes componentes:
• Una configuración de búsqueda: se trata de un fichero XML que permite establecer el
valor de determinados parámetros relacionados con el diálogo de búsqueda.
• Una actividad de búsqueda: se trata de la actividad que recibirá el Intent que lanzará
el diálogo de búsqueda junto con la cadena a buscar.
• Una interfaz de búsqueda: puede ser el diálogo de búsqueda que se ha comentado
anteriormente, o una vista especial que podemos introducir en la interfaz. En nuestro
caso utilizaremos la primera alternativa.
La documentación para la creación y utilización del cuadro de búsqueda de Android se
puede encontrar en la siguiente dirección:
http://developer.android.com/guide/topics/search/search-dialog.html
5
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
Comenzaremos por añadir dos métodos adicionales a la clase AdaptadorLibros llamados
queryNombre y queryIsbn que recibirán como parámetro una cadena. Ambos deberán
devolver un objeto de la clase Cursor apuntando a todos aquellos libros de la base de
datos cuyo título o ISBN contengan, respectivamente, la cadena recibida como parámetro.
Nota:
En SQL podemos utilizar el operador like junto con el carácter comodin % en una cláusula
WHERE. Por ejemplo: SELECT * FROM libros WHERE titulo LIKE "%titulo%".
Es importante no olvidar poner dicho comodín. Por ejemplo, para la búsqueda por título en la
query podemos poner como selección "titulo like ?" y como argumento de la selección
new String[]{ "%" + titulo + "%"}.
2.5. Búsqueda de libros (mostrar el diálogo de búsqueda) (0,5 puntos)
El siguiente paso será crear el archivo /res/xml/searchable.xml que contendrá la
configuración de la búsqueda. El fichero debe contener un único elemento searchable,
cuyo único atributo obligatorio es android:label. Debe apuntar al recurso de tipo
cadena que contenga el nombre de la aplicación. El resto de atributos los puedes consultar
en
http://developer.android.com/guide/topics/search/searchable-config.html#searchable-element.
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name" >
</searchable>
Ahora vamos a modificar la declaración de nuestra actividad de listado de libros para que
sea también una actividad de búsqueda. Esta actividad será la que recibirá el Intent con
la cadena de búsqueda. Dentro del Manifest de la aplicación deberemos añadir el
siguiente contenido al elemento activity correspondiente:
<activity android:name=".Listado" >
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
A continuación, y también dentro del Manifest de la aplicación, deberemos indicar cuál
será la actividad desde la cual se podrá mostrar el diálogo de búsqueda. En nuestro caso
concreto será la propia actividad de listado de libros, por lo que dentro de su elemento
activity en el Manifest añadimos lo siguiente (el atributo android:value en este caso
indica qué actividad recibirá la búsqueda):
<meta-data android:name="android.app.default_searchable"
android:value=".Listado" />
Por fin podemos incluir una llamada al método onSearchRequested() desde las
6
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
opciones correspondientes (búsqueda por título y búsqueda por ISBN) del menú
contextual de la actividad de búsqueda de libros. Comprueba que al incluir esta llamada
se muestra correctamente el diálogo de búsqueda y que éste nos vuelve a dirigir a la
actividad de listado de libros (aunque todavía no se muestren los resultados de la
búsqueda).
2.6. Búsqueda de libros (mostrando los resultados) (0,5 puntos)
Como vamos a proporcionar la posibilidad de realizar una búsqueda basada en dos
criterios (título o ISBN) debemos establecer algún mecanismo para indicar a la actividad
que recibe el Intent de la búsqueda (en nuestro caso la actividad de búsqueda de libros)
cuál de estos dos criterios es el que se está utilizando. Para conseguir esto debemos
sobrecargar el método onSearchRequested dentro de la actividad desde la que se lanza
la búsqueda (que en nuestro caso es, también, la actividad de listado de libros). Dentro de
este método inicializaremos un objeto Bundle que se pasará junto al Intent al hacer la
búsqueda. El código quedaría de la siguiente forma:
@Override
public boolean onSearchRequested() {
Bundle appData = new Bundle();
appData.putBoolean(Listado.BUSCAR_TITULO, buscarTitulo);
startSearch(null, false, appData, false);
return true;
}
Nota:
El parámetro Listado.BUSCAR_TITULO es una cadena definida como pública y estática en
la actividad de listado de libros. Su función es la de identificar el dato que se pasa a la actividad
que recibirá el Intent de la búsqueda. El parámetro buscarTitulo, por otra parte, es un
atributo booleano de esa misma actividad. No olvides cambiar el valor a true o false
dependiendo de cuál fuera la opción de búsqueda seleccionada, antes de llamar a
onSearchRequested.
Por último haremos que la actividad de listado de libros muestre el resultado de la
búsqueda, en caso de que dicha actividad fuera invocada desde el diálogo de búsqueda. Al
ejecutar la búsqueda se lanzará una nueva instancia de la actividad Listado. Por lo tanto,
en su método onCreate deberemos comprobar si la actividad ha sido lanzada de forma
normal, o por una petición de búsqueda. Esto lo haremos comprobando si la acción del
Intent que la lanzó es ACTION_SEARCH. En este caso, podemos extraer del Intent la
query y también el parámetro que le pasamos indicando si se busca por título o por ISBN:
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
boolean buscarTitulo = true;
if (appData != null) {
buscarTitulo = appData.getBoolean(Listado.BUSCAR_TITULO);
}
7
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
// Crear el cursor a partir de la query oportuna
...
} else {
// Crear el cursor a partir de la lista completa de libros
...
}
Nota
Si nuestra actividad fuese de tipo singleTop, singleTask, o singleInstance, al estar
ya en la actividad cuando se solicita la búsqueda no se abriría una nueva instancia. En estos casos
la búsqueda se debería realizar en el método onNewIntent, que es el que se ejecutaría al
recibir el Intent de petición de búsqueda sobre una actividad ya creada.
En caso de que la actividad se cree de la forma normal (sin recibir Intent de solicitud de
búsqueda), ejecutaremos el código que teníamos anteriormente, en el que el cursor se
creaba a partir de la lista de todos los libros.
8
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.
Servicios Web Android
9
Copyright © 2012-13 Dept. Ciencia de la Computación e IA All rights reserved.