You are on page 1of 445

1

Curso Programacin Android


Conceptos Generales (Android Studio) [Nuevo!]
1. Entorno de desarrollo Android (Android Studio) [Nuevo!]
2. Estructura de un proyecto Android (Android Studio) [Nuevo!]
3. Componentes de una aplicacin Android (Android Studio) [Nuevo!]
4. Desarrollando una aplicacin Android sencilla (Android Studio) [Nuevo!]
Interfaz de Usuario en Android
1. Interfaz de usuario en Android: Layouts [v3] [Actualizado]
2. Controles bsicos (I): Botones [v3] [Actualizado]
3. Controles bsicos (II): Texto e Imgenes [v3] [Actualizado]
4. Controles bsicos (III): Checkbox y Radiobutton [v3] [Actualizado]
5. Controles de seleccin (I): Listas desplegables (Spinner) [v3] [Actualizado]
6. Controles de seleccin (II): Listas (ListView) [v3] [Actualizado]
7. Controles de seleccin (III): Listas optimizadas (ViewHolder) [v3] [Actualizado]
8. Controles de seleccin (IV): Tablas (GridView) [v3] [Actualizado]
9. Controles de seleccin (V): RecyclerView [Nuevo!]
10. Interfaz de Usuario en Android: CardView [Nuevo!]
11. Interfaz de usuario en Android: Controles personalizados (I) [v3] [Actualizado]
12. Interfaz de usuario en Android: Controles personalizados (II) [v3] [Actualizado]
13. Interfaz de usuario en Android: Controles personalizados (III) [v3] [Actualizado]
14. Interfaz de usuario en Android: Pestaas (Tabs) [v3]
15. Interfaz de usuario en Android: Fragments [v3]
16. Interfaz de usuario en Android: ActionBar (I): Introduccin [v3]
17. Interfaz de usuario en Android: ActionBar (II): Tabs [v3]
18. Interfaz de usuario en Android: ActionBar Compat
19. Interfaz de usuario en Android: Navigation Drawer
Mens en Android
1. Mens en Android (I): Mens y Submens bsicos [v3]
2. Mens en Android (II): Mens Contextuales [v3]
3. Mens en Android (III): Opciones avanzadas [v3]
Widgets en Android
1. Interfaz de usuario en Android: Widgets (I) [v3]
2. Interfaz de usuario en Android: Widgets (II) [v3]
Gestin de Preferencias en Android
1. Preferencias en Android I: SharedPreferences [v3]
2. Preferencias en Android II: PreferenceActivity [v3]
Bases de Datos en Android
1. Bases de datos en Android (I): Primeros pasos con SQLite [v3]
2. Bases de datos en Android (II): Insercin, actualizacin y eliminacin de registros [v3]
3. Bases de datos en Android (III): Consulta y recuperacin de registros [v3]
Ficheros en Android
1. Ficheros en Android (I): Memoria Interna [v3]

2. Ficheros en Android (II): Memoria Externa (Tarjeta SD) [v3]

1.
2.
3.

Tratamiento de XML en Android


Tratamiento de XML en Android (I): SAX [v3]
Tratamiento de XML en Android (II): SAX simplicado [v3]
Tratamiento de XML en Android (III): DOM [v3]
Tratamiento de XML en Android (IV): XmlPull [v3]
Alternativas para leer y escribir XML (y otros ficheros) en Android [v3]
Localizacin Geogrfica en Android
Localizacin geogrfica en Android (I) [v3]
Localizacin geogrfica en Android (II) [v3]
Content Providers en Android
Content Providers en Android (I): Construccin [v3]
Content Providers en Android (II): Utilizacin [v3]
Notificaciones en Android
Notificaciones en Android (I): Toast [v3]
Notificaciones en Android (II): Barra de Estado [v3]
Notificaciones en Android (III): Dilogos [v3]

1.
2.
3.
4.

Acceso a Servicios Web en Android


Servicios Web SOAP en Android (1/2) [v3]
Servicios Web SOAP en Android (2/2) [v3]
Servicios Web REST en Android (1/2) [v3]
Servicios Web REST en Android (2/2) [v3]

1.
2.
3.
4.
5.
1.
2.
1.
2.

Tareas en segundo plano en Android


1. Tareas en segundo plano I: Thread y AsyncTask [v3]
2. Tareas en segundo plano II: IntentService [v3]
Depuracin de aplicaciones en Android
1. Depuracin en Android: Logging [v3]

1.
1.
2.
3.
4.
5.
6.
1.
2.
3.
4.
1.

Google Play Services


I. Introduccin y Preparativos
Introduccin y Preparativos
II. Mapas en Android
Mapas en Android API v1 (I): Preparativos y ejemplo bsico [Obsoleto. Ver API v2]
Mapas en Android API v1 (II): Control MapView [Obsoleto. Ver API v2]
Mapas en Android API v1 (III): Overlays (Capas) [Obsoleto. Ver API v2]
Mapas en Android (Google Maps Android API v2) I [v3] [Actualizado]
Mapas en Android (Google Maps Android API v2) II [v3] [Actualizado]
Mapas en Android (Google Maps Android API v2) III [v3] [Actualizado]
III. Notificaciones Push en Android Google Cloud Messaging (GCM / C2DM)
Introduccin [v3]
Implementacin del Servidor [v3]
Implementacin del Cliente Android [v3] [Ver nueva versin]
Implementacin del Cliente Android (Nueva Versin)
IV. Integracin con Google+
Inicio de Sesin con Google+ (Sign-In)

2. Acceso a datos del perfil y crculos

Conceptos Generales (Android Studio)


Entorno de desarrollo Android (Android Studio)
by Sgoliver on 20/12/2014 in Android, Programacin
El ritmo de actualizaciones de Android Studio es bastante alto, por lo que algunos detalles
de este artculo pueden no ajustarse exactamente a la ltima versin de la aplicacin. Este
artculo se encuentra actualizado para la versin de Android Studio 1.0.2
Para empezar con este Curso de Programacin Android, vamos a describir los pasos bsicos
para disponer en nuestro PC del entorno y las herramientas necesarias para comenzar a
programar aplicaciones para la plataforma Android.
No voy a ser exhaustivo, ya que existen muy buenos tutoriales sobre la instalacin de Java,
Android Studio y el SDK de Android, incluida la documentacin oficial de la plataforma,
por lo que tan slo enumerar los pasos necesarios de instalacin y configuracin, y
proporcionar los enlaces a las distintas herramientas. Vamos all.
Paso 1. Descarga e instalacin de Java.
Si an no tienes instalado ninguna versin del JDK (Java Development Kit) puedes
descargarla desde la web de Oracle.
Aunque ya est disponible Java 8, para el desarrollo en Android nos seguiremos
quedando por ahora con Java 7. En el momento de escribir este manual la reversin ms
reciente de esta serie es laversin 7 update 71, que deberemos descargar para nuestra
versin concreta del sistema operativo. Por ejemplo, para Windows 64 bits descargaremos
el ejecutable marcado como Windows x64 cuyo nombre de fichero es jdk-7u71windows-x64.exe.

La instalacin no tiene ninguna dificultad, se trata de un instalador estndar de Windows


donde tan slo hay que aceptar, pantalla por pantalla, las opciones que ofrece por defecto.
El siguiente paso es opcional, pero puede evitarnos algn que otro problema en el futuro.
Crearemos una nueva variable de entorno llamada JAVA_HOME y cuyo valor sea la ruta
donde hemos instalado el JDK, por ejemplo C:\Program Files\Java\jdk1.7.0_71. Para
aadir una variable de entorno del sistema en Windows podemos acceder al Panel de
Control / Sistema y Seguridad / Sistema / Configuracin avanzada del sistema / Opciones
Avanzadas / Variables de entorno.

Paso 2. Descarga e instalacin de Android Studio y el SDK de Android.


Descargaremos Android Studio accediendo a la web de desarrolladores de Android, y
dirigindonos a la seccin dedicada al SDK de la plataforma. Descargaremos el instalador
correspondiente a nuestro sistema operativo pulsando el botn verde Download Android
Studio y aceptando en la pantalla siguiente los trminos de la licencia.

Para instalar la aplicacin ejecutamos el instalador descargado (en mi caso el fichero se


llama android-studio-bundle-135.1641136.exe) y seguimos el asistente aceptando todas

las opciones seleccionadas por defecto. Durante el proceso se instalar el SDK de Android,
los componentes adicionales para el desarrollo sobre Android 5.0, un dispositivo virtual (o
AVD, ms adelante veremos lo que es esto) preconfigurado para dicha versin de
Android, y por supuesto el entorno de desarrollo Android Studio.

Como puede verse en la imagen anterior, tambin se instalar y configurar durante la


instalacin (si tu PC es compatible) el llamado Intel Hardware Accelerated Execution
Manager (o HAXM), que nos ayudar a mejorar el rendimiento del emulador de
Android, ms adelante hablaremos de esto. En un paso posterior del instalador se podr
indicar adems la cantidad de memoria que reservaremos para este componente (se puede
dejar seleccionada la opcin por defecto):

Durante la instalacin tendremos que indicar tambin las rutas donde queremos instalar
tanto Android Studio como el SDK de Android. Para evitar posibles problemas futuros mi
recomendacin personal es seleccionar rutas que no contengan espacios en blanco.

Una vez finalizada la instalacin se iniciar automticamente Android Studio. Es posible


que nos aparezca en este momento un cuadro de dilogo consultando si queremos
reutilizar la configuracin de alguna versin anterior del entorno. Para realizar una
instalacin limpia seleccionaremos la opcin I do not have a previous version.

Durante la primera ejecucin aparecer adems el asistente de inicio que se encarga de


descargar e instalar/actualizar algunos componentes importantes del SDK de Android (si
existieran).

Paso 3. Actualizacin de Android Studio.


Este paso tambin es opcional, aunque recomendable. Tras finalizar el asistente de inicio
nos aparecer la pantalla de bienvenida de Android Studio:

Podemos comprobar si existe alguna actualizacin de Android Studio pulsando el enlace


situado en la parte inferior de la pantalla de bienvenida (Check for updates now), lo que nos
mostrar informacin sobre la ltima actualizacin disponible (si existe) y nos permitir
instalarla pulsando el botn Update and restart. En mi caso, estaba disponible como
actualizacin la versin 1.0.2:

Tras la actualizacin, Android Studio se reiniciar y volveremos a aparecer en la pantalla


de bienvenida.
Paso 4. Configuracin inicial de Android Studio.
Lo siguiente que haremos antes de empezar a utilizar el IDE ser asegurarnos de que estn
correctamente configuradas las rutas a los SDK de Java y Android.
Para ello pulsaremos la opcin Configure de la pantalla de bienvenida, tras sta
accederemos a Project Defaults y despus a Project Structure. En la ventana de
opciones que aparece revisaremos el apartado SDK Location asegurndonos de que
tenemos correctamente configuradas las rutas al JDK y al SDK de Android. A continuacin
muestro la configuracin en mi caso, aunque puede variar segn las rutas que hayis
utilizado para instalar los distintos componentes.

Tras la revisin pulsamos el botn OK para aceptar la configuracin y volvemos al men


de la pantalla de bienvenida de Android Studio.
Paso 5. Instalar/actualizar componentes del SDK de Android.
El siguiente paso ser actualizar algunos componentes del SDK de Android e instalar otros
adicionales que nos pueden ser necesarios/tiles para el desarrollo de nuestras aplicaciones.
Para ello accederemos al men Configure / SDK Manager de la pantalla de bienvenida,
lo que nos permitir acceder al SDK Manager de Android. Con esta herramienta podremos
instalar, desinstalar, o actualizar todos los componentes disponibles como parte del SDK de
Android.

Los componentes principales que, como mnimo, deberemos instalar/actualizar sern los
siguientes:
1.
2.
3.
4.
5.

Android SDK Tools


Android SDK Platform-tools
Android SDK Build-tools (por ahora la versin ms reciente)
Una o ms versiones de la plataforma Android
Android Support Repository (extras)

6. Google Play Services (extras)


7. Google Repository (extras)
El punto 4 es uno de los ms importantes, ya que contiene los componentes y libreras
necesarias para desarrollar sobre cada una de las versiones concretas de Android. As, si
queremos probar nuestras aplicaciones por ejemplo sobre Android 2.2 y 4.4 tendremos que
descargar sus dos plataformas correspondientes. Mi consejo personal es siempre instalar al
menos 2 plataformas: la correspondiente a la ltima versin disponible de Android, y la
correspondiente a la mnima versin de Android que queremos que soporte nuestra
aplicacin, esto nos permitir probar nuestras aplicaciones sobre ambas versiones para
asegurarnos de que funciona correctamente. En este curso nos centraremos en las versiones
4.x y 5.x de Android. Intentar que todo lo expuesto sea compatible al menos desde la
versin 4.0.3 (API 15) en adelante, por lo que en nuestro caso instalaremos, adems de la
reciente versin 5.0 (API 21), alguna plataforma de la versin 4, por ejemplo la 4.4.2 (API
19).
A modo de referencia, en mi caso seleccionar los siguientes componentes/versiones
(algunos pueden estar ya instalados):
1.
2.
3.
4.
1.
2.
3.
5.
1.
2.
6.
1.
2.
3.

Android SDK Tools (Rev. 24.0.2)


Android SDK Platform-tools (Rev. 21)
Android SDK Build-tools (Rev. 21.1.2)
Android 5.0.1 (API 21)
SDK Platform
Google APIs
Google APIs Intel x86 Atom System Image
Android 4.4.2 (API 19)
SDK Platform
Google APIs (x86 System Image)
Extras
Android Support Repository (Rev. 11)
Google Play Services (Rev. 22)
Google Repository (Rev. 15)
Si nuestro PC no fuera compatible con HAXM, podemos sustituir los componentes 4.3 y
5.2 por los dos siguientes (la funcionalidad ser la misma aunque el rendimiento ser ms
lento):

4.3. Google APIs ARM EABI v7a System Image


5.2. Google APIs (ARM Systema Image)
Seleccionaremos los componentes que queremos instalar o actualizar, pulsaremos el botn
Install packages, aceptaremos las licencias correspondientes, y esperaremos a que

10

finalice la descarga e instalacin. Una vez finalizado el proceso es recomendable cerrar el


SDK Manager y reiniciar Android Studio.
Con este paso ya tendramos preparadas todas las herramientas necesarias para comenzar a
desarrollar aplicaciones Android. En prximos apartados veremos como crear un nuevo
proyecto, la estructura y componentes de un proyecto Android, y crearemos y probaremos
sobre el emulador una aplicacin sencilla para poner en prctica todos los conceptos
aprendidos.

Estructura de un proyecto Android (Android Studio)


by Sgoliver on 28/12/2014 in Android, Programacin
El ritmo de actualizaciones de Android Studio es bastante alto, por lo que algunos detalles
de este artculo pueden no ajustarse exactamente a la ltima versin de la aplicacin. Este
artculo se encuentra actualizado para la versin de Android Studio 1.0.2
Seguimos con el Curso de Programacin Android. Para empezar a comprender cmo se
construye una aplicacin Android vamos a crear un nuevo proyecto Android en Android
Studio y echaremos un vistazo a la estructura general del proyecto creado por defecto.
Para crear un nuevo proyecto ejecutaremos Android Studio y desde la pantalla de
bienvenida pulsaremos la opcin Start a new Android Studio project para iniciar el
asistente de creacin de un nuevo proyecto.

Si ya habamos abierto anteriormente Android Studio es posible que se abra directamente la


aplicacin principal en vez de la pantalla de bienvenida. En ese caso accederemos al men
File / New project para crear el nuevo proyecto.

11

El asistente de creacin del proyecto nos guiar por las distintas opciones de creacin y
configuracin de un nuevo proyecto Android.
En la primera pantalla indicaremos, por este orden, el nombre de la aplicacin, el dominio
de la compaa, y la ruta donde crear el projecto. El segundo de los datos indicados tan slo
se utilizar como paquete de nuestras clases java. As, si por ejemplo indicamos como en
mi caso android.sgoliver.net, el paquete java principal utilizado para mis clases
ser net.sgoliver.android.holausuario. En tu caso puedes utilizar cualquier otro dominio.

En la siguiente pantalla del asistente configuraremos las plataformas y APIs que va a


utilizar nuestra aplicacin. Nosotros nos centraremos en aplicaciones para telfonos y
tablets, en cuyo caso tan slo tendremos que seleccionar la API mnima (es decir, la versin
mnima de Android) que soportar la aplicacin. Como ya indiqu en el captulo sobre
la instalacin del entorno de desarrollo, en este curso nos centraremos en Android 4.0.3
como versin mnima (API 15).

La versin mnima que seleccionemos en esta pantalla implicar que nuestra aplicacin se
pueda ejecutar en ms o menos dispositivos. De esta forma, cuanto menor sea sta, a ms
dispositivos podr llegar nuestra aplicacin, pero ms complicado ser conseguir que se
ejecute correctamente en todas las versiones de Android. Para hacernos una idea del
nmero de dispositivos que cubrimos con cada versin podemos pulsar sobre el enlace
Help me choose, que mostrar el porcentaje de dispositivos que ejecutan actualmente
cada versin de Android. Por ejemplo, en el momento de escribir este artculo, si

12

seleccionamos como API mnima la 15 conseguiramos cubrir un 89.7% de los dispositivos


actuales. Como informacin adicional, si pulsamos sobre cada versin de Android en esta
pantalla podremos ver una lista de las novedades introducidas por dicha versin.

En la siguiente pantalla del asistente elegiremos el tipo de actividad principal de la


aplicacin. Entenderemos por ahora que una actividad es una ventana o pantalla de la
aplicacin. Para empezar seleccionaremos BlankActivity, que es el tipo ms sencillo.

Por ltimo, en el siguiente paso del asistente indicaremos los datos asociados a esta
actividad principal que acabamos de elegir, indicando el nombre de su clase java asociada
(Activity Name) y el nombre de su layout xml (algo as como la interfaz grfica de la
actividad, lo veremos ms adelante), su ttulo, y el nombre del recurso XML
correspondiente a su men principal. No nos preocuparemos mucho por ahora de todos
estos datos por lo que podemos dejar todos los valores por defecto. Ms adelante en el
curso explicaremos cmo y para qu utilizar estos elementos.

13

Una vez configurado todo pulsamos el botn Finish y Android Studio crear por nosotros
toda la estructura del proyecto y los elementos indispensables que debe contener. Si todo va
bien aparecer la pantalla principal de Android Studio con el nuevo proyecto creado.

En ocasiones, la versin actual de Android Studio no realiza correctamente esta primera


carga del proyecto y es posible que os encontris con el error que veis en la siguiente
imagen (Rendering Problems). Para solucionarlo no tenis ms que cerrar la ventana
del editor grfico (1) y volverla a abrir pulsando sobre el fichero activity_main.xml que
podis ver en el explorador de la parte izquierda (2).

En la parte izquierda, podemos observar todos los elementos creados inicialmente para
el nuevo proyecto Android, sin embargo por defecto los vemos de una forma un tanto
peculiar que podra llevarnos a confusin. Para entender mejor la estructura del proyecto
vamos a cambiar momentneamente la forma en la que Android Studio nos la muestra. Para
ello, pulsaremos sobre la lista desplegable situada en la parte superior izquierda, y
cambiaremos la vista de proyecto a Project.

14

Tras hacer esto, la estructura del proyecto cambia un poco de aspecto y pasa a ser como se
observa en la siguiente imagen:

En los siguientes apartados describiremos los elementos principales de esta estructura.


Lo primero que debemos distinguir son los conceptos de proyecto y mdulo. La
entidad proyecto es nica, y engloba a todos los dems elementos. Dentro de un proyecto
podemos incluir varios mdulos, que pueden representar aplicaciones distintas, versiones
diferentes de una misma aplicacin, o distintos componentes de un sistema (aplicacin
mvil, aplicacin servidor, libreras, ). En la mayora de los casos, trabajaremos con un
proyecto que contendr un slo mdulo correspondiente a nuestra aplicacin principal. Por

15

ejemplo en este caso que estamos creando tenemos el proyecto android-hola-usuario que
contiene al mdulo app que contendr todo el software de la aplicacin de ejemplo.

A continuacin describiremos los contenidos principales de nuestro mdulo principal.


Carpeta /app/src/main/java
Esta carpeta contendr todo el cdigo fuente de la aplicacin, clases auxiliares, etc.
Inicialmente, Android Studio crear por nosotros el cdigo bsico de la pantalla
(actividad o activity) principal de la aplicacin, que recordemos que en nuestro caso
era MainActivity, y siempre bajo la estructura del paquete java definido durante la creacin
del proyecto.

Carpeta /app/src/main/res/
Contiene todos los ficheros de recursos necesarios para el proyecto: imgenes, layouts,
cadenas de texto, etc. Los diferentes tipos de recursos se pueden distribuir entre las
siguientes subcarpetas:

Carpeta

Descripcin

/res/drawable/

Contiene las imgenes [y otros elementos grficos] usados


en por la aplicacin. Para poder definir diferentes recursos
dependiendo de la resolucin y densidad de la pantalla del
dispositivo se suele dividir en varias subcarpetas:
/drawable (recursos independientes de la densidad)
/drawable-ldpi (densidad baja)

16

/drawable-mdpi (densidad media)


/drawable-hdpi (densidad alta)
/drawable-xhdpi (densidad muy alta)
/drawable-xxhdpi (densidad muy muy alta :)

Contiene los ficheros de definicin XML de las diferentes


pantallas de la interfaz grfica. Para definir
distintos layouts dependiendo de la orientacin del
dispositivo se puede dividir tambin en subcarpetas:
/layout (vertical)
/layout-land (horizontal)

/res/layout/

/res/anim/
/res/animator/

Contienen la definicin de las animaciones utilizadas por la


aplicacin.

/res/color/

Contiene ficheros XML de definicin de


segn estado.

/res/menu/

Contiene la definicin XML de los mens de la aplicacin.

/res/xml/

Contiene otros ficheros XML de datos utilizados por la


aplicacin.

/res/raw/

Contiene recursos adicionales, normalmente en formato


distinto a XML, que no se incluyan en el resto de carpetas
de recursos.

/res/values/

Contiene otros ficheros XML de recursos de la aplicacin,


como por ejemplo cadenas de texto (strings.xml), estilos
(styles.xml), colores (colors.xml), arrays de valores
(arrays.xml), tamaos (dimens.xml), etc.

colores

No todas estas carpetas tienen por qu aparecer en cada proyecto Android, tan slo las que
se necesiten. Iremos viendo durante el curso qu tipo de elementos se pueden incluir en
cada una de ellas y cmo se utilizan.
Como ejemplo, para un proyecto nuevo Android como el que hemos creado, tendremos por
defecto los siguientes recursos para la aplicacin:

17

Como se puede observar, existen algunas carpetas en cuyo nombre se incluye un sufijo
adicional, como por ejemplo values-w820dp. Estos, y otros sufijos, se emplean para
definir recursos independientes para determinados dispositivos segn sus caractersticas. De
esta forma, por ejemplo, los recursos incluidos en la carpeta values-w820dp se aplicaran
slo a pantallas con ms de 820dp de ancho, o los incluidos en una carpeta
llamada values-v11 se aplicaran tan slo a dispositivos cuya versin de Android sea la
3.0 (API 11) o superior. Al igual que los sufijos -w y v existen otros muchos para
referirse a otras caractersticas del terminal, puede consultarse la lista completa en
la documentacin oficial del Android.
Entre los recursos creados por defecto cabe destacar los layouts, en nuestro caso slo
tendremos por ahora el llamado activity_main.xml, que contienen la definicin de la
interfaz grfica de la pantalla principal de la aplicacin. Si hacemos doble clic sobre este
fichero Android Studio nos mostrar esta interfaz en su editor grfico, y como podremos
comprobar, en principio contiene tan slo una etiqueta de texto con el mensaje Hello
World!.

18

Pulsando sobre las pestaas inferiores Design y Text podremos alternar entre el editor
grfico (tipo arrastrar-y-soltar), mostrado en la imagen anterior, y el editor XML que se
muestra en la imagen siguiente:

Durante el curso no utilizaremos demasiado el editor grfico, sino que modificaremos la


interfaz de nuestras pantallas manipulando directamente su fichero XML asociado. Esto en
principio puede parecer mucho ms complicado que utilizar el editor grfico [no es nada
complicado en realidad], pero por el contrario nos permitir aprender muchos de los
entresijos de Android ms rpidamente.
Fichero /app/src/main/AndroidManifest.xml
Contiene la definicin en XML de muchos de los aspectos principales de la aplicacin,
como por ejemplo su identificacin (nombre, icono, ), sus componentes (pantallas,
servicios, ), o los permisos necesarios para su ejecucin. Veremos ms adelante ms
detalles de este fichero.
Fichero /app/build.gradle
Contiene informacin necesaria para la compilacin del proyecto, por ejemplo la versin
del SDK de Android utilizada para compilar, la mnima versin de Android que soportar
la aplicacin, referencias a las libreras externas utilizadas, etc. Ms adelante veremos
tambin ms detalles de este fichero.

19

En un proyecto pueden existir varios ficheros build.gradle, para definir determinados


parmetros a distintos niveles. Por ejemplo, en nuestro proyecto podemos ver que existe un
fichero build.gradle a nivel de proyecto, y otro a nivel de mdulo dentro de la carpeta /app.
El primero de ellos definir parmetros globales a todos los mdulos del proyecto, y el
segundo slo tendr efecto para el mdulo correspondiente.
Carpeta /app/libs
Puede contener las libreras java externas (ficheros .jar) que utilice nuestra aplicacin.
Normalmente haremos referencia a dichas librera en el fichero build.gradle descrito en el
punto anterior, de forma que entren en el proceso de compilacin de nuestra aplicacin.
Veremos algn ejemplo ms adelante.
Carpeta /app/build/
Contiene una serie de elementos de cdigo generados automticamente al compilar el
proyecto. Cada vez que compilamos nuestro proyecto, la maquinaria de compilacin de
Android genera por nosotros una serie de ficheros fuente java dirigidos, entre otras muchas
cosas, al control de los recursos de la aplicacin.Importante: dado que estos ficheros se
generan automticamente tras cada compilacin del proyecto es importante que no se
modifiquen manualmente bajo ninguna circunstancia.

A destacar sobre todo el fichero que aparece desplegado en la imagen anterior, llamado
R.java, donde se define la clase R. Esta clase R contendr en todo momento una serie de
constantes con los identificadores (ID) de todos los recursos de la aplicacin incluidos en la
carpeta /app/src/main/res/, de forma que podamos acceder fcilmente a estos recursos desde
nuestro
cdigo
a
travs
de
dicho dato.
As,
por
ejemplo,
la
constante R.layout.activity_main contendr el ID del layout activity_main.xml contenido
en la carpeta /app/src/main/res/layout/.

20

Y con esto todos los elementos principales de un proyecto Android. No pierdas de vista este
proyecto de ejemplo que hemos creado ya que lo utilizaremos en breve como base para
crear nuestra primera aplicacin. Pero antes, en el siguiente apartado hablaremos de los
componentes software principales con los que podemos construir una aplicacin Android.

Componentes de una aplicacin Android


by Sgoliver on 11/08/2010 in Android, Programacin
En el artculo anterior del curso vimos la estructura de un proyecto Android y aprendimos
dnde colocar cada uno de los elementos que componen una aplicacin, tanto elementos de
software como recursos grficos o de datos. En ste nuevo artculo vamos a centrarnos
especficamente en los primeros, es decir, veremos los distintos tipos de componentes de
software con los que podremos construir una aplicacin Android.
En Java o .NET estamos acostumbrados a manejar conceptos como ventana, control,
eventos o servicios como los elementos bsicos en la construccin de una aplicacin.
Pues bien, en Android vamos a disponer de esos mismos elementos bsicos aunque con un
pequeo cambio en la terminologa y el enfoque. Repasemos los componentes principales
que pueden formar parte de una aplicacin Android [Por claridad, y para evitar confusiones
al consultar documentacin en ingls, intentar traducir lo menos posible los nombres
originales de los componentes].
Activity
Las actividades (activities) representan el componente principal de la interfaz grfica de
una aplicacin Android. Se puede pensar en una actividad como el elemento anlogo a una
ventana o pantalla en cualquier otro lenguaje visual.
View
Las vistas (view) son los componentes bsicos con los que se construye la interfaz grfica
de la aplicacin, anlogo por ejemplo a los controles de Java o .NET. De inicio, Android
pone a nuestra disposicin una gran cantidad de controles bsicos, como cuadros de texto,
botones, listas desplegables o imgenes, aunque tambin existe la posibilidad de extender la
funcionalidad de estos controles bsicos o crear nuestros propios controles personalizados.

21

Service
Los servicios (service) son componentes sin interfaz grfica que se ejecutan en segundo
plano. En concepto, son similares a los servicios presentes en cualquier otro sistema
operativo. Los servicios pueden realizar cualquier tipo de acciones, por ejemplo actualizar
datos, lanzar notificaciones, o incluso mostrar elementos visuales (p.ej. actividades) si se
necesita en algn momento la interaccin con del usuario.
Content Provider
Un proveedor de contenidos (content provider) es el mecanismo que se ha definido en
Android para compartir datos entre aplicaciones. Mediante estos componentes es posible
compartir determinados datos de nuestra aplicacin sin mostrar detalles sobre su
almacenamiento interno, su estructura, o su implementacin. De la misma forma, nuestra
aplicacin podr acceder a los datos de otra a travs de loscontent provider que se hayan
definido.
Broadcast Receiver
Un broadcast receiver es un componente destinado a detectar y reaccionar ante
determinados mensajes o eventos globales generados por el sistema (por ejemplo: Batera
baja, SMS recibido, Tarjeta SD insertada, ) o por otras aplicaciones (cualquier
aplicacin puede generar mensajes (intents, en terminologa Android) broadcast, es decir,
no dirigidos a una aplicacin concreta sino a cualquiera que quiera escucharlo).
Widget
Los widgets son elementos visuales, normalmente interactivos, que pueden mostrarse en la
pantalla principal (home screen) del dispositivo Android y recibir actualizaciones
peridicas. Permiten mostrar informacin de la aplicacin al usuario directamente sobre la
pantalla principal.
Intent
Un intent es el elemento bsico de comunicacin entre los distintos componentes Android
que hemos descrito anteriormente. Se pueden entender como los mensajes o peticiones que
son enviados entre los distintos componentes de una aplicacin o entre distintas
aplicaciones. Mediante un intent se puede mostrar una actividad desde cualquier otra,
iniciar un servicio, enviar un mensaje broadcast, iniciar otra aplicacin, etc.
En el siguiente artculo empezaremos ya a ver algo de cdigo, analizando al detalle una
aplicacin sencilla.

22

Desarrollando una aplicacin Android sencilla (Android Studio)


by Sgoliver on 16/01/2015 in Android, Programacin
El ritmo de actualizaciones de Android Studio es bastante alto, por lo que algunos detalles
de este artculo pueden no ajustarse exactamente a la ltima versin de la aplicacin. Este
artculo se encuentra actualizado para la versin de Android Studio 1.0.2
Despus de instalar nuestro entorno de desarrollo para Android y comentar la estructura
bsica de un proyecto y los diferentes componentes software que podemos utilizar ya es
hora de empezar a escribir algo de cdigo. Y como siempre lo mejor es empezar por
escribir una aplicacin sencilla.
En un principio me plante analizar en este captulo el clsico Hola Mundo pero ms tarde
me pareci que se iban a quedar algunas cosas bsicas en el tintero. As que he versionado a
mi manera el Hola Mundo transformndolo en algo as como un Hola Usuario, que es igual
de sencilla pero aade un par de cosas interesantes de contar. La aplicacin constar de dos
pantallas, por un lado la pantalla principal que solicitar un nombre al usuario y una
segunda pantalla en la que se mostrar un mensaje personalizado para el usuario. As de
sencillo e intil, pero aprenderemos muchos conceptos bsicos, que para empezar no est
mal.
Por dibujarlo para entender mejor lo que queremos conseguir, sera algo tan sencillo como
lo siguiente:

Vamos a partir del proyecto de ejemplo que creamos en un apartado anterior, al que
casualmente llamamos HolaUsuario.
Como ya vimos Android Studio haba creado por nosotros la estructura de carpetas del
proyecto y todos los ficheros necesarios de un Hola Mundo bsico, es decir, una sola
pantalla donde se muestra nicamente un mensaje fijo.
Lo primero que vamos a hacer es disear nuestra pantalla principal modificando la que
Android Studio nos ha creado por defecto. Aunque ya lo hemos comentado de pasada,
recordemos dnde y cmo se define cada pantalla de la aplicacin. En Android, el diseo y
la lgica de una pantalla estn separados en dos ficheros distintos. Por un lado, en el fichero
/src/main/res/layout/activity_main.xml tendremos el diseo puramente visual de la pantalla
definido

como

fichero

XML

por

otro

lado,

en

el

fichero

23

/src/main/java/paquete.java/MainActivity.java, encontraremos el cdigo java que


determina la lgica de la pantalla.
Vamos a modificar en primer lugar el aspecto de la ventana principal de la aplicacin
aadiendo los controles (views) que vemos en el esquema mostrado al principio del
apartado. Para ello, vamos a sustituir el contenido del fichero activity_main.xml por el
siguiente:
1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
android:id="@+id/LytContenedor"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:orientation="vertical" >
6
7
<TextView android:id="@+id/LblNombre"
8
android:layout_width="wrap_content"
9
android:layout_height="wrap_content"
10
android:text="@string/nombre" />
11
12
<EditText android:id="@+id/TxtNombre"
13
android:layout_width="match_parent"
14
android:layout_height="wrap_content"
15
android:inputType="text" />
16
17
<Button android:id="@+id/BtnAceptar"
18
android:layout_width="wrap_content"
19
android:layout_height="wrap_content"
20
android:text="@string/aceptar" />
21
22 </LinearLayout>
Al pegar este cdigo en el fichero de layout aparecern algunos errores marcados en rojo,
en los valores de los atributos android:text. Es normal, lo arreglaremos pronto.
En este XML se definen los elementos visuales que componen la interfaz de nuestra
pantalla principal y se especifican todas sus propiedades. No nos detendremos mucho por
ahora en cada detalle, pero expliquemos un poco lo que vemos en el fichero.
Lo primero que nos encontramos es un elemento LinearLayout. Los layout son elementos
no visibles que determinan cmo se van a distribuir en el espacio los controles que
incluyamos en su interior. Los programadores java, y ms concretamente de Swing,
conocern este concepto perfectamente. En este caso, un LinearLayout distribuir los
controles simplemente uno tras otro y en la orientacin que indique su
propiedad android:orientation, que en este caso ser vertical.

24

Dentro del layout hemos incluido 3 controles: una etiqueta (TextView), un cuadro de texto
(EditText), y un botn (Button). En todos ellos hemos establecido las siguientes
propiedades:
android:id. ID del control, con el que podremos identificarlo ms tarde en nuestro cdigo.
Vemos que el identificador lo escribimos precedido de @+id/. Esto tendr como efecto
que al compilarse el proyecto se genere automticamente una nueva constante en la
clase R para dicho control. As, por ejemplo, como al cuadro de texto le hemos asignado el
ID TxtNombre, podremos ms tarde acceder al l desde nuestro cdigo haciendo referencia
a la constante R.id.TxtNombre.
android:layout_height y android:layout_width. Dimensiones del control con respecto al
layout que lo contiene (height=alto, width=ancho). Esta propiedad tomar normalmente los
valores wrap_content para indicar que las dimensiones del control se ajustarn al
contenido del mismo, o bien match_parent para indicar que el ancho o el alto del control
se ajustar al alto o ancho del layout contenedor respectivamente.
Adems de estas propiedades comunes a casi todos los controles que utilizaremos, en el
cuadro de texto hemos establecido tambin la propiedad android:inputType, que indica qu
tipo de contenido va a albergar el control, en este caso ser texto normal (valor text),
aunque podra haber sido una contrasea (valor textPassword), un telfono (phone),
una fecha (date), .
Por ltimo, en la etiqueta y el botn hemos establecido la propiedad android:text, que
indica el texto que aparece en el control. Y aqu nos vamos a detener un poco, ya que
tenemos dos alternativas a la hora de hacer esto. En Android, el texto de un control se
puede especificar directamente como valor de la propiedad android:text, o bien utilizar
alguna de las cadenas de texto definidas en los recursos del proyecto (como ya vimos, en el
fichero strings.xml), en cuyo caso indicaremos como valor de la propiedad android:text su
identificador precedido del prefijo @string/. Dicho de otra forma, la primera alternativa
habra sido indicar directamente el texto como valor de la propiedad, por ejemplo en la
etiqueta de esta forma:
1 <TextView
2
android:id="@+id/LblNombre"
3
android:layout_width="wrap_content"
4
android:layout_height="wrap_content"
5
android:text="Escribe tu nombre:" />
Y la segunda alternativa, la utilizada en el ejemplo, consistira en definir primero una nueva
cadena de texto en el fichero de recursos /src/main/res/values/strings.xml, por ejemplo con
identificador nombre y valor Escribe tu nombre:.
1 <resources>
2
...
3
<string name="nombre">Escribe tu nombre:</string>
4
...
5 </resources>

25

Y posteriormente indicar el identificador de la cadena como valor de la


propiedad android:text, siempre precedido del prefijo @string/, de la siguiente forma:
1 <TextView
2
android:id="@+id/LblNombre"
3
android:layout_width="wrap_content"
4
android:layout_height="wrap_content"
5
android:text="@string/nombre" />
Esta segunda alternativa nos permite tener perfectamente localizadas y agrupadas todas las
cadenas de texto utilizadas en la aplicacin, lo que nos podra facilitar por ejemplo la
traduccin de la aplicacin a otro idioma. Haremos esto para las dos cadenas de texto
utilizadas en el layout, nombre y aceptar. Una vez incluidas ambas cadenas de texto en
el fichero strings.xml deberan desaparecer los dos errores marcados en rojo que nos
aparecieron antes en la ventana activity_main.xml.
Con esto ya tenemos definida la presentacin visual de nuestra ventana principal de la
aplicacin, veamos ahora la lgica de la misma. Como ya hemos comentado, la lgica de la
aplicacin se definir en ficheros java independientes. Para la pantalla principal ya tenemos
creado un fichero por defecto llamado MainActivity.java. Empecemos por comentar su
cdigo por defecto:
1
package net.sgoliver.android.holausuario;
2
3
import android.support.v7.app.ActionBarActivity;
4
import android.os.Bundle;
5
import android.view.Menu;
6
import android.view.MenuItem;
7
8
public class MainActivity extends ActionBarActivity {
9
10
@Override
11
protected void onCreate(Bundle savedInstanceState) {
12
super.onCreate(savedInstanceState);
13
setContentView(R.layout.activity_main);
14
}
15
16
@Override
17
public boolean onCreateOptionsMenu(Menu menu) {
18
// Inflate the menu; this adds items to the action bar if it is present.
19
getMenuInflater().inflate(R.menu.menu_main, menu);
20
return true;
21
}
22
23
@Override
24
public boolean onOptionsItemSelected(MenuItem item) {
25
// Handle action bar item clicks here. The action bar will

26

26
// automatically handle clicks on the Home/Up button, so long
27
// as you specify a parent activity in AndroidManifest.xml.
28
int id = item.getItemId();
29
30
//noinspection SimplifiableIfStatement
31
if (id == R.id.action_settings) {
32
return true;
33
}
34
35
return super.onOptionsItemSelected(item);
36
}
37 }
Como ya vimos en un apartado anterior, las diferentes pantallas de una aplicacin Android
se definen mediante objetos de tipo Activity. Por tanto, lo primero que encontramos en
nuestro fichero java es la definicin de una nueva clase MainActivity que extiende en este
caso de un tipo especial de Activityllamado ActionBarActivity, que soporta la utilizacin
de la Action Bar en nuestras aplicaciones (la action bar es la barra de ttulo y men superior
que se utiliza en la mayora de aplicaciones Android). El nico mtodo que modificaremos
por ahora de esta clase ser el mtodo onCreate(), llamado cuando se crea por primera vez
la actividad. En este mtodo lo nico que encontramos en principio, adems de la llamada a
su
implementacin
en
la
clase
padre,
es
la
llamada
al
mtodo setContentView(R.layout.activity_main). Con esta llamada estaremos indicando a
Android que debe establecer como interfaz grfica de esta actividad la definida en el
recurso R.layout.activity_main, que no es ms que la que hemos especificado en el
fichero/src/main/res/layout/activity_main.xml. Una vez ms vemos la utilidad de las
diferentes constantes de recursos creadas automticamente en la clase R al compilar el
proyecto.
Adems del mtodo onCreate(), vemos que tambin se sobrescriben los
mtodos onCreateOptionsMenu() y onOptionsItemSelected(), que se utilizan para definir y
gestionar los mens de la aplicacin y/o las opciones de la action bar. Por el momento no
tocaremos estos mtodos, ms adelante en el curso nos ocuparemos de estos temas.
Antes de modificar el cdigo de nuestra actividad principal, vamos a crear una nueva
actividad para la segunda pantalla de la aplicacin anloga a sta primera, a la que
llamaremos SaludoActivity.
Para ello, pulsaremos el botn derecho sobre la carpeta /src/main/java/tu.paquete.java/ y
seleccionaremos la opcin de men New / Activity / Blank Activity.

27

En el cuadro de dilogo que aparece indicaremos el nombre de la actividad, en nuestro caso


SaludoActivity, el nombre de su layout XML asociado (Android Studio crear al mismo
tiempo tanto el layout XML como la clase java), que llamaremos activity_saludo, el ttulo
de la actividad que aparecer en la action bar. El resto de opciones las podemos dejar por
ahora con sus valores por defecto.

Pulsaremos Finish y Android Studio crear los nuevos ficheros SaludoActivity.java y


activity_saludo.xml en sus carpetas correspondientes.
De igual forma que hicimos con la actividad principal, definiremos en primer lugar la
interfaz de la segunda pantalla, abriendo el fichero activity_saludo.xml, y aadiendo esta
vez tan slo un LinearLayout como contenedor y una etiqueta (TextView) para mostrar el
mensaje personalizado al usuario.
Para esta segunda pantalla el cdigo que incluiramos sera el siguiente:
1
2
3
4
5
6

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/LytContenedorSaludo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

28

7
<TextView android:id="@+id/TxtSaludo"
8
android:layout_width="wrap_content"
9
android:layout_height="wrap_content"
10
android:text="" />
11
12 </LinearLayout>
Por su parte, si revisamos ahora el cdigo de la clase java SaludoActivity veremos que es
anlogo a la actividad principal:
1
public class SaludoActivity extends ActionBarActivity {
2
3
@Override
4
protected void onCreate(Bundle savedInstanceState) {
5
super.onCreate(savedInstanceState);
6
setContentView(R.layout.activity_saludo);
7
}
8
9
//...
10 }
Sigamos. Por ahora, el cdigo incluido en estas clases lo nico que hace es generar la
interfaz de la actividad. A partir de aqu nosotros tendremos que incluir el resto de la lgica
de la aplicacin.
Y vamos a empezar con la actividad principal MainActivity, obteniendo una referencia a
los diferentes controles de la interfaz que necesitemos manipular, en nuestro caso slo el
cuadro de texto y el botn. Para ello definiremos ambas referencias como atributos de la
clase y para obtenerlas utilizaremos el mtodo findViewById() indicando el ID de cada
control, definidos como siempre en la clase R. Todo esto lo haremos dentro del
mtodo onCreate() de la clase MainActivity, justo a continuacin de la llamada
a setContentView() que ya comentamos.
1
2
3
4
5
6
7
8
9
10
11

package net.sgoliver.android.holausuario;
//..
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends ActionBarActivity {
private EditText txtNombre;
private Button btnAceptar;

29

12
@Override
13
protected void onCreate(Bundle savedInstanceState) {
14
super.onCreate(savedInstanceState);
15
setContentView(R.layout.activity_main);
16
17
//Obtenemos una referencia a los controles de la interfaz
18
txtNombre = (EditText)findViewById(R.id.TxtNombre);
19
btnAceptar = (Button)findViewById(R.id.BtnAceptar);
20
}
21
22
//...
23 }
Como vemos, hemos aadido tambin varios import adicionales (los de las
clases Button y EditText) para tener acceso a todas las clases utilizadas.
Una vez tenemos acceso a los diferentes controles, ya slo nos queda implementar las
acciones a tomar cuando pulsemos el botn de la pantalla. Para ello, continuando el cdigo
anterior, y siempre dentro del mtodo onCreate(), implementaremos el evento onClick de
dicho botn. Este botn tendr que ocuparse de abrir la actividad SaludoActivity pasndole
toda la informacin necesaria. Veamos cmo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

package net.sgoliver.android.holausuario;
//...
import android.content.Intent;
public class MainActivity extends ActionBarActivity {
private EditText txtNombre;
private Button btnAceptar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Obtenemos una referencia a los controles de la interfaz
txtNombre = (EditText)findViewById(R.id.TxtNombre);
btnAceptar = (Button)findViewById(R.id.BtnAceptar);
//Implementamos el evento click del botn
btnAceptar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

30

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

//Creamos el Intent
Intent intent =
new Intent(MainActivity.this, SaludoActivity.class);
//Creamos la informacin a pasar entre actividades
Bundle b = new Bundle();
b.putString("NOMBRE", txtNombre.getText().toString());
//Aadimos la informacin al intent
intent.putExtras(b);
//Iniciamos la nueva actividad
startActivity(intent);
}
});
}
}

Como ya indicamos en el apartado anterior, la comunicacin entre los distintos


componentes y aplicaciones en Android se realiza mediante intents, por lo que el primer
paso ser crear un objeto de este tipo. Existen varias variantes del constructor de la
clase Intent, cada una de ellas dirigida a unas determinadas acciones. En nuestro caso
particular vamos a utilizar el intent para iniciar una actividad desde otra actividad de la
misma aplicacin, para lo que pasaremos a su constructor una referencia a la propia
actividad llamadora (MainActivity.this),
y
la
clase
de
la
actividad llamada (SaludoActivity.class).
Si quisiramos tan slo mostrar una nueva actividad ya tan slo nos quedara llamar
a startActivity() pasndole como parmetro el intent creado. Pero en nuestro ejemplo
queremos tambin pasarle cierta informacin a la actividad llamada, concretamente el
nombre que introduzca el usuario en el cuadro de texto de la pantalla principal. Para hacer
esto vamos a crear un objeto Bundle, que puede contener una lista de pares clave-valor con
toda la informacin a pasar entre actividades. En nuestro caso slo aadiremos un dato de
tipo String mediante el mtodo putString(clave, valor). Como clave para nuestro dato yo he
elegido el literal NOMBRE aunque podis utilizar cualquier otro literal descriptivo. Por
su parte, el valor de esta clave lo obtendremos consultando el contenido del cuadro de texto
de la actividad principal, lo que podemos conseguir llamando a su mtodo getText() y
convirtiendo este contenido a texto mediante toString() (ms adelante en el curso veremos
por qu es necesaria esta conversin).
Tras esto aadiremos la informacin al intent mediante el mtodo putExtras(). Si
necesitramos pasar ms datos entre una actividad y otra no tendramos ms que repetir
estos pasos para todos los parmetros necesarios.

31

Con esto hemos finalizado ya actividad principal de la aplicacin, por lo que pasaremos ya
a la secundaria. Comenzaremos de forma anloga a la anterior, ampliando el
mtodo onCreate() obteniendo las referencias a los objetos que manipularemos, esta vez
slo la etiqueta de texto. Tras esto viene lo ms interesante, debemos recuperar la
informacin pasada desde la actividad principal y asignarla como texto de la etiqueta. Para
ello accederemos en primer lugar al intent que ha originado la actividad actual mediante el
mtodo getIntent() y recuperaremos su informacin asociada (objeto Bundle) mediante el
mtodo getExtras().
Hecho esto tan slo nos queda construir el texto de la etiqueta mediante su
mtodo setText(texto) y recuperando el valor de nuestra clave almacenada en el
objeto Bundle mediante getString(clave).
1
package net.sgoliver.android.holausuario;
2
3
//...
4
import android.widget.TextView;
5
6
public class SaludoActivity extends ActionBarActivity {
7
8
private TextView txtSaludo;
9
10
@Override
11
protected void onCreate(Bundle savedInstanceState) {
12
super.onCreate(savedInstanceState);
13
setContentView(R.layout.activity_saludo);
14
15
//Localizar los controles
16
txtSaludo = (TextView)findViewById(R.id.TxtSaludo);
17
18
//Recuperamos la informacin pasada en el intent
19
Bundle bundle = this.getIntent().getExtras();
20
21
//Construimos el mensaje a mostrar
22
txtSaludo.setText("Hola " + bundle.getString("NOMBRE"));
23
}
24
25
//...
26 }
Con esto hemos concluido la lgica de las dos pantallas de nuestra aplicacin y tan slo nos
queda un paso importante para finalizar nuestro desarrollo. Como ya indicamos en un
apartado anterior, toda aplicacin Android utiliza un fichero especial en formato XML
(AndroidManifest.xml) para definir, entre otras cosas, los diferentes elementos que la

32

componen. Por tanto, todas las actividades de nuestra aplicacin deben quedar
convenientemente definidas en este fichero. En este caso, Android Studio se debe haber
ocupado por nosotros de definir ambas actividades en el fichero, pero lo revisaremos para
as echar un vistazo al contenido.
Si abrimos el fichero AndroidManifest.xml veremos que contiene un elemento
principal <Application> que debe incluir varios elementos <Activity>, uno por cada
actividad incluida en nuestra aplicacin. En este caso, comprobamos como efectivamente
Android Studio ya se ha ocupado de esto por nosotros, aunque este fichero s podramos
modificarlo a mano para hacer ajustes si fuera necesario.
1
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
package="net.sgoliver.android.holausuario" >
3
4
<application
5
android:allowBackup="true"
6
android:icon="@drawable/ic_launcher"
7
android:label="@string/app_name"
8
android:theme="@style/AppTheme" >
9
<activity
10
android:name=".MainActivity"
11
android:label="@string/app_name" >
12
<intent-filter>
13
<action android:name="android.intent.action.MAIN" />
14
<category android:name="android.intent.category.LAUNCHER" />
15
</intent-filter>
16
</activity>
17
<activity
18
android:name=".SaludoActivity"
19
android:label="@string/title_activity_saludo" >
20
</activity>
21
</application>
22 </manifest>
Podemos ver como para cada actividad se indica entre otras cosas el nombre de su clase
java asociada como valor del atributo android:name, y su ttulo mediante el
atributo android:label, ms adelante veremos qu opciones adicionales podemos
especificar. Vemos una vez ms cmo el ttulo de las actividades se indica como referencia
a cadenas de caracteres definidas como recursos, que deben estar incluidas como ya hemos
comentado anteriormente en el fichero /main/res/values/strings.xml
El ltimo elemento que revisaremos de nuestro proyecto, aunque tampoco tendremos que
modificarlo por ahora, ser el fichero build.gradle. Pueden existir varios ficheros llamados
as en nuestra estructura de carpetas, a distintos niveles, pero normalmente siempre

33

accederemos al que est al nivel ms interno, en nuestro caso el que est dentro del mdulo
app. Veamos qu contiene:
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

apply plugin: 'com.android.application'


android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig {
applicationId "net.sgoliver.android.holausuario"
minSdkVersion 15
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
}

Gradle es el nuevo sistema de compilacin y construccin que ha adoptado Google para


Android Studio. Pero no es un sistema especfico de Android, sino que puede utilizarse
con otros lenguajes/plataformas. Por tanto, lo primero que indicamos en este fichero es que
utilizaremos el plugin para Android mediante la sentencia apply plugin. A continuacin
definiremos varias opciones especficas de Android, como las versiones de la API mnima
(minSdkVersion), API objetivo (targetSdkVersion), y API de compilacin
(compileSdkVersion) que utilizaremosen el proyecto, la versin de las build
tools (buildToolsVersion) que queremos utilizar (es uno de los componentes que podemos
descargar/actualizar desde el SDK Manager), la versin tanto interna (versionCode) como
visible (versionName) de la aplicacin, o la configuracin de ProGuard si estamos haciendo
uso de l (no nos preocupamos por ahora de esto). Durante el curso iremos viendo con ms
detalle todos estos elementos. El ultimo elemento llamado dependencies tambin es
importante y nos servir entre otras cosas para definir las libreras externas que utilizaremos

34

en la aplicacin. Por defecto vemos que se aade la librera de


compatibilidad appcompatque nos permite utilizar la Action Bar en la mayora de versiones
de Android, y todos los fichero .jar que incluyamos en la carpeta /libs.
Llegados aqu, y si todo ha ido bien, deberamos poder ejecutar el proyecto sin errores y
probar nuestra aplicacin en el emulador, pero para ello tendremos que definir primero uno.
Vamos a describir los pasos para hacerlo.
Para poder probar aplicaciones Android en nuestro PC, sin tener que recurrir a un
dispositivo fsico, tenemos que definir lo que se denominan AVD (Android Virtual Device).
Para crear un AVD seleccionaremos el men Tools / Android / AVD Manager. Si es la
primera vez que accedemos a esta herramienta veremos la pantalla siguiente:

Pulsando el botn central Create a virtual device accederemos al asistente para crear un
AVD. En el primer paso tendremos que seleccionar a la izquierda qu tipo de dispositivo
queremos que simule nuestro AVD (telfono, tablet, reloj, ) y el tamao, resolucin, y
densidad de pxeles de su pantalla. En mi caso seleccionar por ejemplo las caractersticas
de un Nexus 4 y pasaremos al siguiente paso pulsando Next.

En la siguiente pantalla seleccionaremos la versin de Android que utilizar el AVD.


Aparecern directamente disponibles las que instalamos desde el SDK Manager al instalar
el entorno, aunque tenemos la posibilidad de descargar e instalar nuevas versiones desde
esta misma pantalla. En mi caso utilizar KitKat (API 19) para este primer AVD (podemos
crear tantos como queramos para probar nuestras aplicaciones sobre distintas condiciones).

35

En el siguiente paso del asistente podremos configurar algunas caractersticas ms del


AVD, como por ejemplo la cantidad de memoria que tendr disponible, si simular tener
cmara frontal y/o trasera, teclado fsico, Recomiendo pulsar el botn Show Advanced
Settings para ver todas las opciones disponibles. Si quieres puedes ajustar cualquiera de
estos parmetros, pero por el momento os recomiendo dejar todas las opciones por defecto.
Tan slo nos aseguraremos de tener activada la opcin Use Host GPU con la que
normalmente conseguiremos un mayor rendimiento del emulador.

Tras pulsar el botn Finish tendremos ya configurado nuestro AVD, por lo que podremos
comenzar a probar nuestras aplicaciones sobre l.

Para ello pulsaremos simplemente el men Run / Run app (o la tecla rpida Mays+F10).
Android Studio nos preguntar en qu dispositivo queremos ejecutar la aplicacin y nos
mostrar dos listas. La primera de ellas con los dispositivos que haya en ese momento en
funcionamiento (por ejemplo si ya tenamos un emulador funcionando) y una lista

36

desplegable con el resto de AVDs configurados en nuestro entorno. Podremos seleccionar


cualquiera de los emuladores disponibles en cualquiera de las dos listas. Lo normal ser
mantener un emulador siempre abierto y seleccionarlo de la primera lista cada vez que
ejecutemos la aplicacin.
Elegir para este ejemplo el AVD que acabamos de crear y configurar. Es posible que la
primera ejecucin se demore unos minutos, todo depender de las caractersticas de vuestro
PC, as que paciencia.

Si todo va bien, tras una pequea (o no tan pequea) espera aparecer el emulador de
Android y se iniciar automticamente nuestra aplicacin (si se inicia el emulador pero no
se ejecuta automticamente la aplicacin podemos volver a ejecutarla desde Android
Studio, mediante el men Run, sin cerrar el emulador ya abierto).
Podemos probar a escribir un nombre y pulsar el botn Aceptar para comprobar si el
funcionamiento es el correcto.

37

Y con esto terminamos por ahora. Espero que esta aplicacin de ejemplo os sea de ayuda
para aprender temas bsicos en el desarrollo para Android, como por ejemplo la definicin
de la interfaz grfica, el cdigo java necesario para acceder y manipular los elementos de
dicha interfaz, y la forma de comunicar diferentes actividades de Android. En los apartados
siguientes veremos algunos de estos temas de forma mucho ms especfica.
Podis consultar online y descargar el cdigo fuente completo de este artculo desde github.

Interfaz de Usuario en Android


Interfaz de usuario en Android: Layouts
by Sgoliver on 17/08/2010 in Android, Programacin
En el artculo anterior del curso, donde desarrollamos una sencilla aplicacin Android
desde cero, ya hicimos algunos comentarios sobre los layouts. Como ya indicamos,
los layouts son elementos no visuales destinados a controlar la distribucin, posicin y
dimensiones de los controles que se insertan en su interior. Estos componentes extienden a
la clase base ViewGroup, como muchos otros componentes contenedores, es decir, capaces
de contener a otros controles. En el post anterior conocimos la existencia de un tipo
concreto de layout, LinearLayout, aunque Android nos proporciona algunos otros. Vemos
cuntos y cules.

38

FrameLayout
ste es el ms simple de todos los layouts de Android. Un FrameLayout coloca todos sus
controles hijos alineados con su esquina superior izquierda, de forma que cada control
quedar oculto por el control siguiente (a menos que ste ltimo tenga transparencia). Por
ello, suele utilizarse para mostrar un nico control en su interior, a modo de contenedor
(placeholder) sencillo para un slo elemento sustituible, por ejemplo una imagen.
Los componentes incluidos en un FrameLayout podrn establecer sus propiedades
android:layout_width y android:layout_height,
que podrn tomar
los
valores
match_parent (para que el control hijo tome la dimensin de su layout contenedor) o
wrap_content (para que el control hijo tome la dimensin de su contenido). Veamos un
ejemplo:
Ejemplo:
1
2
3
4
5
6
7
8
9
10
11

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText android:id="@+id/TxtNombre"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="text" />
</FrameLayout>

Con el cdigo anterior conseguimos un layout tan sencillo como el siguiente:

LinearLayout
El siguiente tipo de layout en cuanto a nivel de complejidad es el LinearLayout. Este layout
apila uno tras otro todos sus elementos hijos en sentido horizontal o vertical segn se
establezca su propiedad android:orientation.

39

Al igual que en un FrameLayout, los elementos contenidos en un LinearLayout pueden


establecer sus propiedades android:layout_width y android:layout_height para determinar
sus dimensiones dentro del layout.
1
<LinearLayout
2
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:orientation="vertical">
6
7
<EditText android:id="@+id/TxtNombre"
8
android:layout_width="match_parent"
9
android:layout_height="match_parent" />
10
11
<Button android:id="@+id/BtnAceptar"
12
android:layout_width="wrap_content"
13
android:layout_height="match_parent" />
14
15
</LinearLayout>
Pero en el caso de un LinearLayout, tendremos otro parmetro con el que jugar, la
propiedadandroid:layout_weight. Esta propiedad nos va a permitir dar a los elementos
contenidos en el layout unas dimensiones proporcionales entre ellas. Esto es ms dificil de
explicar que de comprender con un ejemplo. Si incluimos en un LinearLayout vertical dos
cuadros de texto (EditText) y a uno de ellos le establecemos un layout_weight=1 y al otro
un layout_weight=2 conseguiremos como efecto que toda la superficie del layout quede
ocupada por los dos cuadros de texto y que adems el segundo sea el doble (relacin entre
sus propiedades weight) de alto que el primero.
1
<LinearLayout
2
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:orientation="vertical">
6
7
<EditText android:id="@+id/TxtDato1"
8
android:layout_width="match_parent"
9
android:layout_height="match_parent"
10
android:inputType="text"
11
android:layout_weight="1" />
12
13
<EditText android:id="@+id/TxtDato2"
14
android:layout_width="match_parent"
15
android:layout_height="match_parent"
16
android:inputType="text"
17
android:layout_weight="2" />

40

18
19
</LinearLayout>
Con el cdigo anterior conseguiramos un layout como el siguiente:

As pues, a pesar de la simplicidad aparente de este layout resulta ser lo suficiente verstil
como para sernos de utilidad en muchas ocasiones.
TableLayout
Un TableLayout permite distribuir sus elementos hijos de forma tabular, definiendo las filas
y columnas necesarias, y la posicin de cada componente dentro de la tabla.
La estructura de la tabla se define de forma similar a como se hace en HTML, es decir,
indicando las filas que compondrn la tabla (objetos TableRow), y dentro de cada fila las
columnas necesarias, con la salvedad de que no existe ningn objeto especial para definir
una columna (algo as como unTableColumn) sino que directamente insertaremos los
controles necesarios dentro del TableRow y cada componente insertado (que puede ser un
control sencillo o incluso otro ViewGroup) corresponder a una columna de la tabla. De
esta forma, el nmero final de filas de la tabla se corresponder con el nmero de
elementos TableRow insertados, y el nmero total de columnas quedar determinado por el
nmero de componentes de la fila que ms componentes contenga.
Por norma general, el ancho de cada columna se corresponder con el ancho del mayor
componente de dicha columna, pero existen una serie de propiedades que nos ayudarn a
modificar este comportamiento:

android:stretchColumns. Indicar las columnas que pueden expandir para absorver el


espacio libre dejado por las dems columnas a la derecha de la pantalla.
android:shrinkColumns. Indicar las columnas que se pueden contraer para dejar espacio al
resto de columnas que se puedan salir por la derecha de la palntalla.
android:collapseColumns. Indicar las columnas de la tabla que se quieren ocultar
completamente.

41

Todas estas propiedades del TableLayout pueden recibir una lista de ndices de columnas
separados por comas (ejemplo: android:stretchColumns=1,2,3) o un asterisco para indicar
que debe aplicar a todas las columnas (ejemplo: android:stretchColumns=*).
Otra caracterstica importante es la posibilidad de que una celda determinada pueda ocupar
el espacio de varias columnas de la tabla (anlogo al atributo colspan de HTML). Esto se
indicar mediante la propiedad android:layout_span del componente concreto que deber
tomar dicho espacio.
Veamos un ejemplo con varios de estos elementos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableRow>
<TextView android:text="Celda 1.1" />
<TextView android:text="Celda 1.2" />
<TextView android:text="Celda 1.3" />
</TableRow>
<TableRow>
<TextView android:text="Celda 2.1" />
<TextView android:text="Celda 2.2" />
<TextView android:text="Celda 2.3" />
</TableRow>
<TableRow>
<TextView android:text="Celda 3.1"
android:layout_span="2" />
<TextView android:text="Celda 3.2" />
</TableRow>
</TableLayout>

El layout resultante del cdigo anterior sera el siguiente:

42

GridLayout
Este tipo de layout fue incluido a partir de la API 14 (Android 4.0) y sus caractersticas son
similares alTableLayout, ya que se utiliza igualmente para distribuir los diferentes
elementos de la interfaz de forma tabular, distribuidos en filas y columnas. La diferencia
entre ellos estriba en la forma que tiene elGridLayout de colocar y distribuir sus elementos
hijos en el espacio disponible. En este caso, a diferencia del TableLayout indicaremos el
nmero
de
filas
y
columnas
como
propiedades
del
layout,
medianteandroid:rowCount y android:columnCount. Con estos datos ya no es necesario
ningn tipo de elemento para indicar las filas, como hacamos con el
elemento TableRow del TableLayout, sino que los diferentes elementos hijos se irn
colocando
ordenadamente
por
filas
o
columnas
(dependiendo
de
la
propiedad android:orientation) hasta completar el nmero de filas o columnas indicadas en
los atributos anteriores. Adicionalmente, igual que en el caso anterior, tambin tendremos
disponibles las propiedades android:layout_rowSpan y android:layout_columnSpan para
conseguir que una celda ocupe el lugar de varias filas o columnas.
Existe tambin una forma de indicar de forma explcita la fila y columna que debe ocupar
un determinado elemento hijo contenido en el GridLayout, y se consigue utilizando los
atributos android:layout_row yandroid:layout_column. De cualquier forma, salvo para
configuraciones complejas del grid no suele ser necesario utilizar estas propiedades.
Con todo esto en cuenta, para conseguir una distribucin equivalente a la del ejemplo
anterior delTableLayout, necesitaramos escribir un cdigo como el siguiente:
1
2
3
4
5
6
7

<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="2"
android:columnCount="3"
android:orientation="horizontal" >

43

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<TextView android:text="Celda 1.1" />


<TextView android:text="Celda 1.2" />
<TextView android:text="Celda 1.3" />
<TextView android:text="Celda 2.1" />
<TextView android:text="Celda 2.2" />
<TextView android:text="Celda 2.3" />
<TextView android:text="Celda 3.1"
android:layout_columnSpan="2" />
<TextView android:text="Celda 3.2" />
</GridLayout>

RelativeLayout
El ltimo tipo de layout que vamos a ver es el RelativeLayout. Este layout permite
especificar la posicin de cada elemento de forma relativa a su elemento padre o a
cualquier otro elemento incluido en el propio layout. De esta forma, al incluir un nuevo
elemento X podremos indicar por ejemplo que debe colocarsedebajo del elemento
Y y alineado a la derecha del layout padre. Veamos esto en el ejemplo siguiente:
1
<RelativeLayout
2
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent" >
5
6
<EditText android:id="@+id/TxtNombre"
7
android:layout_width="match_parent"
8
android:layout_height="wrap_content"
9
android:inputType="text" />
10
11
<Button android:id="@+id/BtnAceptar"
12
android:layout_width="wrap_content"
13
android:layout_height="wrap_content"
14
android:layout_below="@id/TxtNombre"
15
android:layout_alignParentRight="true" />
16
</RelativeLayout>
En

el

ejemplo,

el

botn BtnAceptar se

colocar

debajo

del

cuadro

de

texto TxtNombre(android:layout_below=@id/TxtNombre) y alineado a la derecha del


layout padre (android:layout_alignParentRight=true), Quedara algo as:

44

Al igual que estas tres propiedades, en un RelativeLayout tendremos un sinfn de


propiedades para colocar cada control justo donde queramos. Veamos las principales [creo
que sus propios nombres explican perfectamente la funcin de cada una]:

Posicin relativa a otro control:

android:layout_above
android:layout_below
android:layout_toLeftOf
android:layout_toRightOf
android:layout_alignLeft
android:layout_alignRight
android:layout_alignTop
android:layout_alignBottom
android:layout_alignBaseline

Posicin relativa al layout padre:

android:layout_alignParentLeft
android:layout_alignParentRight
android:layout_alignParentTop
android:layout_alignParentBottom
android:layout_centerHorizontal
android:layout_centerVertical
android:layout_centerInParent
Por ltimo indicar que cualquiera de los tipos de layout anteriores poseen otras propiedades
comunes como por ejemplo los mrgenes exteriores (margin) e interiores (padding) que
pueden establecerse mediante los siguientes atributos:

Opciones de margen exterior:

android:layout_margin
android:layout_marginBottom
android:layout_marginTop

45

android:layout_marginLeft
android:layout_marginRight

Opciones de margen interior:

android:padding
android:paddingBottom
android:paddingTop
android:paddingLeft
android:paddingRight
Existen otros layouts algo ms sofisticados a los que dedicaremos artculos especficos un
poco ms adelante, como por ejemplo el DrawerLayout para aadir mens laterales
deslizantes.
Tambin en prximos artculos veremos otros elementos comunes que extienden
a ViewGroup, como por ejemplo las vistas de tipo lista (ListView), de tipo grid
(GridView), y las pestaas o tabs (TabHost/TabWidget).

Interfaz de usuario en Android: Controles bsicos (I)


by Sgoliver on 19/08/2010 in Android, Programacin
En el captulo anterior del curso vimos los distintos tipos de layouts con los que contamos
en Android para distribuir los controles de la interfaz por la pantalla del dispositivo. En los
prximos captulos vamos a hacer un repaso de los diferentes controles que pone a nuestra
disposicin la plataforma de desarrollo de este sistema operativo. Empezaremos con los
controles ms bsicos y seguiremos posteriormente con algunos algo ms elaborados.
En este primer post sobre el tema nos vamos a centrar en los diferentes tipos de botones y
cmo podemos personalizarlos. El SDK de Android nos proporciona tres tipos de botones:
los clsicos de texto (Button), los que pueden contener una imagen (ImageButton), y los de
tipo on/off (ToggleButton y Switch).
No vamos a comentar mucho sobre ellos dado que son controles de sobra conocidos por
todos, ni vamos a enumerar todas sus propiedades porque existen decenas. A modo de
referencia, a medida que los vayamos comentando ir poniendo enlaces a su pgina de la
documentacin oficial de Android para poder consultar todas sus propiedades en caso de
necesidad.
Control

Button [API]

46

Un control de tipo Button es el botn ms bsico que podemos utilizar y normalmente


contiene un simple texto. En el ejemplo siguiente definimos un botn con el texto Click
asignando su propiedadandroid:text. Adems de esta propiedad podramos utilizar muchas
otras como el color de fondo (android:background), estilo de fuente (android:typeface),
color de fuente (android:textcolor), tamao de fuente (android:textSize), etc.
1 <Button android:id="@+id/BtnBotonSimple"
2
android:text="@string/click"
3
android:layout_width="wrap_content"
4
android:layout_height="wrap_content" />
Este botn quedara como se muestra en la siguiente imagen:

Control ToggleButton [API]


Un control de tipo ToggleButton es un tipo de botn que puede permanecer en dos posibles
estados, pulsado o no_pulsado. En este caso, en vez de definir un slo texto para el control
definiremos dos, dependiendo de su estado. As, podremos asignar las
propiedades android:textOn y android:textoOff para definir ambos textos. Veamos un
ejemplo a continuacin.
1 <ToggleButton android:id="@+id/BtnToggle"
2
android:textOn="@string/on"
3
android:textOff="@string/off"
4
android:layout_width="wrap_content"
5
android:layout_height="wrap_content" />
El botn se mostrara de alguna de las dos formas siguientes, dependiendo de su estado:

Control Switch [API]


Un control Switch es muy similar al ToggleButton anterior, donde tan slo cambia su
aspecto visual, que en vez de mostrar un estado u otro sobre el mismo espacio, se muestra
en forma de deslizador o interruptor. Su uso sera completamente anlogo al ya comentado:
1 <Switch android:id="@+id/BtnSwitch"

47

2
android:textOn="@string/on"
3
android:textOff="@string/off"
4
android:layout_width="wrap_content"
5
android:layout_height="wrap_content" />
Y su aspecto sera el siguiente:

Control ImageButton [API]


En un control de tipo ImageButton podremos definir una imagen a mostrar en vez de un
texto, para lo que deberemos asignar la propiedad android:src. Normalmente asignaremos
esta propiedad con el descriptor de algn recurso que hayamos incluido en las
carpetas /res/drawable. As, por ejemplo, en nuestro caso vamos a incluir una imagen
llamada
ic_estrella.png por
lo
que
haremos
referencia
al
recurso
@drawable/ic_estrella. Adicionalmente, al tratarse de un control de tipo imagen tambin
deberamos acostumbrarnos a asignar la propiedad android:contentDescription con una
descripcin textual de la imagen, de forma que nuestra aplicacin sea lo ms accesible
posible.
1 <ImageButton android:id="@+id/BtnImagen"
2
android:layout_width="wrap_content"
3
android:layout_height="wrap_content"
4
android:contentDescription="@string/icono_ok"
5
android:src="@drawable/ic_estrella" />
En una aplicacin el botn anterior se mostrara de la siguiente forma:

Aadir imgenes a un proyecto de Android Studio


Android Studio incorpora una utilidad llamada Asset Studio con la que podemos aadir
rpidamente a un proyecto algunas imgenes o iconos estandar de entre una lista bastante
amplia de muestras disponibles, o utilizar nuestras propias imgenes personalizadas.
Podemos acceder a esta utilidad haciendo por ejemplo click derecho sobre la
carpeta /main/res del proyecto y seleccionando la opcin New / Image Asset:

48

Esto nos da acceso a Asset Studio, donde podremos indicar el tipo de imagen a aadir
(icono de lanzador, icono de action bar, icono de notificacin, ), el origen de la imagen
(Image = Fichero externo, Clipart = Coleccin de iconos estandar, Text = Texto
personalizado), el tema de nuestra aplicacin (lo que afectar al color de fondo y primer
plano de los iconos seleccionados), y el nombre del recurso a incluir en el proyecto.
As, en nuestro caso de ejemplo, seleccion Clipart como origen de la imagen, seleccion
el icono de estrella mediante el botn Choose, e indiqu el nombre ic_estrella:

Al pulsar el botn Next el sistema consulta en qu mdulo del proyecto y en qu carpeta de


recursos del mdulo colocar los recursos creados para el icono. Adems, como podemos
ver en la siguiente imagen Asset Studio se encarga de crear el icono para las distintas
densidades de pixeles y colocarlo en su carpeta de recursos correspondiente.

49

Cabe decir adems, que aunque existe este tipo especfico de botn para imgenes, tambin
es posible aadir una imagen a un botn normal de tipo Button, a modo de elemento
suplementario al texto (compound drawable). Por ejemplo, si quisiramos aadir un icono a
la
izquierda
del
texto
de
un
botn
utilizaramos
la
propiedad android:drawableLeft indicando como valor el descriptor (ID) de la imagen que
queremos mostrar, y si fuera necesario podramos indicar tambin el espacio entre la
imagen y el texto mediante la propiedad android:drawablePadding:
1 <Button android:id="@+id/BtnBotonMasImagen"
2
android:text="@string/click"
3
android:drawableLeft="@drawable/ic_estrella"
4
android:drawablePadding="5dp"
5
android:layout_width="wrap_content"
6
android:layout_height="wrap_content" />
El botn mostrado en este caso sera similar a ste:

Eventos de un botn
Como podis imaginar, aunque estos controles pueden lanzar muchos otros eventos, el ms
comn de todos ellos y el que querremos capturar en la mayora de las ocasiones es el
evento onClick, que se lanza cada vez que el usuario pulsa el botn. Para definir la lgica
de
este
evento
tendremos
que
implementarla
definiendo
un
nuevo
objeto View.OnClickListener() y asocindolo al botn mediante el mtodo
setOnClickListener(). La forma ms habitual de hacer esto es la siguiente:

50

1
2
3
4
5
6
7
8

btnBotonSimple = (Button)findViewById(R.id.BtnBotonSimple);
btnBotonSimple.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0)
{
lblMensaje.setText("Botn Simple pulsado!");
}
});

En el caso de un botn de tipo ToggleButton o Switch suele ser de utilidad conocer en qu


estado ha quedado el botn tras ser pulsado, para lo que podemos utilizar su
mtodo isChecked(). En el siguiente ejemplo se comprueba el estado del botn tras ser
pulsado y se realizan acciones distintas segn el resultado.
1
2
3
4
5
6
7
8
9
10
11

btnToggle = (ToggleButton)findViewById(R.id.BtnToggle);
btnToggle.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0)
{
if(btnToggle.isChecked())
lblMensaje.setText("Botn Toggle: ON");
else
lblMensaje.setText("Botn Toggle: OFF");
}
});

Personalizar el aspecto un botn (y otros controles)


En las imgenes mostradas durante este apartado hemos visto el aspecto que presentan por
defecto los diferentes tipos de botones disponibles. Pero, y si quisiramos personalizar su
aspecto ms all de cambiar un poco el tipo o el color de la letra o el fondo?
Para cambiar la forma de un botn podramos simplemente asignar una imagen a la
propiedadandroid:background, pero esta solucin no nos servira de mucho porque siempre
se mostrara la misma imagen incluso con el botn pulsado, dando poca sensacin de
elemento clickable.
La solucin perfecta pasara por tanto por definir diferentes imgenes de fondo
dependiendo del estado del botn. Pues bien, Android nos da total libertad para hacer esto
mediante el uso de selectores. Un selectorse define mediante un fichero XML localizado en

51

la carpeta /res/drawable, y en l se pueden establecer los diferentes valores de una


propiedad determinada de un control dependiendo de su estado.
Por ejemplo, si quisiramos dar un aspecto diferente a nuestro botn ToggleButton, para
que sea de color azul y con esquinas redondeadas, podramos disear las imgenes
necesarias para los estados pulsado (en el ejemplo toggle_on.9.png) y no pulsado (en el
ejemplo toggle_off.9.png) y crear un selector como el siguiente:
1
2
3
4

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false" android:drawable="@drawable/toggle_off"
/>
<item android:state_checked="true" android:drawable="@drawable/toggle_on" />
</selector>

En el cdigo anterior vemos cmo se asigna a cada posible estado del botn una imagen (un
elemento drawable) determinada. As, por ejemplo, para el estado pulsado
(state_checked=true) se asigna la imagen toggle_on.
Este selector lo guardamos por ejemplo en un fichero llamado toggle_style.xml y lo
colocamos como un recurso ms en nuestra carpeta de recursos /res/drawable. Hecho esto,
tan slo bastara hacer referencia a este nuevo recurso que hemos creado en la
propiedad android:background del botn:
1
2
3
4
5
6

<ToggleButton android:id="@+id/BtnPersonalizado"
android:textOn="@string/on"
android:textOff="@string/off"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/toggle_style" />

En
la
siguiente
imagen
vemos
el
aspecto
nuestro ToggleButton personalizado con los cambios indicados:

por

defecto

de

Botones sin borde


Otra forma de personalizar los controles en Android es utilizando estilos. Los estilos
merecen un captulo a parte, pero comentaremos aqu algunos muy utilizados en las ltimas
versiones de Android, concretamente en el tema que nos ocupa de los botones.
En determinadas ocasiones, como por ejemplo cuando se utilizan botones dentro de otros
elementos como listas o tablas, es interesante contar con todas la funcionalidad de un botn

52

pero prescindiendo sus bordes de forma que adquiera un aspecto plano y se intergre mejor
con
el
diseo
de
la
interfaz.
Para
ello,
podemos
utilizar
el
estilo borderlessButtonStyle como estilo del botn (propiedad style), de forma que ste se
mostrar sin bordes pero conservar otros detalles como el cambio de apariencia al ser
pulsado. Veamos cmo se definira por ejemplo un ImageButton sin borde:
1 <ImageButton android:id="@+id/BtnSinBorde"
2
android:layout_width="wrap_content"
3
android:layout_height="wrap_content"
4
android:contentDescription="@string/icono_ok"
5
android:src="@drawable/ic_estrella"
6
style="?android:borderlessButtonStyle"/>
En la siguiente imagen vemos cmo quedara este botn integrado dentro de
un LinearLayout y alineado a la derecha:

El separador vertical que se muestra entre el texto y el botn se consigue utilizando las
propiedadesshowDividers, divider, y dividerPadding del layout contenedor (para mayor
claridad puede consultarse el cdigo completo):
1 <LinearLayout
2
android:id="@+id/BarraBtnSinBorde"
3
android:layout_width="match_parent"
4
android:layout_height="wrap_content"
5
android:orientation="horizontal"
6
android:showDividers="middle"
7
android:divider="?android:dividerVertical"
8
android:dividerPadding="6dp" >
Otro lugar muy habitual donde encontrar botones sin borde es en las llamadas barras de
botones (button bar) que muestran muchas aplicaciones. Para definir una barra de botones,
utilizaremos normalmente como contenedor un LinearLayout horizontal e incluiremos
dentro de ste los botones (Button) necesarios, asignando a cada elelemento su estilo
correspondiente,
en
este
caso buttonBarStyle para
el
contenedor,
y buttonBarButtonStyle para los botones. En nuetro ejemplo crearemos una barra con dos
botones, Aceptar y Cancelar, que quedara as:
1
<LinearLayout
2
android:id="@+id/BarraBotones"
3
android:layout_width="match_parent"
4
android:layout_height="wrap_content"
5
android:orientation="horizontal"
6
android:layout_alignParentBottom="true"
7
style="?android:attr/buttonBarStyle">
8

53

9
<Button android:id="@+id/BtnAceptar"
10
android:layout_width="wrap_content"
11
android:layout_height="wrap_content"
12
android:layout_weight="1"
13
android:text="@string/Aceptar"
14
style="?android:attr/buttonBarButtonStyle" />
15
16
<Button android:id="@+id/BtnCancelar"
17
android:layout_width="wrap_content"
18
android:layout_height="wrap_content"
19
android:layout_weight="1"
20
android:text="@string/Cancelar"
21
style="?android:attr/buttonBarButtonStyle" />
22
23 </LinearLayout>
Visualmente el resultado sera el siguiente:

Botones flotantes (Floating Action Button / FAB)


Como contenido extra de este captulo voy a hacer mencin a un nuevo tipo de botn
aparecido a raiz de la nueva filosofa de diseo Android llamada Material Design. Me
refiero a los botones de accin flotantes que estn incorporando muchas aplicaciones, sobre
todo tras su actualizacin a Android 5 Lollipop.

El inconveniente de este tipo de botones es que no estn incluidos de forma nativa en las
API de la plataforma, por lo que tenemos que construirlos como control
personalizado (ms adelante en el curso hablaremos de esto) o bien utilizar alguna de las
muchas libreras externas que ya lo implementan. Para no complicar ms este captulo voy
a indicar simplemente cmo utilizar una de las libreras open source ms sencillas que
implementan este tipo de control, disponible como proyecto en github: android-floatingaction-button.
Aadir libreras externas a un proyecto de Android Studio
Para hacer uso de una librera externa en un proyecto de Android Studio tenemos dos
posibilidades: aadir el fichero jar de la librera a la carpeta /libs del mdulo, o bien aadir
la referencia a la librera (si est disponible) como dependencia en el
fichero build.gradle del mdulo. En este caso, en la web de la librera nos informan de los
datos necesarios para aadir la librera como dependencia (apartado Usage de la web).

54

Por tanto usaremos esta opcin aadiendo a nuestro ficherobuild.gradle la siguiente lnea
en el apartado dependencies:
dependencies
{

compile
com.getbase:floatingactionbutton:1.6.0
}
Una vez aadida la referencia a la librera, salvamos el fichero y nos aseguramos de pulsar
la opcin Sync Now que nos aparecer en la parte superior derecha del editor de cdigo:

Tras esto, Android Studio se encargar de descargar automticamente los ficheros


necesarios y cuando sea necesario para que podamos hacer uso de la librera.
Una vez aadida la librera al proyecto como se describe en la nota anterior, podremos
aadir un botn flotante a nuestra interfaz aadiendo un nuevo elemento a nuestro layout
principal activity_main.xml de la siguiente forma:
1 <com.getbase.floatingactionbutton.AddFloatingActionButton
2
android:id="@+id/semi_transparent"
3
android:layout_width="wrap_content"
4
android:layout_height="wrap_content"
5
fab:fab_plusIconColor="#FFFFFF"
6
fab:fab_colorNormal="#FF0000"
7
fab:fab_colorPressed="#990000" />
Con las propiedades fab_plusIconColor, fab_colorNormal y fab_colorPressed indicamos
los distintos colores del botn. Si en vez de un botn clsico de Aadir quisiramos
utilizar cualquier otro icono en el botn podemos utilizar FloatingActionButton en vez
de AddFloatingActionButton e indicar el icono a utilizar con la propiedad fab_icon (por
ejemplofab:fab_icon=@drawable/ic_estrella).
Para terminar, en la imagen siguiente se muestra la aplicacin de ejemplo completa, donde
se puede comprobar el aspecto de cada uno de los tipos de botn comentados:

55

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.

Enlaces de inters:
Botones en Material Design
Botones en Gua de diseo Android
Botones en Gua de desarrollo Android
Librera futuresimple/android-floating-action-button (GitHub)
Librera makovkastar/FloatingActionButton (GitHub)
Blog Styling Android: Floating Action Button (Parte 1, 2, 3)

Interfaz de usuario en Android: Controles bsicos (II)


by Sgoliver on 26/08/2010 in Android, Programacin
Despus de haber hablado en el artculo anterior de los controles de tipo botn, en esta
nueva entrega nos vamos a centrar en otros tres componentes bsicos imprescindibles en
nuestras aplicaciones: las imgenes (ImageView), las etiquetas (TextView) y por ltimo los
cuadros de texto (EditText).
Control ImageView [API]
El control ImageView permite mostrar imgenes en la aplicacin. La propiedad ms
interesante esandroid:src, que permite indicar la imagen a mostrar. Nuevamente, lo normal
ser indicar como origen de la imagen el identificador de un recurso de nuestra
carpeta /res/drawable, por ejemploandroid:src=@drawable/unaimagen. Adems de esta
propiedad, existen algunas otras tiles en algunas ocasiones como las destinadas a
establecer

el

tamao

mximo

que

puede

ocupar

la

56

imagen,android:maxWidth y android:maxHeight, o para indicar cmo debe adaptarse la


imagen al tamao del control, android:scaleType (5=CENTER, 6=CENTER_CROP,
7=CENTER_INSIDE, ). Adems, como ya comentamos para el caso de los
controles ImageButton, al tratarse de un control de tipo imagen deberamos establecer
siempre la propiedad android:contentDescription para ofrecer una breve descripcin textual
de la imagen, algo que har nuestra aplicacin mucho ms accesible.
1 <ImageView android:id="@+id/ImgFoto"
2
android:layout_width="wrap_content"
3
android:layout_height="wrap_content"
4
android:src="@drawable/ic_launcher"
5
android:contentDescription="@string/imagen_ejemplo" />
Si en vez de establecer la imagen a mostrar en el propio layout XML de la actividad
quisiramos establecerla mediante cdigo utilizaramos el mtodo setImageResorce(),
pasndole el ID del recurso a utilizar como contenido de la imagen.
1 ImageView img= (ImageView)findViewById(R.id.ImgFoto);
2 img.setImageResource(R.drawable.ic_launcher);
En cuanto a posibles eventos, al igual que comentamos para los controles de tipo botn en
el apartado anterior, para los componentes ImageView tambin podramos implementar su
evento onClick, de forma idntica a la que ya vimos, aunque en estos casos suele ser
menos frecuente la necesidad de capturar este evento.
Control TextView [API]
El control TextView es otro de los clsicos en la programacin de GUIs, las etiquetas de
texto, y se utiliza para mostrar un determinado texto al usuario. Al igual que en el caso de
los botones, el texto del control se establece mediante la propiedad android:text. A parte de
esta propiedad, la naturaleza del control hace que las ms interesantes sean las que
establecen el formato del texto mostrado, que al igual que en el caso de los botones son las
siguientes: android:background (color
de
fondo), android:textColor(color
del
texto), android:textSize (tamao de la fuente) y android:typeface (estilo del texto: negrita,
cursiva, ).
1 <TextView android:id="@+id/LblEtiqueta"
2
android:layout_width="match_parent"
3
android:layout_height="wrap_content"
4
android:text="@string/escribe_algo"
5
android:background="#ff1ca5ff"
6
android:typeface="monospace"/>
La etiqueta tal cual se ha definido en el cdigo anterior tendra el siguiente aspecto:

57

De igual forma, tambin podemos manipular estas propiedades desde nuestro cdigo.
Como ejemplo, en el siguiente fragmento recuperamos el texto de una etiqueta
con getText(), y posteriormente le concatenamos unos nmeros, actualizamos su contenido
mediante setText() y le cambiamos su color de fondo con setBackgroundColor().
1 final TextView lblEtiqueta = (TextView)findViewById(R.id.LblEtiqueta);
2 String texto = lblEtiqueta.getText().toString();
3 texto += "123";
4 lblEtiqueta.setText(texto);
5 lblEtiqueta.setBackgroundColor(Color.BLUE);
Control EditText [API]
El control EditText es el componente de edicin de texto que proporciona la plataforma
Android. Permite la introduccin y edicin de texto por parte del usuario, por lo que en
tiempo de diseo la propiedad ms interesante a establecer, adems de su posicin/tamao
y formato, es el texto a mostrar, atributoandroid:text. Por supuesto si no queremos que el
cuadro de texto aparezca inicializado con ningn texto, no es necesario incluir esta
propiedad en el layout XML. Lo que s deberemos establecer ser la
propiedad android:inputType. Esta propiedad indica el tipo de contenido que se va a
introducir en el cuadro de texto, como por ejemplo una direccin de correo electrnico
(textEmailAddress), un nmero genrico (number), un nmero de telfono (phone), una
direccin web (textUri), o un texto genrico (text). El valor que establezcamos para esta
propiedad tendr adems efecto en el tipo de teclado que mostrar Android para editar
dicho campo. As, por ejemplo, si hemos indicado text mostrar el teclado completo
alfanumrico, si hemos indicado phone mostrar el teclado numrico del telfono, etc.
1 <EditText android:id="@+id/TxtBasico"
2
android:layout_width="match_parent"
3
android:layout_height="wrap_content"
4
android:inputType="text" />
Al igual que ocurra con los botones, donde podamos indicar una imagen que acompaara
al texto del mismo, con los controles de texto podemos hacer lo mismo. Las
propiedades drawableLeft odrawableRight nos permite especificar una imagen, a izquierda
o derecha, que permanecer fija en el cuadro de texto.
Otra opcin adicional ser indicar un texto de ayuda o descripcin (hint), que aparecer en
el cuadro de texto mientras el usuario no haya escrito nada (en cuanto se escribe algo este
texto desaparece). Para esto utilizaremos las propiedades android:hint para indicar el texto
y android:textColorHint para indicar su color.
Veamos un ejemplo utilizando las propiedades anteriores:
1

<EditText android:id="@+id/TxtImagenHint"

58

2
android:layout_width="match_parent"
3
android:layout_height="wrap_content"
4
android:drawableLeft="@drawable/ic_usuario"
5
android:hint="@string/usuario"
6
android:textColorHint="#CFCFCF"
7
android:inputType="text" />
Y su aspecto sera el siguiente:

Para recuperar y establecer el desde nuestro cdigo


los mtodos getText() ysetText(nuevoTexto) respectivamente:
1 EditText txtTexto = (EditText)findViewById(R.id.TxtBasico);
2 String texto = txtTexto.getText().toString();
3 txtTexto.setText("Hola mundo!");

podemos

utilizar

Un detalle que puede haber pasado desapercibido. Os habis fijado en que hemos tenido
que hacer untoString() sobre el resultado de getText()? La explicacin para esto es que el
mtodo getText() no devuelve directamente una cadena de caracteres (String) sino un
objeto de tipo Editable, que a su vez implementa la interfaz Spannable. Y esto nos lleva a la
caracterstica ms interesante del controlEditText, y es que no slo nos permite editar texto
plano sino tambin texto enriquecido o con formato. Veamos cmo y qu opciones tenemos
disponibles, y para empezar comentemos algunas cosas sobre los objetos Spannable.
Interfaz Spanned
Un objeto de tipo Spanned es algo as como una cadena de caracteres (de hecho deriva de la
interfazCharSequence) en la que podemos insertar otros objetos a modo de marcas o
etiquetas(spans) asociados a rangos de caracteres. De esta interfaz deriva la
interfaz Spannable, que permite la modificacin de estas marcas, y a su vez de sta ltima
deriva la interfaz Editable, que permite adems la modificacin del texto.
Aunque en el apartado en el que nos encontramos nos interesaremos principalmente por las
marcas de formato de texto, en principio podramos insertar cualquier tipo de objeto.

Existen muchos tipos de spans predefinidos en la plataforma que podemos utilizar para dar
formato al texto, entre ellos:
TypefaceSpan. Modifica el tipo de fuente.
StyleSpan. Modifica el estilo del texto (negrita, cursiva, ).
ForegroudColorSpan. Modifica el color del texto.
AbsoluteSizeSpan. Modifica el tamao de fuente.
De esta forma, para crear un nuevo objeto Editable e insertar una marca de formato
podramos hacer lo siguiente:
1 //Creamos un nuevo objeto de tipo Editable

59

2
3
4
5

Editable str = Editable.Factory.getInstance().newEditable("Esto es un simulacro.");


//Marcamos cono fuente negrita la palabra "simulacro" (caracteres del 11-19)
str.setSpan(new
StyleSpan(android.graphics.Typeface.BOLD),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

En este ejemplo estamos insertando un span de tipo StyleSpan para marcar un fragmento de
texto con estilo negrita. Para insertarlo utilizamos el mtodo setSpan(), que recibe como
parmetro el objeto Spana insertar, la posicin inicial y final del texto a marcar, y
un flag que indica la forma en la que el span se podr extender al insertarse nuevo texto.
Texto con formato en controles TextView y EditText
Hemos visto cmo crear un objeto Editable y aadir marcas de formato al texto que
contiene, pero todo esto no tendra ningn sentido si no pudiramos visualizarlo. Como ya
podis imaginar, los controlesTextView y EditText nos van a permitir hacer esto. Veamos
qu ocurre si asignamos a nuestro controlEditText el objeto Editable que hemos creado
antes:
1 txtTexto.setText(str);
Tras ejecutar este cdigo, para lo que hemos insertado un botn SetText en la aplicacin
de ejemplo, veremos como efectivamente en el cuadro de texto aparece el mensaje con el
formato esperado:
En la aplicacin de ejemplo tambin he incluido un botn adicional Negrita que se
encargar de convertir a estilo negrita un fragmento de texto previamente seleccionado en
el cuadro de texto. Mi intencin con esto es presentar los mtodos disponibles para
determinar el comienzo y el fin de una seleccin en un control de este tipo. Para ello
utilizaremos los mtodos getSelectionStart() y getSelectionEnd(), que nos devolvern el
ndice del primer y ltimo carcter seleccionado en el texto. Sabiendo esto, ya slo nos
queda utilizar el mtodo setSpan() que ya conocemos para convertir la seleccin a negrita.
1 Spannable texto = txtTexto.getText();
2
3 int ini = txtTexto.getSelectionStart();
4 int fin = txtTexto.getSelectionEnd();
5
6 texto.setSpan(
7
new StyleSpan(android.graphics.Typeface.BOLD),
8
ini, fin,
9
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Bien, ya hemos visto cmo asignar texto con y sin formato a un cuadro de texto, pero qu
ocurre a la hora de recuperar texto con formato desde el control? Ya vimos que la
funcin getText() devuelve un objeto de tipo Editable y que sobre ste podamos hacer

11,

19

60

un toString(). Pero con esta solucin estamos perdiendo todo el formato del texto, por lo
que no podramos por ejemplo salvarlo a una base de datos.
La solucin a esto ltimo pasa obviamente por recuperar directamente el objeto Editable y
serializarlo de algn modo, mejor an si es en un formato estandar. Pues bien, en Android
este trabajo ya nos viene hecho de fbrica a travs de la clase Html [API], que dispone de
mtodos para convertir cualquier objetoSpanned en su representacin HTML equivalente.
Veamos cmo. Recuperemos el texto de la ventana anterior y utilicemos el
mtodo Html.toHtml(Spannable) para convertirlo a formato HTML:
1 //Obtiene el texto del control con etiquetas de formato HTML
2 String aux2 = Html.toHtml(txtTexto.getText());
Haciendo esto, obtendramos una cadena de texto como la siguiente, que ya podramos por
ejemplo almacenar en una base de datos o publicar en cualquier web sin perder el formato
de texto establecido:
1

<p>Esto es un <b>simulacro</b>.</p>

La operacin contraria tambin es posible, es decir, cargar un cuadro de texto de Android


(EditText) o una etiqueta (TextView) a partir de un fragmento de texto en formato HTML.
Para ello podemos utilizar el mtodo Html.fromHtml(String) de la siguiente forma:
1 //Asigna texto con formato HTML
2 txtTexto.setText(
3
Html.fromHtml("<p>Esto es un <b>simulacro</b>.</p>"),
4
BufferType.SPANNABLE);
Desgraciadamente, aunque es de agradecer que este trabajo venga hecho de casa, hay que
decir que tan slo funciona de forma completa con las opciones de formato ms bsicas,
como negritas, cursivas, subrayado o colores de texto, quedando no soportadas otras
sorprendentemente bsicas como el tamao del texto, que aunque s es correctamente
traducido por el mtodo toHtml(), es descartado por el mtodo contrario fromHtml().
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.

Enlaces de inters:
Campos de texto en Material Design
Campos de texto en Gua de diseo Android
Campos de texto en Gua de desarrollo Android
Presentacin Advanced Android TextView (Chiu-Ki Chan)
Artculo Spans, a Powerful Concept (Flavien Laurent)
Implementacin de Floating Label: post y cdigo (Chris Banes)
Librera Calligraphy (fuentes personalizadas)
Interfaz de usuario en Android: Controles bsicos (III)

61

by Sgoliver on 27/08/2010 in Android, Programacin


Tras hablar de varios de los controles indispensables en cualquier aplicacin Android,
como son losbotones y los cuadros de texto, en este artculo vamos a ver cmo utilizar otros
dos tipos de controles bsicos en muchas aplicaciones, los checkboxes y los radio buttons.
Control CheckBox [API]
Un control checkbox se suele utilizar para marcar o desmarcar opciones en una aplicacin,
y en Android est representado por la clase del mismo nombre, CheckBox. La forma de
definirlo en nuestra interfaz y los mtodos disponibles para manipularlos desde nuestro
cdigo son anlogos a los ya comentados para el control ToggleButton.
De esta forma, para definir un control de este tipo en nuestro layout podemos utilizar el
cdigo siguiente, que define un checkbox con el texto Mrcame:
1 <CheckBox android:id="@+id/ChkMarcame"
2
android:layout_width="wrap_content"
3
android:layout_height="wrap_content"
4
android:text="@string/marcame"
5
android:checked="false" />
En cuanto a la personalizacin del control podemos decir que ste extiende
[indirectamente] del controlTextView, por lo que todas las opciones de formato ya
comentadas en artculos anteriores son vlidas tambin para este control. Adems,
podremos utilizar la propiedad android:checked para inicializar el estado del control a
marcado (true) o desmarcado (false). Si no establecemos esta propiedad el control
aparecer por defecto en estado desmarcado.
En el cdigo de la aplicacin podremos hacer uso de los mtodos isChecked() para conocer
el estado del control, y setChecked(estado) para establecer un estado concreto para el
control.
1 if (checkBox.isChecked()) {
2
checkBox.setChecked(false);
3 }
En cuanto a los posibles eventos que puede lanzar este control, onClick vuelve a ser el ms
interesante ya que nos indicar cundo se ha pulsado sobre el checkbox. Dentro de este
evento consultaremos normalmente el estado del control con isChecked() como acabamos
de ver.
1
cbMarcame = (CheckBox)findViewById(R.id.ChkMarcame);
2
3
cbMarcame.setOnClickListener(new View.OnClickListener() {
4
@Override
5
public void onClick(View view) {
6
boolean isChecked = ((CheckBox)view).isChecked();
7

62

8
if (isChecked) {
9
cbMarcame.setText("Checkbox marcado!");
10
}
11
else {
12
cbMarcame.setText("Checkbox desmarcado!");
13
}
14
}
15 });
Otro evento que podramos utilizar es onCheckedChanged, que nos informa de que ha
cambiado el estado del control. Para implementar las acciones de este evento podramos
utilizar la siguiente lgica, donde tras capturar el evento, y dependiendo del nuevo estado
del control (variable isChecked recibida como parmetro), haremos una accin u otra:
cbMarcame = (CheckBox)findViewById(R.id.ChkMarcame);
1
2
cbMarcame.setOnCheckedChangeListener(new
3
CheckBox.OnCheckedChangeListener() {
4
public void onCheckedChanged(CompoundButton buttonView, boolean
5
isChecked) {
6
if (isChecked) {
7
cbMarcame.setText("Checkbox marcado!");
8
}
9
else {
10
cbMarcame.setText("Checkbox desmarcado!");
11
}
12
}
});
Control RadioButton [API]
Al igual que los controles checkbox, un radio button puede estar marcado o desmarcado,
pero en este caso suelen utilizarse dentro de un grupo de opciones donde una, y slo una, de
ellas debe estar marcada obligatoriamente, es decir, que si se marca una de las opciones se
desmarcar automticamente la que estuviera activa anteriormente. En Android, un grupo
de botones radio button se define mediante un elemento RadioGroup, que a su vez
contendr todos los elementos RadioButton necesarios. Veamos un ejemplo de cmo
definir un grupo de dos controles radiobutton en nuestra interfaz:
1
<RadioGroup android:id="@+id/GrbGrupo1"
2
android:orientation="vertical"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent" >
5
6
<RadioButton android:id="@+id/RbOpcion1"
7
android:layout_width="wrap_content"
8
android:layout_height="wrap_content"
9
android:text="@string/opcion_1" />

63

10
11
<RadioButton android:id="@+id/RbOpcion2"
12
android:layout_width="wrap_content"
13
android:layout_height="wrap_content"
14
android:text="@string/opcion_2" />
15 </RadioGroup>
En primer lugar vemos cmo podemos definir el grupo de controles indicando su
orientacin (vertical u horizontal) al igual que ocurra por ejemplo con un LinearLayout.
Tras esto, se aaden todos los objetosRadioButton necesarios indicando su ID mediante la
propiedad android:id y su texto medianteandroid:text.
Una vez definida la interfaz podremos manipular el control desde nuestro cdigo java
haciendo uso de los diferentes mtodos del control RadioGroup, los ms
importantes: check(id) para
marcar
una
opcin
determinada
mediante
su
ID, clearCheck() para desmarcar todas las opciones, ygetCheckedRadioButtonId() que
como su nombre indica devolver el ID de la opcin marcada (o el valor -1 si no hay
ninguna marcada). Veamos un ejemplo:
1 RadioGroup rg = (RadioGroup)findViewById(R.id.GrbGrupo1);
2 rg.clearCheck();
3 rg.check(R.id.RbOpcion1);
4 int idSeleccionado = rg.getCheckedRadioButtonId();
En cuanto a los eventos lanzados, recurriremos nuevamente al evento onClick para saber
cundo se pulsa cada uno de los botones del grupo. Normalmente utilizaremos un
mismo listener para todos los radiobutton del grupo, por lo que lo definiremos de forma
independiente y despus lo asignaremos a todos los botones.
1
private TextView lblMensaje;
2
private RadioButton rbOpcion1;
3
private RadioButton rbOpcion2;
4
5
//...
6
7
lblMensaje = (TextView)findViewById(R.id.LblSeleccion);
8
rbOpcion1 = (RadioButton)findViewById(R.id.RbOpcion1);
9
rbOpcion2 = (RadioButton)findViewById(R.id.RbOpcion2);
10
11 View.OnClickListener list = new View.OnClickListener() {
12
@Override
13
public void onClick(View view) {
14
String opcion = "";
15
switch(view.getId()) {
16
case R.id.RbOpcion1:
17
opcion = "opcin 1";
18
break;

64

19
20
21
22
23
24
25
26
27
28
29

case R.id.RbOpcion2:
opcion = "opcin 2";
break;
}
lblMensaje.setText("ID opcin seleccionada: " + opcion);
}
};
rbOpcion1.setOnClickListener(list);
rbOpcion2.setOnClickListener(list);

Al igual que en el caso de los checkboxes, tambin podremos utilizar el


evento onCheckedChange, que nos informar de los cambios en el elemento seleccionado
dentro de un grupo. La diferencia aqu es que este evento est asociado al RadioGroup, y no
a los diferentes RadioButton del grupo. Veamos cmo tratar este evento haciendo por
ejemplo que una etiqueta de texto cambie de valor al seleccionar cada opcin:
rgOpciones = (RadioGroup)findViewById(R.id.GrbGrupo1);
1
rgOpciones.setOnCheckedChangeListener(new
2
RadioGroup.OnCheckedChangeListener() {
3
public void onCheckedChanged(RadioGroup group, int checkedId) {
4
5
String opcion = "";
6
switch(checkedId) {
7
case R.id.RbOpcion1:
8
opcion = "opcin 1";
9
break;
10
case R.id.RbOpcion2:
11
opcion = "opcin 2";
12
break;
13
}
14
15
lblMensaje.setText("ID opcin seleccionada: " + opcion);
16
}
17
});
Veamos finalmente una imagen del aspecto de estos dos nuevos tipos de controles bsicos
que hemos comentado en este artculo:

65

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.

Enlaces de inters:
Switches en Material Design
Switches en Gua de diseo Android
CheckBoxes y RadioButton en Gua de desarrollo Android.
Interfaz de usuario en Android: Controles de seleccin (I)
by Sgoliver on 07/09/2010 in Android, Programacin
Una vez repasados los controles bsicos (I, II, III) que podemos utilizar en nuestras
aplicaciones Android, vamos a dedicar los prximos artculos a describir los diferentes
controles de seleccin disponibles en la plataforma. Al igual que en
otros frameworks Android dispone de diversos controles que nos permiten seleccionar una
opcin dentro de una lista de posibilidades. As, podremos utilizar por ejemplo listas
desplegables (Spinner), listas fijas (ListView), o tablas (GridView).
En este primer artculo dedicado a los controles de seleccin vamos a describir un elemento
importante y comn a todos ellos, los adaptadores, y lo vamos a aplicar al primer control
de los indicados, las listas desplegables.
Adaptadores en Android (adapters)
Para los desarrolladores de java que hayan utilizado frameworks de interfaz grfica
como Swing, el concepto de adaptador les resultar familiar. Un adaptador representa algo
as como una interfaz comn al modelo de datos que existe por detrs de todos los controles
de seleccin que hemos comentado. Dicho de otra forma, todos los controles de seleccin
accedern a los datos que contienen a travs de un adaptador.
Adems de proveer de datos a los controles visuales, el adaptador tambin ser responsable
de generar a partir de estos datos las vistas especficas que se mostrarn dentro del control
de seleccin. Por ejemplo, si cada elemento de una lista estuviera formado a su vez por una

66

imagen y varias etiquetas, el responsable de generar y establecer el contenido de todos estos


sub-elementos a partir de los datos ser el propio adaptador.
Android proporciona de serie varios tipos de adaptadores sencillos, aunque podemos
extender su funcionalidad facilmente para adaptarlos a nuestras necesidades. Los ms
comunes son los siguientes:

ArrayAdapter. Es el ms sencillo de todos los adaptadores, y provee de datos a un control


de seleccin a partir de un array de objetos de cualquier tipo.
SimpleAdapter. Se utiliza para mapear datos sobre los diferentes controles definidos en un
fichero XML de layout.
SimpleCursorAdapter. Se utiliza para mapear las columnas de un cursor abierto sobre una
base de datos sobre los diferentes elementos visuales contenidos en el control de seleccin.
Para no complicar excesivamente los tutoriales, por ahora nos vamos a conformar con
describir la forma de utilizar un ArrayAdapter con los diferentes controles de seleccin
disponibles.
Veamos cmo crear un adaptador de tipo ArrayAdapter para trabajar con un array genrico
de java:
1 final String[] datos =
2
new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};
3
4 ArrayAdapter<String> adaptador =
5
new ArrayAdapter<String>(this,
6
android.R.layout.simple_spinner_item, datos);
Comentemos un poco el cdigo. Sobre la primera lnea no hay nada que decir, es tan slo la
definicin del array java que contendr los datos a mostrar en el control, en este caso un
array sencillo con cinco cadenas de caracteres. En la segunda lnea creamos el adaptador en
s, al que pasamos 3 parmetros:

1. El contexto, que normalmente ser simplemente una referencia a la actividad donde se crea
el adaptador.
2. El ID del layout sobre el que se mostrarn los datos del control. En este caso le pasamos el
ID de un layout predefinido en Android (android.R.layout.simple_spinner_item), formado
nicamente por un control TextView, pero podramos pasarle el ID de cualquier layout
personalizado de nuestro proyecto con cualquier estructura y conjunto de controles, ms
adelante veremos cmo (en el apartado dedicado a las listas fijas).
3. El array que contiene los datos a mostrar.
Con esto ya tendramos creado nuestro adaptador para los datos a mostrar y ya tan slo nos
quedara asignar este adaptador a nuestro control de seleccin para que ste mostrase los
datos en la aplicacin.

67

Una alternativa a tener en cuenta si los datos a mostrar en el control son estticos sera
definir la lista de posibles valores como un recurso de tipo string-array. Para ello, primero
crearamos un nuevo fichero XML en la carpeta /res/values llamado por
ejemplo valores_array.xml e incluiramos en l los valores seleccionables de la siguiente
forma:
1
<?xml version="1.0" encoding="utf-8"?>
2
<resources>
3
<string-array name="valores_array">
4
<item>Elem1</item>
5
<item>Elem2</item>
6
<item>Elem3</item>
7
<item>Elem4</item>
8
<item>Elem5</item>
9
</string-array>
10 </resources>
Tras
esto,
a
la
hora
de
crear
el
adaptador,
utilizaramos
el
mtodo createFromResource() para hacer referencia a este array XML que acabamos de
crear:
1 ArrayAdapter<CharSequence> adapter =
2
ArrayAdapter.createFromResource(this,
3
R.array.valores_array,
4
android.R.layout.simple_spinner_item);
Control Spinner [API]
Las listas desplegables en Android se llaman Spinner. Funcionan de forma similar a
cualquier control de este tipo, el usuario selecciona la lista, se muestra una especie de lista
emergente al usuario con todas las opciones disponibles y al seleccionarse una de ellas sta
queda fijada en el control. Para aadir una lista de este tipo a nuestra aplicacin podemos
utilizar el cdigo siguiente:
1
2
3

<Spinner android:id="@+id/CmbOpciones"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

Poco vamos a comentar de aqu ya que lo que nos interesan realmente son los datos a
mostrar. En cualquier caso, las opciones para personalizar el aspecto visual del control
(fondo, color y tamao de fuente, ) son las mismas ya comentadas para los controles
bsicos.
Para enlazar nuestro adaptador (y por tanto nuestros datos) a este control utilizaremos el
siguiente cdigo java:

68

1
2
3
4
5
6
7
8
9
10

private Spinner cmbOpciones;


//...
cmbOpciones = (Spinner)findViewById(R.id.CmbOpciones);
adaptador.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
cmbOpciones.setAdapter(adaptador);

Comenzamos como siempre por obtener una referencia al control a travs de su ID. Y en la
ltima lnea asignamos el adaptador al control mediante el mtodo setAdapter(). Y la
segunda lnea para qu es? Cuando indicamos en el apartado anterior cmo construir un
adaptador vimos cmo uno de los parmetros que le pasbamos era el ID del layout que
utilizaramos para visualizar los elementos del control. Sin embargo, en el caso del
control Spinner, este layout tan slo se aplicar al elemento seleccionado en la lista, es
decir, al que se muestra directamente sobre el propio control cuando no est desplegado.
Sin embargo, antes indicamos que el funcionamiento normal del control Spinner incluye
entre otras cosas mostrar una lista emergente con todas las opciones disponibles. Pues bien,
para personalizar tambin el aspecto de cada elemento en dicha lista emergente tenemos el
mtodosetDropDownViewResource(ID_layout), al que podemos pasar otro ID de layout
distinto al primero sobre el que se mostrarn los elementos de la lista emergente. En este
caso hemos utilizado otro layout predefinido an Android para las listas desplegables
(android.R.layout.simple_spinner_dropdown_item), formado por una etiqueta de texto con
la descripcin de la opcin (en Android 2.x tambin se muestra un marcador circular a la
derecha que indica si la opcin est o no seleccionada).
Con estas simples lineas de cdigo conseguiremos mostrar un control como el que vemos
en la siguiente imagen:

69

En cuanto a los eventos lanzados por el control Spinner, el ms comunmente utilizado ser
el generado al seleccionarse una opcin de la lista desplegable, onItemSelected. Para
capturar este evento se proceder de forma similar a lo ya visto para otros controles
anteriormente, asignadole su controlador mediante el mtodo setOnItemSelectedListener():
1
cmbOpciones.setOnItemSelectedListener(
2
new AdapterView.OnItemSelectedListener() {
3
public void onItemSelected(AdapterView<?> parent,
4
android.view.View v, int position, long id) {
5
lblMensaje.setText("Seleccionado: " +
6
parent.getItemAtPosition(position));
7
}
8
9
public void onNothingSelected(AdapterView<?> parent) {
10
lblMensaje.setText("");
11
}
12
});
A diferencia de ocasiones anteriores, para este evento definimos dos mtodos, el primero de
ellos (onItemSelected) que ser llamado cada vez que se selecciones una opcin en la lista
desplegable, y el segundo (onNothingSelected) que se llamar cuando no haya ninguna
opcin seleccionada (esto puede ocurrir por ejemplo si el adaptador no tiene
datos). Adems, vemos cmo para recuperar el dato seleccionado utilizamos el
mtodo getItemAtPosition() del parmetro AdapterView que recibimos en el evento.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.
En el siguiente artculo describiremos el uso de controles de tipo lista (ListView).

70

Interfaz de usuario en Android: Controles de seleccin (II)


by Sgoliver on 07/09/2010 in Android, Programacin
En el artculo anterior ya comenzamos a hablar de los controles de seleccin en Android,
empezando por explicar el concepto de adaptador y describiendo el control Spinner. En
este nuevo artculo nos vamos a centrar en el control de seleccin ms utilizado de todos,
el ListView.
Un control ListView muestra al usuario una lista de opciones seleccionables directamente
sobre el propio control, sin listas emergentes como en el caso del control Spinner. En caso
de existir ms opciones de las que se pueden mostrar sobre el control se podr por supuesto
hacer scroll sobre la lista para acceder al resto de elementos.
Para empezar, veamos como podemos aadir un control ListView a nuestra interfaz de
usuario:
1 <ListView android:id="@+id/LstOpciones"
2
android:layout_width="match_parent"
3
android:layout_height="wrap_content" />
Una vez ms, podremos modificar el aspecto del control utilizando las propiedades de
fuente y color ya comentadas en artculos anteriores. Por su parte, para enlazar los datos
con el control podemos utlizar por ejemplo el mismo cdigo que ya vimos para el
control Spinner. Definiremos primero un array con nuestros datos de prueba, crearemos
posteriormente el adaptador de tipo ArrayAdapter y lo asignaremos finalmente al control
mediante el mtodo setAdapter():
1
final String[] datos =
2
new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};
3
4
ArrayAdapter<String> adaptador =
5
new ArrayAdapter<String>(this,
6
android.R.layout.simple_list_item_1, datos);
7
8
lstOpciones = (ListView)findViewById(R.id.LstOpciones);
9
10 lstOpciones.setAdapter(adaptador);
NOTA: En caso de necesitar mostrar en la lista datos procedentes de una base de datos la
mejor prctica es utilizar un Loader (concretamente un CursorLoader), que cargar los
datos de forma asncrona de forma que la aplicacin no se bloquee durante la carga. Esto lo
veremos ms adelante en el curso, ahora nos conformaremos con cargar datos estticos
procedentes de un array.
En el cdigo anterior, para mostrar los datos de cada elemento hemos utilizado otro layout
genrico

de

Android

para

los

controles

de

71

tipo ListView (android.R.layout.simple_list_item_1),


un TextView con unas dimensiones determinadas.

formado

nicamente

por

Como podis comprobar el uso bsico del control ListView es completamente anlogo al
ya comentado para el control Spinner.
Hasta aqu todo sencillo. Pero, y si necesitamos mostrar datos ms complejos en la lista?
qu ocurre si necesitamos que cada elemento de la lista est formado a su vez por varios
elementos? Pues vamos a provechar este artculo dedicado a los ListView para ver cmo
podramos conseguirlo, aunque todo lo que comentar es extensible a otros controles de
seleccin.
Para no complicar mucho el tema vamos a hacer que cada elemento de la lista muestre por
ejemplo dos lneas de texto a modo de ttulo y subttulo con formatos diferentes (por
supuesto se podran aadir muchos ms elementos, por ejemplo imgenes, checkboxes, etc).
En primer lugar vamos a crear una nueva clase java para contener nuestros datos de prueba.
Vamos a llamarla Titular y tan slo va a contener dos atributos, ttulo y subttulo.
1
public class Titular
2
{
3
private String titulo;
4
private String subtitulo;
5
6
public Titular(String tit, String sub){
7
titulo = tit;
8
subtitulo = sub;
9
}
10
11
public String getTitulo(){
12
return titulo;
13
}

72

14
15
16
17
18

public String getSubtitulo(){


return subtitulo;
}
}

En cada elemento de la lista queremos mostrar ambos datos, por lo que el siguiente paso
ser crear un layout XML con la estructura que deseemos. En mi caso voy a mostrarlos en
dos etiquetas de texto (TextView), la primera de ellas en negrita y con un tamao de letra
un poco mayor. Llamaremos a este layout listitem_titular.xml:
1
<LinearLayout
2
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="wrap_content"
4
android:layout_height="wrap_content"
5
android:orientation="vertical">
6
7
<TextView android:id="@+id/LblTitulo"
8
android:layout_width="match_parent"
9
android:layout_height="wrap_content"
10
android:textStyle="bold"
11
android:textSize="20sp" />
12
13 <TextView android:id="@+id/LblSubTitulo"
14
android:layout_width="match_parent"
15
android:layout_height="wrap_content"
16
android:textStyle="normal"
17
android:textSize="12sp" />
18
19 </LinearLayout>
Ahora que ya tenemos creados tanto el soporte para nuestros datos como el layout que
necesitamos para visualizarlos, lo siguiente que debemos hacer ser indicarle al adaptador
cmo debe utilizar ambas cosas para generar nuestra interfaz de usuario final. Para ello
vamos a crear nuestro propio adaptador extendiendo de la clase ArrayAdapter.
1
class AdaptadorTitulares extends ArrayAdapter<Titular> {
2
3
public AdaptadorTitulares(Context context, Titular[] datos) {
4
super(context, R.layout.listitem_titular, datos);
5
}
6
7
public View getView(int position, View convertView, ViewGroup parent) {
8
LayoutInflater inflater = LayoutInflater.from(getContext());
9
View item = inflater.inflate(R.layout.listitem_titular, null);
10
11
TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
12
lblTitulo.setText(datos[position].getTitulo());

73

13
14
15
16
17
18
19

TextView lblSubtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);


lblSubtitulo.setText(datos[position].getSubtitulo());
return(item);
}
}

Analicemos el cdigo anterior. Lo primero que encontramos es el constructor para nuestro


adaptador, al que slo pasaremos el contexto (que normalmente ser la actividad desde la
que se crea el adaptador) y el array de datos a mostrar, que en nuestro caso es un array de
objetos de tipo Titular. En este constructor tan slo llamaremos al constructor padre tal
como ya vimos al principio de este artculo, pasndole nuestros dos parmetros (contexto y
datos) y el ID del layout que queremos utilizar (en nuestro caso el nuevo que hemos
creado, listitem_titular).
Posteriormente, redefinimos el mtodo encargado de generar y rellenar con nuestros datos
todos los controles necesarios de la interfaz grfica de cada elemento de la lista. Este
mtodo es getView().
El mtodo getView() se llamar cada vez que haya que mostrar un elemento de la lista. Lo
primero que debe hacer es inflar el layout XML que hemos creado. Esto consiste en
consultar el XML de nuestro layout y crear e inicializar la estructura de objetos java
equivalente. Para ello, crearemos un nuevo objetoLayoutInflater y generaremos la
estructura de objetos mediante su mtodo inflate(id_layout).
Tras esto, tan slo tendremos que obtener la referencia a cada una de nuestras etiquetas
como siempre lo hemos hecho y asignar su texto correspondiente segn los datos de nuestro
array y la posicin del elemento actual (parmetro position del mtodo getView()).
Una vez tenemos definido el comportamiento de nuestro adaptador la forma de proceder en
la actividad principal ser anloga a lo ya comentado, definiremos el array de datos de
prueba, crearemos el adaptador y lo asignaremos al control mediante setAdapter():
1
private Titular[] datos =
2
new Titular[]{
3
new Titular("Ttulo 1", "Subttulo largo 1"),
4
new Titular("Ttulo 2", "Subttulo largo 2"),
5
new Titular("Ttulo 3", "Subttulo largo 3"),
6
new Titular("Ttulo 4", "Subttulo largo 4"),
7
//...
8
new Titular("Ttulo 15", "Subttulo largo 15")};
9
10 //...
11
12 AdaptadorTitulares adaptador =

74

13
14
15
16
17

new AdaptadorTitulares(this, datos);


lstOpciones = (ListView)findViewById(R.id.LstOpciones);
lstOpciones.setAdapter(adaptador);

Hecho esto, y si todo ha ido bien, nuestra nueva lista debera quedar como vemos en la
imagen siguiente:

Otra posible personalizacin de nuestra lista podra ser aadirle una cabecera o un pie. Para
esto, definiremos un nuevo layout XML para la cabecera/pie y lo aadiremos a la lista antes
de asignar el adaptador.
As por ejemplo, podramos crear la siguiente cabecera compuesta por una etiqueta de texto
centrada y en negrita sobre fondo azul, en un fichero XML situado
en layout/list_header.xml:
1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
android:orientation="vertical" android:layout_width="match_parent"
3
android:layout_height="match_parent">
4
5
<TextView
6
android:layout_width="match_parent"
7
android:layout_height="40dp"
8
android:text="CABECERA"
9
android:textStyle="bold"
10
android:background="#ff0093ff"
11
android:gravity="center" />
12
13 </LinearLayout>

75

Y una vez creada la aadiramos a nuestra lista inflando su layout y llamando al


mtodo addHeaderView()con la vista resultante:
1 lstOpciones = (ListView)findViewById(R.id.LstOpciones);
2
3 //...
4
5 View header = getLayoutInflater().inflate(R.layout.list_header, null);
6 lstOpciones.addHeaderView(header);
ste sera un ejemplo sencillo, pero tanto las cabeceras como los pie de lista pueden
contener por supuesto otros elementos como imgenes o botones. Veamos cmo quedara:

Por ltimo comentemos un poco los eventos de este tipo de controles. Si quisiramos
realizar cualquier accin al pulsarse sobre un elemento de la lista creada tendremos que
implementar el eventoonItemClick. Veamos cmo con un ejemplo:
1
lstOpciones.setOnItemClickListener(new AdapterView.OnItemClickListener() {
2
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
3
4
//Alternativa 1:
5
String opcionSeleccionada =
6
((Titular)a.getItemAtPosition(position)).getTitulo();
7
8
//Alternativa 2:
9
//String opcionSeleccionada =
10
// ((TextView)v.findViewById(R.id.LblTitulo))
11
//
.getText().toString();
12
13
lblEtiqueta.setText("Opcin seleccionada: " + opcionSeleccionada);
14
}

76

15 });
Este evento recibe 4 parmetros:

Referencia al control lista que ha recibido el click (AdapterView a).


Referencia al objeto View correspondiente al tem pulsado de la lista (View v).
Posicin del elemento pulsado dentro del adaptador de la lista (int position).
Id del elemento pulsado (long id).

Con todos estos datos, si quisiramos por ejemplo mostrar el ttulo de la opcin pulsada en
la etiqueta de texto superior (lblEtiqueta) tendramos dos posibilidades:
1. Acceder a la vista asociada al adaptador y a partir de sta obtener
mediante getItemAtPosition()el elemento cuya posicin sea position. Esto nos devolvera
un objeto de tipo Titular, por lo que obtendramos el ttulo llamando a su
mtodo getTitulo().
2. Acceder directamente a la vista que se ha pulsado, que tendra la estructura definida en
nuestro
layout
personalizado listitem_titular.xml,
y
obtener
mediante findViewById() y getText() el texto del control que alberga el campo ttulo.
Y esto sera todo por ahora. Aunque ya sabemos utilizar y personalizar las listas en
Android, en el prximo apartado daremos algunas indicaciones para utilizar de una forma
mucho ms eficiente los controles de este tipo, algo que los usuarios de nuestra aplicacin
agradecern enormemente al mejorarse la respuesta de la aplicacin y reducirse el consumo
de batera.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.
Interfaz de usuario en Android: Controles de seleccin (III)
by Sgoliver on 10/09/2010 in Android, Programacin
En el artculo anterior ya vimos cmo utilizar los controles de tipo ListView en Android.
Sin embargo, acabamos comentando que exista una forma ms eficiente de hacer uso de
dicho control, de forma que la respuesta de nuestra aplicacin fuera ms agil y se reduciese
el consumo de batera, algo que en plataformas mviles siempre es importante.
Como base para este artculo vamos a utilizar como cdigo que ya escribimos en el artculo
anterior, por lo que si has llegado hasta aqu directamente te recomiendo que leas primero
el primer post dedicado al control ListView.
Cuando comentamos cmo crear nuestro propio adaptador, extendiendo de ArrayAdapter,
para personalizar la forma en que nuestros datos se iban a mostrar en la lista escribimos el
siguiente cdigo:
1
class AdaptadorTitulares extends ArrayAdapter<Titular> {

77

2
3
public AdaptadorTitulares(Context context, Titular[] datos) {
4
super(context, R.layout.listitem_titular, datos);
5
}
6
7
public View getView(int position, View convertView, ViewGroup parent) {
8
LayoutInflater inflater = LayoutInflater.from(getContext());
9
View item = inflater.inflate(R.layout.listitem_titular, null);
10
11
TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
12
lblTitulo.setText(datos[position].getTitulo());
13
14
TextView lblSubtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
15
lblSubtitulo.setText(datos[position].getSubtitulo());
16
17
return(item);
18
}
19 }
Centrndonos en la definicin del mtodo getView() vimos que la forma normal de
proceder consista en primer lugar en inflar nuestro layout XML personalizado para crear
todos los objetos correspondientes (con la estructura descrita en el XML) y posteriormente
acceder a dichos objetos para modificar sus propiedades. Sin embargo, hay que tener en
cuenta que esto se hace todas y cada una de las veces que se necesita mostrar un elemento
de la lista en pantalla, se haya mostrado ya o no con anterioridad, ya que Android no
guarda los elementos de la lista que desaparecen de pantalla (por ejemplo al hacer scroll
sobre la lista). El efecto de esto es obvio, dependiendo del tamao de la lista y sobre todo
de la complejidad del layout que hayamos definido esto puede suponer la creacin y
destruccin de cantidades ingentes de objetos (que puede que ni siquiera nos sean
necesarios), es decir, que la accin de inflar un layout XML puede ser bastante costosa, lo
que podra aumentar mucho, y sin necesidad, el uso de CPU, de memoria, y de batera.
Para aliviar este problema, Android nos propone un mtodo que permite reutilizar algn
layout que ya hayamos inflado con anterioridad y que ya no nos haga falta por algn
motivo, por ejemplo porque el elemento correspondiente de la lista ha desaparecido de la
pantalla al hacer scroll. De esta forma evitamos todo el trabajo de crear y estructurar todos
los objetos asociados al layout, por lo que tan slo nos quedara obtener la referencia a ellos
mediante findViewById() y modificar sus propiedades.
Pero cmo podemos reutilizar estos layouts obsoletos? Pues es bien sencillo, siempre
que exista algn layout que pueda ser reutilizado ste se va a recibir a travs del
parmetro convertView del mtodogetView(). De esta forma, en los casos en que ste no

78

sea null podremos obviar el trabajo de inflar el layout. Veamos cmo quedara el
mtod getView() tras esta optimizacin:
1
public View getView(int position, View convertView, ViewGroup parent)
2
{
3
View item = convertView;
4
5
if(item == null)
6
{
7
LayoutInflater inflater = context.getLayoutInflater();
8
item = inflater.inflate(R.layout.listitem_titular, null);
9
}
10
11
TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
12
lblTitulo.setText(datos[position].getTitulo());
13
14
TextView lblSubtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
15
lblSubtitulo.setText(datos[position].getSubtitulo());
16
17
return(item);
18 }
Si ejecutamos ahora la aplicacin podemos comprobar que al hacer scroll sobre la lista todo
sigue funcionando con normalidad, con la diferencia de que le estamos ahorrando gran
cantidad de trabajo a la CPU.
Pero vamos a ir un poco ms all. Con la optimizacin que acabamos de implementar
conseguimos ahorrarnos el trabajo de inflar el layout definido cada vez que se muestra un
nuevo elemento. Pero an hay otras dos llamadas relativamente costosas que se siguen
ejecutando en todas las llamadas. Me refiero a la obtencin de la referencia a cada uno de
los objetos a modificar mediante el mtodo findViewById(). La bsqueda por ID de un
control determinado dentro del rbol de objetos de un layout tambin puede ser una tarea
costosa dependiendo de la complejidad del propio layout. Por qu no aprovechamos que
estamos guardando un layout anterior para guardar tambin la referencia a los controles
que lo forman de forma que no tengamos que volver a buscarlos? Pues eso es exactamente
lo que vamos a hacer mediante lo que suelen llamar patrn ViewHolder.
Nuestra clase ViewHolder tan slo va a contener una referencia a cada uno de los controles
que tengamos que manipular de nuestro layout, en nuestro caso las dos etiquetas de texto.
Definamos por tanto esta clase de la siguiente forma:
1 static class ViewHolder {
2
TextView titulo;
3
TextView subtitulo;
4 }

79

La idea ser por tanto crear e inicializar el objeto ViewHolder la primera vez que inflemos
un elemento de la lista y asociarlo a dicho elemento de forma que posteriormente podamos
recuperarlo fcilmente. Pero dnde lo guardamos? Fcil, en Android todos los controles
tienen una propiedad llamada Tag (podemos asignarla y recuperarla mediante los
mtodos setTag() y getTag() respectivamente) que puede contener cualquier tipo de objeto,
por lo que resulta ideal para guardar nuestro objeto ViewHolder. De esta forma, cuando el
parmetro convertView llegue informado sabremos que tambin tendremos disponibles las
referencias a sus controles hijos a travs de la propiedad Tag. Veamos el cdigo modificado
de getView() para aprovechar esta nueva optimizacin:
1
public View getView(int position, View convertView, ViewGroup parent)
2
{
3
View item = convertView;
4
ViewHolder holder;
5
6
if(item == null)
7
{
8
LayoutInflater inflater = context.getLayoutInflater();
9
item = inflater.inflate(R.layout.listitem_titular, null);
10
11
holder = new ViewHolder();
12
holder.titulo = (TextView)item.findViewById(R.id.LblTitulo);
13
holder.subtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
14
15
item.setTag(holder);
16
}
17
else
18
{
19
holder = (ViewHolder)item.getTag();
20
}
21
22
holder.titulo.setText(datos[position].getTitulo());
23
holder.subtitulo.setText(datos[position].getSubtitulo());
24
25
return(item);
26 }
Con estas dos optimizaciones hemos conseguido que la aplicacin sea mucho ms
respetuosa con los recursos del dispositivo de nuestros usuarios, algo que sin duda nos
agradecern.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.

80

Interfaz de usuario en Android: Controles de seleccin (IV)


by Sgoliver on 11/09/2010 in Android, Programacin
Tras haber visto en artculos anteriores los dos controles de seleccin ms comunes en
cualquier interfaz grfica, como son las listas desplegables (Spinner) y las listas fijas
(ListView), tanto en su versin bsica como optimizada, en este nuevo artculo vamos a
terminar de comentar los controles de seleccin con otro menos comn pero no por ello
menos til, el control GridView.

El control GridView de Android presenta al usuario un conjunto de opciones seleccionables


distribuidas de forma tabular, o dicho de otra forma, divididas en filas y columnas. Dada la
naturaleza del control ya podis imaginar sus propiedades ms importantes, que paso a
enumerar a continuacin:
android:numColumns, indica el nmero de columnas de la tabla o auto_fit si queremos
que sea calculado por el propio sistema operativo a partir de las siguientes propiedades.
android:columnWidth, indica el ancho de las columnas de la tabla.
android:horizontalSpacing, indica el espacio horizontal entre celdas.
android:verticalSpacing, indica el espacio vertical entre celdas.
android:stretchMode, indica qu hacer con el espacio horizontal sobrante. Si se establece al
valor columnWidth este espacio ser absorbido a partes iguales por las columnas de la
tabla. Si por el contrario se establece a spacingWidth ser absorbido a partes iguales por
los espacios entre celdas.
Veamos cmo definiramos un GridView de ejemplo en nuestra aplicacin:
1 <GridView android:id="@+id/GridOpciones"
2
android:layout_width="match_parent"
3
android:layout_height="match_parent"
4
android:numColumns="auto_fit"
5
android:columnWidth="80px"
6
android:horizontalSpacing="5dp"
7
android:verticalSpacing="10dp"
8
android:stretchMode="columnWidth" />
Una vez definida la interfaz de usuario, la forma de asignar los datos desde el cdigo de la
aplicacin es completamente anloga a la ya comentada tanto para las listas desplegables
como para las listas estticas: creamos un array genrico que contenga nuestros datos de
prueba, declaramos un adaptador de tipoArrayAdapter (como ya comentamos, si los datos
proceden de una base de datos lo normal ser utilizar un SimpleCursorAdapter, pero de eso
nos ocuparemos ms adelante en el curso) pasndole en este caso un layout genrico
(simple_list_item_1, compuesto por un simple TextView) y asociamos el adaptador al
control GridView mediante su mtodo setAdapter():
1
private String[] datos = new String[50];
2
//...
3
for(int i=1; i<=50; i++)

81

4
5
6
7
8
9
10
11
Por

datos[i-1] = "Dato " + i;


ArrayAdapter<String> adaptador =
new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datos);
grdOpciones = (GridView)findViewById(R.id.GridOpciones);
grdOpciones.setAdapter(adaptador);
defecto, los datos del array se aadirn al control GridView ordenados por filas, y por

supuesto, si no caben todos en la pantalla se podr hacer scroll sobre la tabla. Vemos en
una imagen cmo queda nuestra aplicacin de prueba:

En cuanto a los eventos disponibles, el ms interesante vuelve a ser el lanzado al


seleccionarse una celda determinada de la tabla: onItemClick. Este evento podemos
capturarlo de la misma forma que hacamos con los controles Spinner y ListView. Veamos
un ejemplo de cmo hacerlo:
1 grdOpciones.setOnItemClickListener(
2
new AdapterView.OnItemClickListener() {
3
public void onItemClick(AdapterView<?> parent,
4
android.view.View v, int position, long id) {
5
lblMensaje.setText("Opcin seleccionada: "
6
+ parent.getItemAtPosition(position));
7
}
8
});
Todo lo comentado hasta el momento se refiere al uso bsico del control GridView, pero
por supuesto podramos aplicar de forma practicamente directa todo lo comentado para las
listas en los dos artculosanteriores, es decir, la personalizacin de las celdas para presentar
datos complejos creando nuestro propio adaptador, y las distintas optimizaciones para
mejorar el rendiemiento de la aplicacin y el gasto de batera.

82

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.
Controles de seleccin (V): RecyclerView
by Admin on 24/02/2015 in Android, Programacin
Con la llegada de Android 5.0, Google ha incorporado al SDK de Android un nuevo
componente que viene a mejorar a los clsicos ListView y GridView existentes desde hace
tiempo. Probablemente no slo venga a mejorarlos sino tambin a sustituirlos en la mayora
de ocasiones ya que aporta flexibilidad de sobra para suplir la funcionalidad de ambos
controles e ir incluso ms all.
Este nuevo control se llama RecyclerView y, al igual que consegumos

con ListView y GridView, nos va a permitir mostrar en pantalla colecciones grandes de


datos. Pero lo va a hacer de una forma algo distinta a la que estbamos habituados con los
controles anteriores. Y es que RecyclerView no va a hacer casi nada por s mismo, sino
que se va a sustentar sobre otros componentes complementarios para determinar cmo
acceder a los datos y cmo mostrarlos. Los ms importantes sern los siguientes:
RecyclerView.Adapter
RecyclerView.ViewHolder
LayoutManager
ItemDecoration
ItemAnimator
Los dos primeros componentes deberais ya reconocerlos si habis revisado los captulos
anteriores delcurso dedicados a los controles de seleccin. De igual forma que hemos hecho
con los componentes anteriores, un RecyclerView se apoyar tambin en
un adaptador para trabajar con nuestros datos, en este caso un adaptador que herede de la
clase RecyclerView.Adapter. La peculiaridad en esta ocasin es que este tipo de adaptador
nos obligar en cierta medida a utilizar el patrn View Holder que ya describimos en
el segundo artculo sobre ListView, y de ah la necesidad del segundo componente de la
lista anterior, RecyclerView.ViewHolder.
Los tres siguientes s son una novedad, siendo el ms importante el primero de ellos,
el LayoutManager. Anteriormente, cuando decidamos utilizar un ListView ya sabamos
que nuestros datos se representaran de forma lineal con la posibilidad de hacer scroll en un
sentido u otro, y en el caso de elegir un GridView la representacin sera tabular. Una vista
de tipo RecyclerView por el contrario no determina por s sola la forma en que se van a
mostrar en pantalla los elementos de nuestra coleccin, sino que va a delegar esa tarea a
otro componente llamado LayoutManager, que tambin tendremos que crear y asociar

83

alRecyclerView para su correcto funcionamiento. Por suerte, el SDK incorpora de serie


tres LayoutManager para las tres representaciones ms habituales: lista vertical u horizontal
(LinearLayoutManager), tabla tradicional (GridLayoutManager) y tabla apilada o de celdas
no alineadas (StaggeredGridLayoutManager). Por tanto, siempre que optemos por alguna
de estas distribuciones de elementos no tendremos que crear nuestro
propio LayoutManager personalizado, aunque por supuesto nada nos impide hacerlo, y ah
uno de los puntos fuertes del nuevo componente: su flexibilidad.
Los dos ltimos componentes de la lista se encargarn de definir cmo se representarn
algunos aspectos visuales concretos de nuestra coleccin de datos (ms all de
la distribucin definida por elLayoutManager), por ejemplo marcadores o separadores de
elementos, y de cmo se animarn los elementos al realizarse determinadas acciones sobre
la coleccin, por ejemplo al aadir o eliminar elementos.
Como hemos comentado, no siempre ser obligatorio implementar todos estos
componentes para hacer uso de un RecyclerView. Lo ms habitual ser implementar
el Adapter y el ViewHolder, utilizar alguno de los LayoutManager predefinidos, y slo en
caso de necesidad crear los ItemDecoration eItemAnimator necesarios para dar un toque de
personalizacin especial a nuestra aplicacin.
En el resto del artculo voy a intentar describir de forma ms o menos detallada cmo crear
una aplicacin de ejemplo anloga a la creada en el captulo sobre ListView, pero
utilizando en esta ocasin el nuevo componente RecyclerView.
Vamos a empezar por la parte ms sencilla, crearemos un nuevo proyecto en Android
Studio y aadiremos a la seccin de dependencias del fichero build.gradle del mdulo
principal la referencia a la librera de soporte recyclerview-v7, lo que nos permitir el uso
del componente RecyclerView en la aplicacin:
1 dependencies {
2
compile fileTree(dir: 'libs', include: ['*.jar'])
3
compile 'com.android.support:appcompat-v7:21.0.3'
4
compile 'com.android.support:recyclerview-v7:+'
5 }
Tras esto ya podremos aadir un nuevo RecyclerView al layout de nuestra actividad
principal:
1 <android.support.v7.widget.RecyclerView
2
android:id="@+id/RecView"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent" />
Como siguiente paso escribiremos nuestro adaptador. Este adaptador deber extender a la
claseRecyclerView.Adapter, de la cual tendremos que sobrescribir principalmente tres
mtodos:

84

onCreateViewHolder(). Encargado de crear los nuevos objetos ViewHolder necesarios para


los elementos de la coleccin.
onBindViewHolder(). Encargado de actualizar los datos de un ViewHolder ya existente.
onItemCount(). Indica el nmero de elementos de la coleccin de datos.
Un par de anotaciones antes de seguir. En primer lugar, para nuestra aplicacin de ejemplo
utilizar como fuente de datos una lista (ArrayList) de objetos de tipo Titular, la misma
clase que ya utilizamos en elcaptulo sobre el control ListView. En segundo lugar, la
representacin de los datos que queremos lograr ser tambin la misma que conseguimos
en aquel ejemplo, es decir, cada elemento de la lista consistir en dos lneas sencillas de
texto donde mostraremos el ttulo y subttulo de cada objeto Titular. Por tanto, tanto la
clase Titular,
como
el
layout
asociado
a los
elementos de
la
lista
(fichero listitem_titular.xml) podis copiarlos directamente de dicho proyecto.
Como ya dijimos, la clase RecyclerView.Adapter nos obligar a hacer uso del
patrn ViewHolder(recomiendo leer el artculo donde explicamos la utilidad de este
patrn). Por tanto, para poder seguir con la implementacin del adaptador debemos definir
primero el ViewHolder necesario para nuestro caso de ejemplo. Lo definiremos como clase
interna a nuestro adaptador, extendiendo de la clase RecyclerView.ViewHolder, y ser
bastante sencillo, tan slo tendremos que incluir como atributos las referencias a los
controles del layout de un elemento de la lista (en nuestro caso los dos TextView) e
inicializarlas en el constructor utilizando como siempre el mtodo findViewById() sobre la
vista recibida como parmetro. Por comodidad aadiremos tambin un mtodo auxiliar, que
llamaremosbindTitular(), que se encargue de asignar los contenidos de los dos cuadros de
texto a partir de un objeto Titular cuando nos haga falta.
1
public class AdaptadorTitulares
2
extends RecyclerView.Adapter<AdaptadorTitulares.TitularesViewHolder> {
3
4
//...
5
6
public static class TitularesViewHolder
7
extends RecyclerView.ViewHolder {
8
9
private TextView txtTitulo;
10
private TextView txtSubtitulo;
11
12
public TitularesViewHolder(View itemView) {
13
super(itemView);
14
15
txtTitulo = (TextView)itemView.findViewById(R.id.LblTitulo);
16
txtSubtitulo = (TextView)itemView.findViewById(R.id.LblSubTitulo);
17
}
18

85

19
public void bindTitular(Titular t) {
20
txtTitulo.setText(t.getTitulo());
21
txtSubtitulo.setText(t.getSubtitulo());
22
}
23
}
24
25
//...
26 }
Finalizado nuestro ViewHolder podemos ya seguir con la implementacin del adaptador
sobrescribiendo los mtodos indicados. En el mtodo onCreateViewHolder() nos
limitaremos a inflar una vista a partir del layout correspondiente a los elementos de la lista
(listitem_titular), y crear y devolver un nuevo ViewHolderllamando al constructor de
nuestra clase TitularesViewHolder pasndole dicha vista como parmetro.
Los dos mtodos restantes son an ms sencillos. En onBindViewHolder() tan slo
tendremos que recuperar el objeto Titular correspondiente a la posicin recibida como
parmetro y asignar sus datos sobre el ViewHolder tambin recibido como parmetro. Por
su parte, getItemCount() tan slo devolver el tamao de la lista de datos.
1
public class AdaptadorTitulares
2
extends RecyclerView.Adapter<AdaptadorTitulares.TitularesViewHolder> {
3
4
private ArrayList<Titular> datos;
5
6
//...
7
8
public AdaptadorTitulares(ArrayList<Titular> datos) {
9
this.datos = datos;
10
}
11
12
@Override
13
public TitularesViewHolder onCreateViewHolder(ViewGroup viewGroup, int
14 viewType) {
15
16
View itemView = LayoutInflater.from(viewGroup.getContext())
17
.inflate(R.layout.listitem_titular, viewGroup, false);
18
19
TitularesViewHolder tvh = new TitularesViewHolder(itemView);
20
21
return tvh;
22
}
23
24
@Override
25
public void onBindViewHolder(TitularesViewHolder viewHolder, int pos) {
26
Titular item = datos.get(pos);
27

86

28
29
30
31
32
33
34
35
36

viewHolder.bindTitular(item);
}
@Override
public int getItemCount() {
return datos.size();
}
//...
}

Con esto tendramos finalizado el adaptador, por lo que ya podramos asignarlo


al RecyclerView en nuestra actividad principal. Esto es tan sencillo como lo era en el caso
de ListView/GridView,
tan
slo
tendremos
que
crear
nuestro adaptador
personalizado AdaptadorTitulares pasndole como parmetro la lista de datos y asignarlo al
control RecyclerView mediante setAdapter().
1
public class MainActivity extends ActionBarActivity {
2
3
private RecyclerView recView;
4
5
private ArrayList<Titular> datos;
6
7
@Override
8
protected void onCreate(Bundle savedInstanceState) {
9
super.onCreate(savedInstanceState);
10
setContentView(R.layout.activity_main);
11
12
//inicializacin de la lista de datos de ejemplo
13
datos = new ArrayList<Titular>();
14
for(int i=0; i<50; i++)
15
datos.add(new Titular("Ttulo " + i, "Subttulo item " + i));
16
17
//Inicializacin RecyclerView
18
recView = (RecyclerView) findViewById(R.id.RecView);
19
recView.setHasFixedSize(true);
20
21
final AdaptadorTitulares adaptador = new AdaptadorTitulares(datos);
22
23
recView.setAdapter(adaptador);
24
25
//...
26
}
27
28
//...
29 }

87

Algunos comentarios ms sobre el cdigo anterior. He aprovechado el


mtodo onCreate() para inicializar la lista de datos de ejemplo (atributo datos) con 50
titulares con un texto tipo. Tras obtener la referencia alRecyclerView he incluido tambin
una llamada a setHasFixedSize(). Aunque esto no es obligatorio, s es conveniente hacerlo
cuando tengamos certeza de que el tamao de nuestro RecyclerView no va a variar (por
ejemplo debido a cambios en el contenido del adaptador), ya que esto permitir
aplicar determinadas optimizaciones sobre el control.
El siguiente paso obligatorio ser asociar al RecyclerView un LayoutManager determinado,
para determinar la forma en la que se distribuirn los datos en pantalla. Como ya dijimos, si
nuestra intencin es mostrar los datos en forma de lista o tabla (al estilo de los
antiguos ListView o GridView)
no
tendremos
que
implementar
nuestro
propio LayoutManager (una tarea nada sencilla), ya que el SDK proporciona varias clases
predefinidas para los tipos ms habituales. En nuestro caso particular queremos mostrar los
datos en forma de lista con desplazamiento vertical. Para ello tenemos disponible la
claseLinearLayoutManager, por lo que tan slo tendremos que instanciar un objeto de dicha
clase
indicando
en el
constructor
la
orientacin del desplazamiento
(LinearLayoutManager.VERTICAL oLinearLayoutManager.HORIZONTAL)
y
lo
asignaremos al RecyclerView mediante el mtodosetLayoutManager(). Esto lo haremos
justo despus del cdigo anterior, tras la asignacin del adaptador:
1 //...
2
3 recView.setAdapter(adaptador);
4
5 recView.setLayoutManager(
6
new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
7
8 //...
Llegados aqu ya sera posible ejecutar la aplicacin de ejemplo para ver cmo quedan
nuestros datos en pantalla, ya que los dos componentes que nos falta por comentar
(ItemDecoration e ItemAnimator) no son obligatorios para el funcionamiento bsico del
control RecyclerView.

88

Lo bueno de todo lo que llevamos hasta el momento, es que si cambiramos de idea y


quisiramos mostrar los datos de forma tabular tan slo tendramos que cambiar la
asignacin del LayoutManageranterior y utilizar un GridLayoutManager, al que pasaremos
como parmetro el nmero de columnas a mostrar.
1 //...
2
3 recView.setAdapter(adaptador);
4
5 recView.setLayoutManager(new GridLayoutManager(this,3));
6
7 //...
Con este simple cambio la aplicacin quedara con el siguiente aspecto:

El siguiente paso que nos podemos plantear es cmo responder a los eventos que se
produzcan sobre elRecyclerView, como opcin ms habitual el evento click sobre un

89

elemento de la lista. Para sorpresa de todos la clase RecyclerView no tiene incluye un


evento onItemClick() como
ocurra
en
el
caso
deListView.
Una
vez
ms, RecyclerView delegar tambin esta tarea a otro componente, en este caso a la propia
vista que conforma cada elemento de la coleccin.
Ser por tanto dicha vista a la que tendremos que asociar el cdigo a ejecutar como
respuesta al evento click. Esto podemos hacerlo de diferentes formas, yo tan slo mostrar
una de ellas. En nuestro caso aprovecharemos la creacin de cada nuevo ViewHolder para
asignar a su vista asociada el eventoonClick. Adicionalmente, para poder hacer esto desde
fuera del adaptador, incluiremos el listener correspondiente como atributo del adaptador, y
dentro de ste nos limitaremos a asignar el evento a la vista del nuevo ViewHolder y a
lanzarlo cuando sea necesario desde el mtodo onClick(). Creo que es ms fcil verlo sobre
el cdigo:
1
public class AdaptadorTitulares
2
extends RecyclerView.Adapter<AdaptadorTitulares.TitularesViewHolder>
3
implements View.OnClickListener {
4
5
//...
6
private View.OnClickListener listener;
7
8
@Override
9
public TitularesViewHolder onCreateViewHolder(ViewGroup viewGroup, int
10 viewType) {
11
View itemView = LayoutInflater.from(viewGroup.getContext())
12
.inflate(R.layout.listitem_titular, viewGroup, false);
13
14
itemView.setOnClickListener(this);
15
16
TitularesViewHolder tvh = new TitularesViewHolder(itemView);
17
18
return tvh;
19
}
20
21
//...
22
23
public void setOnClickListener(View.OnClickListener listener) {
24
this.listener = listener;
25
}
26
27
@Override
28
public void onClick(View view) {
29
if(listener != null)
30
listener.onClick(view);
31
}

90

}
Como vemos, nuestro adaptador implementar la interfaz OnClickListener, declarar un
listener (el que podremos asignar posteriormente desde fuera del adaptador) como atributo
de la clase, en el momento de crear el nuevo ViewHolder asociar el evento a la vista, y por
ltimo implementar el evento onClick, que se limitar a lanzar el mismo evento sobre el
listener externo. El mtodo adicional setOnClickListener()nos serivir para asociar el
listener real a nuestro adaptador en el momento de crearlo. Veamos cmo quedara nuestra
actividad principal con este cambio:
public class MainActivity extends ActionBarActivity {
1
2
//...
3
4
@Override
5
protected void onCreate(Bundle savedInstanceState) {
6
7
//...
8
9
recView = (RecyclerView) findViewById(R.id.RecView);
10
recView.setHasFixedSize(true);
11
12
final AdaptadorTitulares adaptador = new AdaptadorTitulares(datos);
13
14
adaptador.setOnClickListener(new View.OnClickListener() {
15
@Override
16
public void onClick(View v) {
17
Log.i("DemoRecView", "Pulsado el elemento " + recView.getChildPosition(v));
18
}
19
});
20
21
recView.setAdapter(adaptador);
22
23
recView.setLayoutManager(new
24
LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
25
26
//...
27
}
28
}
En este caso al pulsar sobre un elemento de la lista tan slo escribiremos un mensaje de log
indicando la posicin en la lista del elemento pulsado, lo que conseguimos llamando al
mtodo getChildPosition()del RecyclerView.
Con esto ya podramos volver a ejecutar la aplicacin de ejemplo y comprobar si todo
funciona correctamente segn lo definido al pulsar sobre los elementos de la lista.

91

Por ltimo vamos a describir brevemente los dos componentes restantes relacionados
con RecyclerView.
En primer lugar nos ocuparemos de ItemDecoration. Los ItemDecoration nos servirn para
personalizar el aspecto de un RecyclerView ms all de la distribucin de los elementos en
pantalla. El ejemplo tpico de esto son los separadores o divisores de una
lista. RecyclerView no tiene ninguna propiedad divider como en el caso del ListView, por
lo que dicha funcionalidad debemos suplirla con un ItemDecoration.
Crear un ItemDecoration personalizado no es una tarea demasiado fcil, o al menos no
inmediata, por lo que por ahora no nos detendremos mucho en ello. Para el caso de los
separadores de lista podemos encontrar en un ejemplo del propio SDK de
Android un ejemplo de ItemDecoration que lo implementa. La clase en cuestin se
llama DividerItemDecoration y la incluyo en el proyecto de ejemplo de este
apartado (tenis el enlace a github al final del artculo). Si estudiamos un poco el cdigo
veremos que tendremos que extender de la clase RecyclerView.ItemDecoration e
implementar sus mtodosgetItemOffsets() y onDraw()/onDrawOver(). El primero de ellos
se encargar de definir los lmites del elemento de la lista y el segundo de dibujar el
elemento de personalizacin en s. En el fondo no es complicado, aunque s lleva su trabajo
construirlo desde cero.
Para utilizar este componente deberemos simplemente crear el objeto y asociarlo a
nuestro RecyclerViewmediante addItemDecoration() en nuestra actividad principal:
1
public class MainActivity extends ActionBarActivity {
2
3
//...
4
5
@Override
6
protected void onCreate(Bundle savedInstanceState) {
7
8
//...
9
10
recView = (RecyclerView) findViewById(R.id.RecView);
11
recView.setHasFixedSize(true);
12
13
final AdaptadorTitulares adaptador = new AdaptadorTitulares(datos);
14
15
adaptador.setOnClickListener(new View.OnClickListener() {
16
@Override
17
public void onClick(View v) {
18
Log.i("DemoRecView", "Pulsado el elemento " + recView.getChildPosition(v));
19
}
20
});
21

92

22
23
24
25
26
27
28
29
30
31
32

recView.setAdapter(adaptador);
recView.setLayoutManager(
new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
recView.addItemDecoration(
new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
//...
}
}

Como
nota
adicional
indicar
que
podremos
aadir
a
un RecyclerView tantos ItemDecoration como queramos, que se aplicarn en el mismo
orden que se hayan aadido con addItemDecoration().
Por ltimo hablemos muy brevemente de ItemAnimator. Un componente ItemAnimator nos
permitir definir las animaciones que mostrar nuestro RecyclerView al realizar las
acciones ms comunes sobre un elemento (aadir, eliminar, mover, modificar). Este
componente tampoco es sencillo de implementar, pero por suerte el SDK tambin
proporciona una implementacin por defecto que puede servirnos en la mayora de
ocasiones, aunque por supuesto podremos personalizar creando nuestro
propioItemAnimator. Esta implementacin por defecto se llama DefaultItemAnimator y
podemos asignarla alRecyclerView mediante el mtodo setItemAnimator().
1
public class MainActivity extends ActionBarActivity {
2
3
//...
4
5
@Override
6
protected void onCreate(Bundle savedInstanceState) {
7
8
//...
9
10
recView = (RecyclerView) findViewById(R.id.RecView);
11
recView.setHasFixedSize(true);
12
13
final AdaptadorTitulares adaptador = new AdaptadorTitulares(datos);
14
15
adaptador.setOnClickListener(new View.OnClickListener() {
16
@Override
17
public void onClick(View v) {
18
Log.i("DemoRecView", "Pulsado el elemento " + recView.getChildPosition(v));
19
}
20
});
21

93

22
23
24
25
26
27
28
29
30
31
32
33
34

recView.setAdapter(adaptador);
recView.setLayoutManager(
new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
recView.addItemDecoration(
new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
recView.setItemAnimator(new DefaultItemAnimator());
//..
}
}

Para probar su funcionamiento vamos a aadir a nuestro layout principal tres botones con
las opciones de aadir, eliminar y mover un elemento de la lista (concretamente el segundo
elemento de la lista). La implementacin de estos botones es bastante sencilla y la
incluiremos tambin en el onCreate() de la actividad principal:
1
btnInsertar = (Button)findViewById(R.id.BtnInsertar);
2
3
btnInsertar.setOnClickListener(new View.OnClickListener() {
4
@Override
5
public void onClick(View v) {
6
datos.add(1, new Titular("Nuevo titular", "Subtitulo nuevo titular"));
7
adaptador.notifyItemInserted(1);
8
}
9
});
10
11 btnEliminar = (Button)findViewById(R.id.BtnEliminar);
12
13 btnEliminar.setOnClickListener(new View.OnClickListener() {
14
@Override
15
public void onClick(View v) {
16
datos.remove(1);
17
adaptador.notifyItemRemoved(1);
18
}
19 });
20
21 btnMover = (Button)findViewById(R.id.BtnMover);
22
23 btnMover.setOnClickListener(new View.OnClickListener() {
24
@Override
25
public void onClick(View v) {
26
27
Titular aux = datos.get(1);
28
datos.set(1,datos.get(2));

94

29
30
31
32
33

datos.set(2,aux);
adaptador.notifyItemMoved(1, 2);
}
});

Lo ms interesante a destacar del cdigo anterior son los mtodos de notificacin de


cambios del adaptador. Tras realizar cada accin sobre los datos debemos llamar a su
mtodo de notificacin correspondiente de forma que pueda refrescarse correctamente el
control y se ejecute la animacin correspondiente. As, tras aadir un elemento llamaremos
a notifyItemInserted() con la posicin del nuevo elemento, al eliminar un datos llamaremos
a notifyItemRemoved(), al actualizar un dato anotifyItemChanged() y al moverlo de lugar
a notifyItemMoved().
Ejecutemos por ltima vez la aplicacin de ejemplo para revisar que las animaciones por
defecto funcionan tambin segn lo esperado.

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.

Interfaz de usuario en Android: CardView


by Sgoliver on 11/03/2015 in Android, Programacin
Junto a Android 5.0 Lollipop nos lleg un nuevo componente que, si bien se llevaba
utilizando ya algn tiempo en diversas aplicaciones, no tena soporte directo en el SDK.
Este nuevo componente llamadoCardView es la implementacin que nos
proporciona Google del elemento visual en forma de tarjetas de informacin que tanto

95

utiliza en muchas de sus aplicaciones, entre ellas Google Now, quiz la que ms a ayudado
a popularizar este componente.
Hasta la llegada de Android 5.0, para utilizar estas tarjetas en la interfaz de nuestras
aplicaciones tenamos que recurrir a libreras de terceros o bien trabajar un poco para
implementar nuestra propia versin. Sin embargo ahora las tenemos disponibles en forma
de nueva librera de soporte oficial, proporcionada junto al SDK de Android.
Para hacer uso de la librera tan slo tendremos que hacer referencia a ella en la seccin de
dependencias del fichero build.gradle del mdulo principal de la aplicacin:
1 dependencies {
2
...
3
compile 'com.android.support:cardview-v7:21.0.+'
4 }
Una vez incluida la referencia a la librera, la utilizacin del componente es muy sencilla.
Tan slo tendremos que aadir a nuestro layout XML un control de
tipo<android.support.v7.widget.CardView> y establecer algunas de sus propiedades
principales. Yo por ejemplo le asignar una altura de 200dp (layout_height), una anchura
que se ajuste a su control padre (layout_width), y un color de fondo amarillo estilo post-it
(cardBackgroundColor).
1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
xmlns:card_view="http://schemas.android.com/apk/res-auto"
3
xmlns:tools="http://schemas.android.com/tools"
4
android:layout_width="match_parent"
5
android:layout_height="match_parent"
6
android:paddingLeft="@dimen/activity_horizontal_margin"
7
android:paddingRight="@dimen/activity_horizontal_margin"
8
android:paddingTop="@dimen/activity_vertical_margin"
9
android:paddingBottom="@dimen/activity_vertical_margin"
10
android:orientation="vertical"
11
tools:context=".MainActivity">
12
13
<android.support.v7.widget.CardView
14
android:id="@+id/card2"
15
android:layout_width="match_parent"
16
android:layout_height="200dp"
17
card_view:cardBackgroundColor="#fffffe91" >
18
19
<TextView
20
android:id="@+id/txt1"
21
android:layout_width="match_parent"
22
android:layout_height="wrap_content"
23
android:padding="10dp"

96

24
25
26
27
28

android:text="@string/tarjeta_1" />
</android.support.v7.widget.CardView>
</LinearLayout>

Por supuesto, como en el caso de cualquier otro contenedor (un CardView no es ms que
una extensin de FrameLayout con esquinas redondeadas y una sombra inferior), dentro de
un CardView podemos aadir todos los controles que necesitemos. Como podis ver en el
cdigo anterior, a modo de ejemplo he aadido tan solo una etiqueta de texto (TextView).
Si ejecutamos en este momento el ejemplo sobre un dispositivo o emulador con Android
4.x encontraremos el resultado que esperbamos:

Sin embargo, si hacemos lo mismo sobre Android 5.x el resultado ser el que se muestra
en la siguiente imagen:

Notis la diferencia en la sombra en los bordes derecho e izquierdo de la tarjeta con


respecto a la captura de Android 4? Efectivamente, la sombra casi no es visible por los
laterales. Sin entrar en mucho detalle, tan slo indicar que esto es debido a las diferencias

97

en la forma de calcular y mostrar las sombras (ente otras cosas) en Android 5 respecto a
versiones anteriores. Estas diferencias de comportamiento entre versiones del sistema
operativo provocan que en casos como el mostrado para un CardView, los tamaos
efectivos del contenido de la tarjeta y los mrgenes o padding de la vista (utilizado en esta
ocasin para dibujar la sombra) no coincidan entre versiones, lo que nos puede llevar a
alguna que otra sorpresa. En este caso, la solucin es sencilla, y pasa por utilizar una
propiedad adicional de CardViewllamada cardUseCompatPadding. Dando un valor true a
esta propiedad conseguiremos que en en Android 5.x se aada un padding extra
al CardView de forma que su representacin en pantalla sea similar a la de versiones
anteriores.
1 <android.support.v7.widget.CardView
2
android:id="@+id/card2"
3
android:layout_width="match_parent"
4
android:layout_height="200dp"
5
card_view:cardBackgroundColor="#fffffe91"
6
card_view:cardUseCompatPadding="true" >
Con este simple cambio, conseguiremos un comportamiento muy similar tanto en Android
4 como en Android 5.
Adems del color de fondo que ya hemos comentado, tambin podremos definir la
elevacin de la tarjeta y el radio de las esquinas redondeadas, utilizando las
propiedades cardElevation y cardCornerRadiusrespectivamente.
No son muchas ms las opciones de personalizacin que nos ofrece CardView, pero con
poco esfuerzo deben ser suficientes para crear tarjetas ms sofisticadas, jugando por
supuesto tambin con el contenido de la tarjeta. Como ejemplo, si incluimos una imagen a
modo de fondo (ImageView), y una etiqueta de texto superpuesta y alineada al borde
inferior (layout_gravity=bottom) con fondo negro algo traslcido (por
ejemplo background=#8c000000) y un color y tamao de texto adecuados, podramos
conseguir una tarjeta con el aspecto siguiente en Android 5.x:

El cdigo concreto para conseguir lo anterior sera el siguiente:

98

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

<android.support.v7.widget.CardView
android:id="@+id/card1"
android:layout_width="match_parent"
android:layout_height="200dp"
card_view:cardCornerRadius="6dp"
card_view:cardElevation="10dp"
card_view:cardUseCompatPadding="true" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/city"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/txt2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/tarjeta_2"
android:layout_gravity="bottom"
android:background="#8c000000"
android:textColor="#ffe3e3e3"
android:textSize="30sp"
android:textStyle="bold"/>
</android.support.v7.widget.CardView>

Todo parece correcto, pero si ejecutamos una vez ms el ejemplo en un


dispositivo/emulador con Android 4.x veremos lo siguiente:

Nueva sorpresa. Como podemos ver, en Android 4.x y anteriores la imagen no llega al
borde delCardView, sino que se introduce un pequeo margen adicional que evita que se
tengan que hacer las operaciones necesarias para redondear las esquinas de la imagen. Esta
operacin de redondeo, aunque a priori pueda parecer simple, es bastante costosa. En
Android 5.x puede realizarse sin afectar a la experiencia de usuario gracias a que muchas de
las operaciones necesarias para mostrar la interfaz grfica de la aplicacin (animaciones,

99

sombras, ) se realizan en un hilo de ejecucin independiente del principal (render


thread). En Android 4 y anteriores se evitan estas operaciones costosas dado que se
ejecutaran en el hilo principal pudiendo afectar al rendimiento de la aplicacin.
Para evitar este margen adicional en Android 4 y anteriores, puede asignarse el
valor false a la propiedad cardPreventCornerOverlap del CardView.
1 <android.support.v7.widget.CardView
2
android:id="@+id/card1"
3
android:layout_width="match_parent"
4
android:layout_height="200dp"
5
card_view:cardCornerRadius="6dp"
6
card_view:cardElevation="10dp"
7
card_view:cardUseCompatPadding="true"
8
card_view:cardPreventCornerOverlap="false" >
Esto evitar que se incluya el margen extra alrededor de la imagen, aunque no redondear
las esquinas de la imagen, quedando un efecto extrao respecto a la sombra
del CardView si se ha utilizado un radio amplio para redondear las esquinas (en el ejemplo
he utilizado un radio alto para que se note bien el efecto, si el radio es menor podra pasar
ms desapercibido).

Si queris corregir completamente este efecto, an a costa de afectar al rendimiento de


la aplicacin, podis echar un vistazo a este gist de Gabriele Mariotti, donde utiliza un tipo
de Drawable personalizado (RoundCornersDrawable) que redondea las esquinas de la
imagen para adaptarla exactamente a los bordes y la sombra de un CardView en APIs < 21
(Android 5.0). De cualquier forma, antes de utilizar esta opcin yo optara por retocar el
layout de forma que la imagen no ocupe todo el fondo de la tarjeta, eliminando as el
problema planteado.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.

Enlaces de inters:
Cards en Material Design
Creating Lists and Cards en Gua de desarrollo Android
CardView en Referencia APIs

100

Interfaz de usuario en Android: Controles personalizados (I)


by Sgoliver on 16/09/2010 in Android, Programacin
En artculos anteriores de la serie hemos conocido y aprendido a utilizar muchos de los
controles que proporciona Android en su SDK. Con la ayuda de estos controles podemos
disear interfaces grficas de lo ms variopinto pero en ocasiones, si queremos dar un toque
especial y original a nuestra aplicacin, o simplemente si necesitamos cierta funcionalidad
no presente en los componentes estandar de Android, nos vemos en la necesidad de crear
nuestros propios controles personalizados, diseados a la medida de nuestros requisitos.
Android admite por supuesto crear controles personalizados (custom views), y permite
hacerlo de diferentes formas:
1. Extendiendo la funcionalidad de un control ya existente.
2. Combinando varios controles para formar otro ms complejo.
3. Diseando desde cero un nuevo control.
En este primer artculo sobre el tema vamos a hablar de la primera opcin, es decir, vamos
a ver cmo podemos crear un nuevo control partiendo de la base de un control ya existente.
A modo de ejemplo, vamos a extender el control EditText (cuadro de texto) para que
muestre en todo momento el nmero de caracteres que contiene a medida que se escribe en
l.
En la esquina superior derecha del cuadro de texto vamos a mostrar el nmero de caracteres
del mensaje de texto introducido, que ira actualizndose a medida que modificamos el
texto.
Para empezar, vamos a crear una nueva clase java que extienda del control que queremos
utilizar como base, en este caso EditText.
1 public class ExtendedEditText extends EditText
2 {
3
//...
4 }
Tras esto, sobreescribiremos siempre los tres constructores heredados, donde por el
momento nos limitaremos a llamar al mismo constructor de la clase padre.
1
2
3
4
5
6
7
8

public ExtendedEditText(Context context, AttributeSet attrs, int defStyle){


super(context, attrs,defStyle);
}
public ExtendedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}

101

9
10
11

public ExtendedEditText(Context context) {


super(context);
}

Por ltimo el paso ms importante. Dado que queremos modificar el aspecto del control
para aadir el contador de caracteres tendremos que sobreescribir el evento onDraw(), que
es llamado por Android cada vez que hay que redibujar el control en pantalla. Este mtodo
recibe como parmetro un objeto Canvas, que no es ms que el lienzo sobre el que
podemos dibujar todos los elementos extra necesarios en el control. El objeto Canvas,
proporciona una serie de mtodos para dibujar cualquier tipo de elemento (lineas,
rectngulos, elipses, texto, bitmaps, ) sobre el espacio ocupado por el control. En nuestro
caso tan slo vamos a necesitar dibujar sobre el control un rectngulo que sirva de fondo
para el contador y el texto del contador con el nmero de caracteres actual del cuadro de
texto. No vamos a entrar en muchos detalles sobre la forma de dibujar grficos, pero vamos
a ver al menos las acciones principales.
En primer lugar definiremos los pinceles (objetos Paint) que utilizaremos para dibujar,
uno de ellos (p1) de color negro y relleno slido para el fondo del contador, y otro (p2) de
color blanco para el texto. Para configurar los colores, el estilo de fondo y el tamao del
texto utilizaremos los mtodos setColor(),setStyle() y setTextSize() respectivamente:
1
private void inicializacion()
2
{
3
Paint p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
4
p1.setColor(Color.BLACK);
5
p1.setStyle(Style.FILL);
6
7
Paint p2 = new Paint(Paint.ANTI_ALIAS_FLAG);
8
p2.setColor(Color.WHITE);
9
p2.setTextSize(20);
10 }
Dado que estos elementos tan slo har falta crearlos la primera vez que se dibuje el
control, para evitar trabajo innecesario no incluiremos su definicin en el
mtodo onDraw(), sino que los definiremos como atributos de la clase y los inicializaremos
en el constructor del control (en los tres).
Una vez definidos los diferentes pinceles necesarios, dibujaremos el fondo y el texto del
contador mediante los mtodos drawRect() y drawText(), respectivamente, del objeto
canvas recibido en el evento.
Lo nico a tener en cuenta es que todos estos mtodos de dibujo reciben las unidades en
pixeles y por tanto si utilizamos valores fijos tendremos problemas al visualizar los
resultados en pantallas con distintas densidades de pxeles. Para evitar esto en lo posible,
tendremos que convertir nuestros valores de pxeles a algn valor dependiente de la

102

densidad de la pantalla, lo que en Android podemos conseguir multiplicando siempre


nuestros pxeles por un factor de escala que podemos obtener mediante los
mtodos getResources().getDisplayMetrics().density.
Tras
obtener
este
valor,
multiplicaremos por l todas nuestras unidades en pxeles para conseguir los mismos
efectos en cualquier pantalla. Veamos cmo quedara el cdigo completo:
1
private void inicializacion()
2
{
3
//...
4
5
escala = getResources().getDisplayMetrics().density;
6
}
7
8
//...
9
10 @Override
11 public void onDraw(Canvas canvas)
12 {
13
//Llamamos al mtodo de la clase base (EditText)
14
super.onDraw(canvas);
15
16
//Dibujamos el fondo negro del contador
17
canvas.drawRect(this.getWidth()-30*escala, 5*escala,
18
this.getWidth()-5*escala, 20*escala, p1);
19
20
//Dibujamos el nmero de caracteres sobre el contador
21
canvas.drawText("" + this.getText().toString().length(),
22
this.getWidth()-28*escala, 17*escala, p2);
23 }
Como puede comprobarse, a estos mtodos se les pasa como parmetro las coordenadas del
elemento a dibujar relativas al espacio ocupado por el control y el pincel a utilizar en cada
caso.
Hecho esto, ya tenemos finalizado nuestro cuadro de texto personalizado con contador de
caracteres. Para aadirlo a la interfaz de nuestra aplicacin lo incluiremos en el layout
XML de la ventana tal como haramos con cualquier otro control, teniendo en cuenta que
deberemos hacer referencia a l con el nombre completo de la nueva clase creada (incluido
el
paquete
java),
que
en
mi
seranet.sgoliver.android.controlpers1.ExtendedEditText.
1 <net.sgoliver.android.controlpers1.ExtendedEditText
2
android:layout_width="match_parent"
3
android:layout_height="wrap_content" />

caso

particular

103

Para finalizar, veamos cmo quedara nuestro control ejecutando la aplicacin de ejemplo
en el emulador:

En el siguiente artculo veremos cmo crear un control personalizado utilizando la segunda


de las opciones expuestas, es decir, combinando varios controles ya existentes.
Comentaremos adems como aadir eventos y propiedades personalizadas a nuestro control
y cmo hacer referencia a dichas propiedades desde su definicin XML.
Interfaz de usuario en Android: Controles personalizados (II)
by Sgoliver on 23/12/2010 in Android, Programacin
Ya vimos cmo Android ofrece tres formas diferentes de crear controles personalizados
para nuestras aplicaciones y dedicamos el artculo anterior a comentar la primera de las
posibilidades, que consista en extender la funcionalidad de un control ya existente.
En este segundo artculo sobre el tema vamos a centrarnos en la creacin de controles
compuestos, es decir, controles personalizados construidos a partir de varios controles
estandar, combinando la funcionalidad de todos ellos en un slo control reutilizable en otras
aplicaciones.
Como ejemplo ilustrativo vamos a crear un control de identificacin (login) formado por
varios controles estandar de Android. La idea es conseguir un control como el que se
muestra la siguiente imagen esquemtica:

104

A efectos didcticos, y para no complicar ms el ejemplo, vamos a aadir tambin a la


derecha del botnLogin una etiqueta donde mostrar el resultado de la identificacin del
usuario (login correcto o incorrecto).
A este control aadiremos adems eventos personalizados, veremos como aadirlo a
nuestras aplicaciones, y haremos que se pueda personalizar su aspecto desde el layout XML
de nuestra interfaz utilizando tambin atributos XML personalizados.
Empecemos por el principio. Lo primero que vamos a hacer es construir la interfaz de
nuestro control a partir de controles sencillos: etiquetas, cuadros de texto y botones. Para
ello vamos a crear un nuevo layout XML en la carpeta \res\layout con el nombre
control_login.xml. En este fichero vamos a definir la estructura del control como ya
hemos visto en muchos artculos anteriores, sin ninguna particularidad destacable. Para este
caso quedara como sigue:
1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout
3
xmlns:android="http://schemas.android.com/apk/res/android"
4
android:layout_width="match_parent"
5
android:layout_height="wrap_content"
6
android:orientation="vertical"
7
android:padding="10dp">
8
9
<TextView android:id="@+id/TextView01"
10
android:layout_width="wrap_content"
11
android:layout_height="wrap_content"
12
android:text="@string/usuario"
13
android:textStyle="bold" />
14
15
<EditText android:id="@+id/TxtUsuario"
16
android:layout_height="wrap_content"
17
android:layout_width="match_parent"
18
android:inputType="text" />
19
20
<TextView android:id="@+id/TextView02"
21
android:layout_width="wrap_content"
22
android:layout_height="wrap_content"
23
android:text="@string/contrasena"
24
android:textStyle="bold" />
25
26
<EditText android:id="@+id/TxtPassword"
27
android:layout_height="wrap_content"
28
android:layout_width="match_parent"
29
android:inputType="textPassword" />
30
31
<LinearLayout android:orientation="horizontal"

105

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/BtnAceptar"
android:text="@string/login"
android:paddingLeft="20dp"
android:paddingRight="20dp" />
<TextView android:id="@+id/LblMensaje"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>

A continuacin crearemos su clase java asociada donde definiremos toda la funcionalidad


de nuestro control. Dado que nos hemos basado en un LinearLayout para construir el
control, esta nueva clase deber heredar tambin de la clase java LinearLayout de Android.
Redefiniremos adems los dos constructores bsicos:
1
package net.sgoliver.android.controlpers2;
2
3
//...
4
5
public class ControlLogin extends LinearLayout
6
{
7
public ControlLogin(Context context) {
8
super(context);
9
inicializar();
10
}
11
12
public ControlLogin(Context context, AttributeSet attrs) {
13
super(context, attrs);
14
inicializar();
15
}
16
}
Como se puede observar, todo el trabajo lo dejamos para el mtodo inicializar(). En este
mtodo inflaremos el layout XML que hemos definido, obtendremos las referencias a todos
los controles y asignaremos los eventos necesarios. Todo esto ya lo hemos hecho en otras
ocasiones, por lo que tampoco nos vamos a detener mucho. Veamos como queda el mtodo
completo:

106

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

private void inicializar()


{
//Utilizamos el layout 'control_login' como interfaz del control
String infService = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li =
(LayoutInflater)getContext().getSystemService(infService);
li.inflate(R.layout.control_login, this, true);
//Obtenemoslas referencias a los distintos control
txtUsuario = (EditText)findViewById(R.id.TxtUsuario);
txtPassword = (EditText)findViewById(R.id.TxtPassword);
btnLogin = (Button)findViewById(R.id.BtnAceptar);
lblMensaje = (TextView)findViewById(R.id.LblMensaje);
//Asociamos los eventos necesarios
asignarEventos();
}

Dejaremos por ahora a un lado el mtodo asignarEventos(), volveremos sobre l ms tarde.


Con esto ya tenemos definida la interfaz y la funcionalidad bsica del nuevo control por lo
que ya podemos utilizarlo desde otra actividad como si se tratase de cualquier otro control
predefinido. Para ello haremos referencia a l utilizando la ruta completa del paquete java
utilizado, en nuestro caso quedara comonet.sgoliver.android.controlpers2.ControlLogin.
Vamos a insertar nuestro nuevo control en la actividad principal de la aplicacin:
1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
xmlns:tools="http://schemas.android.com/tools"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:paddingLeft="@dimen/activity_horizontal_margin"
6
android:paddingRight="@dimen/activity_horizontal_margin"
7
android:paddingTop="@dimen/activity_vertical_margin"
8
android:paddingBottom="@dimen/activity_vertical_margin"
9
android:orientation="vertical"
10
tools:context=".MainActivity">
11
12
<net.sgoliver.android.controlpers2.ControlLogin
13
android:id="@+id/CtlLogin"
14
android:layout_width="match_parent"
15
android:layout_height="wrap_content"
16
android:background="#DDDDDD" />
17
18
</LinearLayout>
Dado que estamos heredando de un LinearLayout podemos utilizar en principio cualquier
atributo permitido para dicho tipo de controles, en este caso hemos establecido por ejemplo

107

los atributoslayout_width, layout_height y background. Si ejecutamos ahora la aplicacin


veremos cmo ya hemos conseguido gran parte de nuestro objetivo.

Vamos a aadir ahora algo ms de funcionalidad. En primer lugar, podemos aadir algn
mtodo pblico exclusivo de nuestro control. Como ejemplo podemos aadir un mtodo
que permita modificar el texto de la etiqueta de resultado del login. Esto no tiene ninguna
dificultad:
1
public void setMensaje(String msg)
2
{
3
lblMensaje.setText(msg);
4
}
En segundo lugar, todo control que se precie debe tener algunos eventos que nos permitan
responder a las acciones del usuario de la aplicacin. As por ejemplo, los botones tienen un
evento OnClick, las listas un evento OnItemSelected, etc. Pues bien, nosotros vamos a
dotar a nuestro control de un evento personalizado, llamado OnLogin, que se lance cuando
el usuario introduce sus credenciales de identificacin y pulsa el botn Login.
Para ello, lo primero que vamos a hacer es concretar los detalles de dicho evento, creando
una interfaz java para definir su listener. Esta interfaz tan slo tendr un mtodo
llamado onLogin() que devolver los dos datos introducidos por el usuario (usuario y
contrasea). Vemos cmo queda:
1
package net.sgoliver.android.controlpers2;
2
3
public interface OnLoginListener
4
{
5
void onLogin(String usuario, String password);
6
}
A continuacin, deberemos aadir un nuevo miembro de tipo OnLoginListener a la
clase ControlLogin, y un mtodo pblico que permita suscribirse al nuevo evento.
1
public class ControlLogin extends LinearLayout

108

2
3
4
5
6
7
8
9
10
11

{
private OnLoginListener listener;
//...
public void setOnLoginListener(OnLoginListener l)
{
listener = l;
}
}

Con esto, la aplicacin principal ya puede suscribirse al evento OnLogin y ejecutar su


propio cdigo cuando ste se genere. Pero cundo se genera exactamente? Dijimos antes
que queremos lanzar el eventoOnLogin cuando el usuario pulse el botn Login de
nuestro control. Pues bien, para hacerlo, volvamos al mtodo asignarEventos() que antes
dejamos aparcado. En este mtodo vamos a implementar el eventoOnClick del botn de
Login para lanzar el nuevo evento OnLogin del control. Confundido?. Intento explicarlo
de otra forma. Vamos a aprovechar el evento OnClick() del botn Login (que es un evento
interno a nuestro control, no se ver desde fuera) para lanzar hacia el exterior el
evento OnLogin() (que ser el que debe capturar y tratar la aplicacin que haga uso del
control).

Para ello, implementaremos el evento OnClick como ya hemos hecho en otras ocasiones y
como acciones generaremos el evento OnLogin de nuestro listener pasndole los datos que
ha introducido el usuario en los cuadros de texto Usuario y Contrasea:
1
private void asignarEventos()
2
{
3
btnLogin.setOnClickListener(new OnClickListener()
4
{
5
@Override
6
public void onClick(View v) {
7
listener.onLogin(txtUsuario.getText().toString(),
8
txtPassword.getText().toString());
9
}
10
});

109

11

Con todo esto, la aplicacin principal ya puede implementar el evento OnLogin de nuestro
control, haciendo por ejemplo la validacin de las credenciales del usuario y modificando
convenientemente el texto de la etiqueta de resultado:
1
@Override
2
public void onCreate(Bundle savedInstanceState)
3
{
4
super.onCreate(savedInstanceState);
5
setContentView(R.layout.main);
6
7
ctlLogin = (ControlLogin)findViewById(R.id.CtlLogin);
8
9
ctlLogin.setOnLoginListener(new OnLoginListener()
10
{
11
@Override
12
public void onLogin(String usuario, String password)
13
{
14
//Validamos el usuario y la contrasea
15
if (usuario.equals("demo") && password.equals("demo"))
16
ctlLogin.setMensaje("Login correcto!");
17
else
18
ctlLogin.setMensaje("Vuelva a intentarlo.");
19
}
20
});
21
}
Veamos lo que ocurre al ejecutar ahora la aplicacin principal e introducir las credenciales
correctas:

Nuestro control est ya completo, en aspecto y funcionalidad. Hemos personalizado su


interfaz y hemos aadido mtodos y eventos propios. Podemos hacer algo ms? Pues s.
Cuando vimos cmo aadir el control de login al layout de la aplicacin principal dijimos
que podamos utilizar cualquier atributo XML permitido para el contenedor LinearLayout,

110

ya que nuestro control derivaba de ste. Pero vamos a ir ms all y vamos a definir tambin
atributos XML exclusivos para nuestro control. Como ejemplo, vamos a definir un atributo
llamado login_text que permita establecer el texto del botn de Login desde el propio
layout XML, es decir, en tiempo de diseo.
Primero vamos de declarar el nuevo atributo y lo vamos a asociar al control ControlLogin.
Esto debe hacerse en el fichero \res\values\attrs.xml. Para ello, aadiremos una nueva
seccin <declare-styleable> asociada a ControlLogin dentro del elemento <resources>,
donde indicaremos el nombre (name) y el tipo (format) del nuevo atributo.
1
<resources>
2
<declare-styleable name="ControlLogin">
3
<attr name="login_text" format="string"/>
4
</declare-styleable>
5
</resources>
En nuestro caso, el tipo del atributo ser string, dado que contendr una cadena de texto con
el mensaje a mostrar en el botn.
Con esto ya tendremos permitido el uso del nuevo atributo dentro del layout de la
aplicacin principal. Ahora nos falta procesar el atributo desde nuestro control
personalizado. Este tratamiento lo podemos hacer en el construtor de la clase ControlLogin.
Para ello, obtendremos la lista de atributos asociados aControlLogin mediante el
mtodo obtainStyledAttributes() del contexto de la aplicacin, obtendremos el valor del
nuevo atributo definido (mediante su ID, que estar formado por la concatenacin del
nombre del control y el nombre del atributo, en nuestro caso ControlLogin_login_text) y
modificaremos el texto por defecto del control con el nuevo texto.
1
public ControlLogin(Context context, AttributeSet attrs) {
2
super(context, attrs);
3
inicializar();
4
5
// Procesamos los atributos XML personalizados
6
TypedArray a =
7
getContext().obtainStyledAttributes(attrs,
8
R.styleable.ControlLogin);
9
10
String textoBoton = a.getString(
11
R.styleable.ControlLogin_login_text);
12
13
btnLogin.setText(textoBoton);
14
15
a.recycle();
16
}

111

Ya slo nos queda utilizarlo. Para ello debemos primero declarar un nuevo espacio de
nombres (namespace) local para el paquete java utilizado, que en nuestro caso he llamado
sgo:
1
xmlns:sgo="http://schemas.android.com/apk/res-auto"
Tras esto, slo queda asignar el valor del nuevo atributo precedido del nuevo namespace,
por ejemplo con el texto Entrar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:sgo="http://schemas.android.com/apk/res-auto"
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"
android:orientation="vertical"
tools:context=".MainActivity">
<net.sgoliver.android.controlpers2.ControlLogin
android:id="@+id/CtlLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#DDDDDD"
sgo:login_text="Entrar" />
</LinearLayout>

Con esto, conseguiramos el mismo efecto que los ejemplos antes mostrados, pero de una
forma mucho ms fcilmente personalizable, con el texto del botn controlado directamente
por un atributo del layout XML
Como resumen, en este artculo hemos visto cmo construir un control android
personalizado a partir de otros controles estandar, componiendo su interfaz, aadiendo
mtodos y eventos personalizados, e incluso aadiendo nuevas opciones en tiempo de
diseo aadiendo atributos xml exclusivos.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.
Espero que os sea til y que sigis los artculos que quedan por venir.
Interfaz de usuario en Android: Controles personalizados (III)

112

by Sgoliver on 10/02/2011 in Android, Programacin


En artculos anteriores del curso ya comentamos dos de las posibles vas que tenemos para
crear controles personalizados en Android: la primera de ellas extendiendo la funcionalidad
de un control ya existente, y como segunda opcin creando un nuevo control compuesto
por otros ms sencillos.
En este nuevo artculo vamos a describir la tercera de las posibilidades que tenamos
disponibles, que consiste en crear un control completamente desde cero, sin utilizar como
base otros controles existentes. Como ejemplo, vamos a construir un control que
reproduzca el comportamiento de un tablero del juego Tres en Raya.

En las anteriores ocasiones vimos cmo el nuevo control creado siempre heredaba de algn
otro control o contenedor ya existente. En este caso sin embargo, vamos a heredar nuestro
contro directamente de la clase View (clase padre de la gran mayora de elementos visuales
de Android). Esto implica, entre otras cosas, que por defecto nuestro control no va a tener
ningn tipo de interfaz grfica, por lo que todo el trabajo de dibujar la interfaz lo vamos a
tener que hacer nosotros. Adems, como paso previo a la representacin grfica de la
interfaz, tambin vamos a tener que determinar las dimensiones que nuestro control tendr
dentro de su elemento contenedor. Como veremos ahora, ambas cosas se llevarn a cabo
redefiniendo dos eventos de la clase View: onDraw() para el dibujo de la interfaz,
y onMeasure() para el clculo de las dimensiones.
Por llevar un orden cronolgico, empecemos comentando el evento onMeasure(). Este
evento se ejecuta automticamente cada vez que se necesita recalcular el tamao de un
control. Pero como ya hemos visto en varias ocasiones, los elementos grficos incluidos en

113

una aplicacin Android se distribuyen por la pantalla de una forma u otra dependiendo del
tipo de contenedor o layout utilizado. Por tanto, el tamao de un control determinado en la
pantalla no depender slo de l, sino de ciertas restricciones impuestas por su elemento
contenedor o elemento padre. Para resolver esto, en el evento onMeasure() recibiremos
como parmetros las restricciones del elemento padre en cuanto a ancho y alto del control,
con lo que podremos tenerlas en cuenta a la hora de determinar el ancho y alto de nuestro
control personalizado. Estas restricciones se reciben en forma de objetos MeasureSpec, que

contiene dos campos: modo ytamao. El significado del segundo de ellos es obvio, el
primero por su parte sirve para matizar el significado del segundo. Me explico. Este
campo modo puede contener tres valores posibles:
AT_MOST: indica que el control podr tener como mximo el tamao especificado.
EXACTLY: indica que al control se le dar exactamente el tamao especificado.
UNSPECIFIED: indica que el control padre no impone ninguna restriccin sobre el
tamao.
Dependiendo de esta pareja de datos, podremos calcular el tamao deseado para nuestro
control. Para nuestro control de ejemplo, apuraremos siempre el tamao mximo disponible
(o un tamao por defecto de 100*100 en caso de no recibir ninguna restriccin), por lo que
en todos los casos elegiremos como tamao de nuestro control el tamao recibido como
parmetro:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int ancho = calcularAncho(widthMeasureSpec);
int alto = calcularAlto(heightMeasureSpec);
if(ancho < alto)
alto = ancho;
else
ancho = alto;
setMeasuredDimension(ancho, alto);
}
private int calcularAlto(int limitesSpec)
{
int res = 100; //Alto por defecto
int modo = MeasureSpec.getMode(limitesSpec);
int limite = MeasureSpec.getSize(limitesSpec);
if (modo == MeasureSpec.AT_MOST) {

114

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

res = limite;
}
else if (modo == MeasureSpec.EXACTLY) {
res = limite;
}
return res;
}
private int calcularAncho(int limitesSpec)
{
int res = 100; //Ancho por defecto
int modo = MeasureSpec.getMode(limitesSpec);
int limite = MeasureSpec.getSize(limitesSpec);
if (modo == MeasureSpec.AT_MOST) {
res = limite;
}
else if (modo == MeasureSpec.EXACTLY) {
res = limite;
}
return res;
}

Como nota importante, al final del evento onMeasure() siempre debemos llamar al
mtodosetMeasuredDimension() pasando como parmetros el ancho y alto calculados para
nuestro control.
Con esto ya hemos determinado las dimensiones del control, por lo que tan slo nos queda
dibujar su interfaz grfica, pero antes vamos a ver qu datos nos har falta guardar para
poder almacenar el estado del control y, entre otras cosas, poder dibujar su interfaz
convenientemente.
Por un lado guardaremos en un array de 33 (tablero) el estado de cada casilla del tablero.
Cada casilla la rellenaremos con un valor constante dependiendo de si contiene una ficha X,
una ficha O, o si est vaca, valores para lo que definiremos tres constantes
(FICHA_X, FICHA_O, VACIA). Guardaremos tambin los colores que utilizarn las
fichas X y O (xColor y oColor), de forma que ms tarde podamos personalizarlos. Y por
ltimo, almacenaremos tambin la ficha activa, es decir, el tipo de ficha que se colocar al
pulsar sobre el tablero (fichaActiva).
1 public static final int VACIA = 0;
2 public static final int FICHA_O = 1;

115

3 public static final int FICHA_X = 2;


4
5 private int[][] tablero;
6 private int fichaActiva;
7 private int xColor;
8 private int oColor;
Todos estos datos los inicializaremos en un nuevo mtodo inicializacion() que llamaremos
desde nuestros constructores del control.
1
public TresEnRaya(Context context) {
2
super(context);
3
4
inicializacion();
5
}
6
7
public TresEnRaya(Context context, AttributeSet attrs, int defaultStyle) {
8
super(context, attrs, defaultStyle);
9
10
inicializacion();
11 }
12
13 public TresEnRaya(Context context, AttributeSet attrs) {
14
super(context, attrs);
15
16
inicializacion();
17 }
18
19 private void inicializacion() {
20
tablero = new int[3][3];
21
limpiar();
22
23
fichaActiva = FICHA_X;
24
xColor = Color.RED;
25
oColor = Color.BLUE;
26 }
27
28 public void limpiar() {
29
for(int i=0; i<3; i++)
30
for(int j=0; j<3; j++)
31
tablero[i][j] = VACIA;
32 }
Definiremos adems una serie de mtodos pblicos del control para poder obtener y
actualizar todos estos datos:
1

public void setFichaActiva(int ficha) {

116

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

fichaActiva = ficha;
}
public int getFichaActiva() {
return fichaActiva;
}
public void alternarFichaActiva()
{
if(fichaActiva == FICHA_O)
fichaActiva = FICHA_X;
else
fichaActiva = FICHA_O;
}
public void setXColor(int color) { xColor = color; }
public int getXColor() { return xColor; }
public void setOColor(int color) { oColor = color; }
public int getOColor() { return oColor; }
public void setCasilla(int fil, int col, int valor) { tablero[fil][col] = valor; }
public int getCasilla(int fil, int col) {
return tablero[fil][col];
}

Pasemos ya al dibujo de la interfaz a partir de los datos anteriores. Como hemos indicado
antes, esta tarea se realiza dentro del evento onDraw(). Este evento recibe como parmetro
un objeto de tipo Canvas, sobre el que podremos ejecutar todas las operaciones de dibujo de
la interfaz. No voy a entrar en detalles de la clase Canvas, por ahora nos vamos a conformar
sabiendo que es la clase que contiene la mayor parte de los mtodos de dibujo en interfaces
Android,
por
ejemplo drawRect() para
dibujar
rectngulos,drawCircle() para
crculos, drawBitmap() para imagenes, drawText() para texto, e infinidad de posibilidades
ms. Para consultar todos los mtodos disponibles puedes dirigirte a la documentacin
oficial de la clase Canvas de Android. Adems de la clase Canvas, tambin me gustara
destacar la clase Paint, que permite definir el estilo de dibujo a utilizar en los metodos de
dibujo de Canvas, por ejemplo el ancho de trazado de las lneas, los colores de relleno, etc.
Para nuestro ejemplo no necesitaramos conocer nada ms, ya que la interfaz del control es
relativamente sencilla. Vemos primero el cdigo y despus comentamos los pasos
realizados:

117

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
46
47

@Override
protected void onDraw(Canvas canvas) {
//Obtenemos las dimensiones del control
int alto = getMeasuredHeight();
int ancho = getMeasuredWidth();
//Lineas
Paint pBorde = new Paint();
pBorde.setStyle(Style.STROKE);
pBorde.setColor(Color.BLACK);
pBorde.setStrokeWidth(2);
canvas.drawLine(ancho/3, 0, ancho/3, alto, pBorde);
canvas.drawLine(2*ancho/3, 0, 2*ancho/3, alto, pBorde);
canvas.drawLine(0, alto/3, ancho, alto/3, pBorde);
canvas.drawLine(0, 2*alto/3, ancho, 2*alto/3, pBorde);
//Marco
canvas.drawRect(0, 0, ancho, alto, pBorde);
//Marcas
Paint pMarcaO = new Paint();
pMarcaO.setStyle(Style.STROKE);
pMarcaO.setStrokeWidth(8);
pMarcaO.setColor(oColor);
Paint pMarcaX = new Paint();
pMarcaX.setStyle(Style.STROKE);
pMarcaX.setStrokeWidth(8);
pMarcaX.setColor(xColor);
//Casillas Seleccionadas
for(int fil=0; fil<3; fil++) {
for(int col=0; col<3; col++) {
if(tablero[fil][col] == FICHA_X) {
//Cruz
canvas.drawLine(
col * (ancho / 3) + (ancho / 3) * 0.1f,
fil * (alto / 3) + (alto / 3) * 0.1f,
col * (ancho / 3) + (ancho / 3) * 0.9f,
fil * (alto / 3) + (alto / 3) * 0.9f,
pMarcaX);
canvas.drawLine(
col * (ancho / 3) + (ancho / 3) * 0.1f,

118

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

fil * (alto / 3) + (alto / 3) * 0.9f,


col * (ancho / 3) + (ancho / 3) * 0.9f,
fil * (alto / 3) + (alto / 3) * 0.1f,
pMarcaX);
}
else if(tablero[fil][col] == FICHA_O) {
//Circulo
canvas.drawCircle(
col * (ancho / 3) + (ancho / 6),
fil * (alto / 3) + (alto / 6),
(ancho / 6) * 0.8f, pMarcaO);
}
}
}
}

En primer lugar obtenemos las dimensiones calculadas en la ltima llamada


a onMeasure() mediante
los
mtodos getMeasuredHeight() y getMeasuredWidth().
Posteriormente definimos un objeto Paint que usaremos para dibujar la lineas interiores del
tablero. Para indicar que se trata de un color de linea utilizaremos la llamada
a setStyle(Style.STROKE) (si
quisiramos
definir
un
color
de
relleno
utilizaramos Style.FILL). Por su parte, el color y el ancho de la lnea lo definimos
mediante setColor()y setStrokeWidth(). Tras esto, ya slo debemos dibujar cada una de las
lineas en su posicin correspondiente con drawLine(), que recibe como parmetros las
coordenadas X e Y de inicio, las coordenadas X e Y de fin, y el estilo de linea a utilizar, por
ese orden. Por ltimo, dibujamos el marco exterior del tablero mediante drawRect().
Lo siguiente ser dibujar las fichas que haya colocadas en el tablero. Para ello definimos
primero
dos
nuevos
objetos Paint con
los
colores
definidos
en
los
atributos xColor y oColor. Posteriormente recorremos el array que representa al tablero y
dibujamos las fichas en su posicin dependiendo de su tipo. Las fichas de tipo X las
dibujaremos mediante drawLine() y las de tipo O mediante drawCircle(). Los clculos para
saber la posicin de cada linea o crculo son laboriosos pero muy sencillos si se estudian
con detenimiento.
El siguiente paso ser definir su funcionalidad implementando los eventos a los que
queramos que responda nuestro control, tanto eventos internos como externos.
En nuestro caso slo vamos a tener un evento de cada tipo. En primer lugar definiremos un
evento interno (evento que slo queremos capturar de forma interna al control, sin
exponerlo al usuario) para responder a las pulsaciones del usuario sobre las distintas
casillas del tablero, y que utilizaremos para actualizar el dibujo de la interfaz con la nueva

119

ficha colocada. Para ello implementaremos el evento onTouch(), lanzado cada vez que el
usuario toca la pantalla sobre nuestro control. La lgica ser sencilla, simplemente
consultaremos las coordenadas donde ha pulsado el usuario (mediante los
mtodos getX() y getY()), y dependiendo del lugar pulsado determinaremos a qu casilla
del tablero se corresponde y actualizaremos el array tablero con el valor de la ficha
actual fichaActiva. Finalmente, llamamos al mtodoinvalidate() para refrescar la interfaz
del control:
1
@Override
2
public boolean onTouchEvent(MotionEvent event)
3
{
4
int fil = (int) (event.getY() / (getMeasuredHeight()/3));
5
int col = (int) (event.getX() / (getMeasuredWidth()/3));
6
7
//Actualizamos el tablero
8
tablero[fil][col] = fichaActiva;
9
10
//Refrescamos el control
11
this.invalidate();
12
13
return super.onTouchEvent(event);
14 }
En segundo lugar crearemos un evento externo personalizado, que lanzaremos cuando el
usuario
pulse
sobre
una
casilla
del
tablero.
Llamaremos
a
este
evento onCasillaSeleccionada(). Para crearlo actuaremos de la misma forma que ya vimos
en el artculo anterior. Primero definiremos una interfaz para el listener de nuestro evento:
1 package net.sgoliver.android.controlpers3;
2
3 public interface OnCasillaSeleccionadaListener
4 {
5
void onCasillaSeleccionada(int fila, int columna);
6 }
Posteriormente, definiremos un objeto de este tipo como atributo de nuestro control y
escribiremos un nuevo mtodo que permita a las aplicaciones suscribirse al evento:
1
2
3
4
5
6
7
8
9

public class TresEnRaya extends View


{
private OnCasillaSeleccionadaListener listener;
//...
public void setOnCasillaSeleccionadaListener(OnCasillaSeleccionadaListener l)
{
listener = l;

120

10
}
11 }
Y ya slo nos quedara lanzar el evento en el momento preciso. Esto tambin lo haremos
dentro del eventoonTouch(), cuando detectemos que el usuario ha pulsado sobre una casilla
del tablero:
1
@Override
2
public boolean onTouchEvent(MotionEvent event)
3
{
4
int fil = (int) (event.getY() / (getMeasuredHeight()/3));
5
int col = (int) (event.getX() / (getMeasuredWidth()/3));
6
7
tablero[fil][col] = fichaActiva;
8
9
//Lanzamos el evento de pulsacin
10
if (listener != null) {
11
listener.onCasillaSeleccionada(fil, col);
12
}
13
14
//Refrescamos el control
15
this.invalidate();
16
17
return super.onTouchEvent(event);
18 }
Con esto, nuestra aplicacin principal ya podra suscribirse a este nuevo evento para estar
informada cada vez que se seleccione una casilla. En la aplicacin de ejemplo he incluido,
adems del tablero (terTablero), un botn para alternar la ficha activa (btnFicha), y una
etiqueta de texto para mostrar la casilla seleccionada (txtCasilla) haciendo uso de la
informacin recibida en el evento externoonCasillaSeleccionada():
1
public class MainActivity extends ActionBarActivity {
2
3
private Button btnFicha;
4
private TresEnRaya terTablero;
5
private TextView txtCasilla;
6
7
@Override
8
protected void onCreate(Bundle savedInstanceState) {
9
super.onCreate(savedInstanceState);
10
setContentView(R.layout.activity_main);
11
12
terTablero = (TresEnRaya)findViewById(R.id.tablero);
13
btnFicha = (Button)findViewById(R.id.btnFicha);
14
txtCasilla = (TextView)findViewById(R.id.txtCasilla);
15
16
btnFicha.setOnClickListener(new View.OnClickListener() {

121

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

@Override
public void onClick(View view) {
terTablero.alternarFichaActiva();
}
});
terTablero.setOnCasillaSeleccionadaListener(new OnCasillaSeleccionadaListener() {
@Override
public void onCasillaSeleccionada(int fila, int columna) {
txtCasilla.setText("ltima casilla seleccionada: " + fila + "." + columna);
}
});
}
//....
}

Por ltimo, al igual que en el apartado anterior, voy a definir dos atributos XML
personalizados para poder indicar los colores de las fichas X y O desde el propio layout
XML. Para ellos, creamos primero un nuevo fichero /res/values/attrs.xml :
1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3
<declare-styleable name="TresEnRaya">
4
<attr name="ocolor" format="color"/>
5
<attr name="xcolor" format="color"/>
6
</declare-styleable>
7 </resources>
Posteriormente, accedemos a estos atributos desde nuestros constructores para establecer
con ellos los valores de las variables xColor y oColor, esto ya lo vimos en el apartado
anterior sobre controles compuestos:
1
public TresEnRaya(Context context) {
2
super(context);
3
4
inicializacion();
5
}
6
7
public TresEnRaya(Context context, AttributeSet attrs, int defaultStyle) {
8
super(context, attrs, defaultStyle );
9
10
inicializacion();
11
12
// Procesamos los atributos XML personalizados
13
TypedArray a =
14
getContext().obtainStyledAttributes(attrs,
15
R.styleable.TresEnRaya);
16

122

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

oColor = a.getColor(
R.styleable.TresEnRaya_ocolor, Color.BLUE);
xColor = a.getColor(
R.styleable.TresEnRaya_xcolor, Color.RED);
a.recycle();
}
public TresEnRaya(Context context, AttributeSet attrs) {
super(context, attrs);
inicializacion();
// Procesamos los atributos XML personalizados
TypedArray a =
getContext().obtainStyledAttributes(attrs,
R.styleable.TresEnRaya);
oColor = a.getColor(
R.styleable.TresEnRaya_ocolor, Color.BLUE);
xColor = a.getColor(
R.styleable.TresEnRaya_xcolor, Color.RED);
a.recycle();
}

Tras definir nuestros atributos, podemos ver cmo quedara el layout de nuestra actividad
principal haciendo uso de ellos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:sgo="http://schemas.android.com/apk/res-auto"
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"
android:orientation="vertical"
tools:context=".MainActivity">
<net.sgoliver.android.controlpers3.TresEnRaya
android:id="@+id/tablero"
android:layout_width="match_parent"

123

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

android:layout_height="match_parent"
android:layout_margin="10dp"
sgo:ocolor="#0000FF"
sgo:xcolor="#FF0000" />
<Button android:id="@+id/btnFicha"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Cambiar Ficha" />
<TextView android:id="@+id/txtCasilla"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp" />
</LinearLayout>

Lo ms relevante del cdigo anterior, como ya comentamos, es la declaracin de nuestro


espacio de nombres local xmlns:sgo, y la asignacin de los atributos precedidos por el
prefijo elegido, sgo:xcolor ysgo:ocolor.
Con esto, tendramos finalizado nuestro control completamente personalizado, que hemos
construido sin utilizar como base ningn otro control predefinido, definiendo desde cero
tanto su aspecto visual como su funcionalidad interna o sus eventos pblicos. Veamos
cmo queda visualmente:

Interfaz de usuario en Android: Pestaas (Tabs)


by Sgoliver on 07/10/2011 in Android, Programacin

124

En artculos anteriores del Curso de Programacin Android hemos visto como dar forma a
la interfaz de usuario de nuestra aplicacin mediante el uso de diversos tipos de layouts,
como por ejemplo los lineales, absolutos, relativos, u otros ms elaborados como los de tipo
lista o tabla. stos van a ser siempre los elementos organizativos bsicos de nuestra
interfaz, pero sin embargo, dado el poco espacio con el que contamos en las pantallas de
muchos dispositivos, o simplemente por cuestiones de organizacin, a veces es
necesario/interesante dividir nuestros controles en varias pantallas. Y una de las formas
clsicas de conseguir esto consiste en la distribucin de los elementos por pestaas o tabs.
Android por supuesto permite utilizar este tipo de interfaces, aunque lo hace de una forma
un tanto peculiar, ya que la implementacin no va a depender de un slo elemento sino de
varios, que adems deben estar distribuidos y estructurados de una forma determinada nada
arbitraria. Adicionalmente no nos bastar simplemente con definir la interfaz en XML
como hemos hecho en otras ocasiones, sino que tambin necesitaremos completar el
conjunto con algunas lneas de cdigo. Desarrollemos esto poco a poco.
En Android, el elemento principal de un conjunto de pestaas ser el control TabHost. ste
va a ser el contenedor principal de nuestro conjunto de pestaas y deber tener
obligatoriamente como id el valor @android:id/tabhost. Dentro de ste vamos a incluir
un LinearLayout que nos servir para distribuir verticalmente las secciones principales del
layout: la seccin de pestaas en la parte superior y la seccin de contenido en la parte
inferior. La seccin de pestaas se representar mediante un elementoTabWidget, que
deber tener como id el valor @android:id/tabs, y como contenedor para el contenido de
las pestaas aadiremos un FrameLayout con el id obligatorio @android:id/tabcontent.
Por ltimo, dentro del FrameLayout incluiremos el contenido de cada pestaa,
normalmente cada uno dentro de su propio layout principal (en mi caso he
utilizado LinearLayout) y con un id nico que nos permita posteriormente hacer referencia
a ellos fcilmente (en mi caso he utilizado por ejemplo los ids tab1, tab2, ). A
continuacin represento de forma grfica toda la estructura descrita.

125

Si traducimos esta estructura a nuestro fichero de layout XML tendramos lo siguiente:


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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<TabHost android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TabWidget android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@android:id/tabs" />
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@android:id/tabcontent" >
<LinearLayout android:id="@+id/tab1"
android:orientation="vertical"
android:layout_width="match_parent"

126

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

android:layout_height="match_parent" >
<TextView android:id="@+id/textView1"
android:text="Contenido Tab 1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout android:id="@+id/tab2"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView android:id="@+id/textView2"
android:text="Contenido Tab 2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>
</LinearLayout>

Como podis ver, como contenido de las pestaas tan slo he aadido por simplicidad una
etiqueta de texto con el texto Contenido Tab NTab. Esto nos permitir ver que el
conjunto de pestaas funciona correctamente cuando ejecutemos la aplicacin.
Con esto ya tendramos montada toda la estructura de controles necesaria para nuestra
interfaz de pestaas. Sin embargo, como ya dijimos al principio del artculo, con esto no es
suficiente. Necesitamos asociar de alguna forma cada pestaa con su contenido, de forma
que el el control se comporte correctamente cuando cambiamos de pestaa. Y esto
tendremos que hacerlo mediante cdigo en nuestra actividad principal.
Empezaremos obteniendo una referencia al control principal TabHost y preparndolo para
su configuracin llamando a su mtodo setup(). Tras esto, crearemos un objeto de
tipo TabSpec para cada una de las pestaas que queramos aadir mediante el
mtodo newTabSpec(), al que pasaremos como parmetro una etiqueta identificativa de la
pestaa (en mi caso de ejemplo mitab1, mitab2, ). Adems, tambin le asignaremos
el layout de contenido correspondiente a la pestaa llamando al mtodo setContent(), e
indicaremos el texto y el icono que queremos mostrar en la pestaa mediante el
mtodosetIndicator(texto, icono). Veamos el cdigo completo.
1
Resources res = getResources();
2
3
TabHost tabs=(TabHost)findViewById(android.R.id.tabhost);

127

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

tabs.setup();
TabHost.TabSpec spec=tabs.newTabSpec("mitab1");
spec.setContent(R.id.tab1);
spec.setIndicator("",
res.getDrawable(android.R.drawable.ic_btn_speak_now));
tabs.addTab(spec);
spec=tabs.newTabSpec("mitab2");
spec.setContent(R.id.tab2);
spec.setIndicator("TAB2",
res.getDrawable(android.R.drawable.ic_dialog_map));
tabs.addTab(spec);
tabs.setCurrentTab(0);

Si vemos el cdigo, vemos por ejemplo como para la primera pestaa creamos un
objeto TabSpec con la etiqueta mitab1, le asignamos como contenido uno de
los LinearLayout que incluimos en la seccin de contenido (en este caso R.id.tab1) y
finalmente
le
asignamos
el
texto
TAB1
y
el
iconoandroid.R.drawable.ic_btn_speak_now (ste es un icono incluido con la propia
plataforma Android. Si no existiera en vuestra versin podis sustituirlo por cualquier otro
icono). Finalmente aadimos la nueva pestaa al control mediante el mtodo addTab().
Si ejecutamos ahora la aplicacin tendremos algo como lo que muestra la siguiente imagen,
donde podremos cambiar de pestaa y comprobar cmo se muestra correctamente el
contenido de la misma.
En Android 2.x

Y en Android 4.x

128

Una pequea sorpresa. Como podis comprobar, aunque estamos indicando en todos los
casos un texto y un icono para cada pestaa, el comportamiento difiere entre las distintas
versiones de Android. En Android 4, el comportamiento por defecto del control TabHost es
mostrar slo el texto, o solo el icono, pero no ambos. Si eliminamos el texto de la primera
pestaa y volvemos a ejecutar veremos como el icono s aparece.
1 TabHost.TabSpec spec=tabs.newTabSpec("mitab1");
2 spec.setContent(R.id.tab1);
3 spec.setIndicator("",
4
res.getDrawable(android.R.drawable.ic_btn_speak_now));
5 tabs.addTab(spec);
Con esta pequea modificacin la aplicacin se vera as en Android 4.x

En cuanto a los eventos disponibles del control TabHost, aunque no suele ser necesario
capturarlos, podemos ver a modo de ejemplo el ms interesante de ellos, OnTabChanged,
que se lanza cada vez que se cambia de pestaa y que nos informa de la nueva pestaa
visualizada. Este evento podemos implementarlo y asignarlo mediante el
mtodo setOnTabChangedListener() de la siguiente forma:
1 tabs.setOnTabChangedListener(new OnTabChangeListener() {
2
@Override
3
public void onTabChanged(String tabId) {
4
Log.i("AndroidTabsDemo", "Pulsada pestaa: " + tabId);
5
}
6 });
En el mtodo onTabChanged() recibimos como parmetro la etiqueta identificativa de la
pestaa

(no

su

ID),

que

debimos

asignar

cuando

creamos

su

129

objeto TabSpec correspondiente. Para este ejemplo, lo nico que haremos al detectar un
cambio de pestaa ser escribir en el log de la aplicacin un mensaje informativo con la
etiqueta de la nueva pestaa visualizada. As por ejemplo, al cambiar a la segunda pestaa
recibiremos el mensaje de log: Pulsada pestaa: mitab2.
Interfaz de usuario en Android: Fragments
by Sgoliver on 25/01/2013 in Android, Programacin
Cuando empezaron a aparecer dispositivos de gran tamao tipo tablet, el equipo de Android
tuvo que solucionar el problema de la adaptacin de la interfaz grfica de las aplicaciones a
ese nuevo tipo de pantallas. Una interfaz de usuario diseada para un telfono mvil no se
adaptaba fcilmente a una pantalla 4 o 5 pulgadas mayor. La solucin a esto vino en forma
de un nuevo tipo de componente llamado Fragment.
Un fragment no puede considerarse ni un control ni un contenedor, aunque se parecera
ms a lo segundo. Un fragment podra definirse como una porcin de la interfaz de usuario
que puede aadirse o eliminarse de una interfaz de forma independiente al resto de
elementos de la actividad, y que por supuesto puede reutilizarse en otras actividades. Esto,
aunque en principio puede parecer algo trivial, nos va a permitir poder dividir nuestra
interfaz en varias porciones de forma que podamos disear diversas configuraciones de
pantalla, dependiendo de su tamao y orientacin, sin tener que duplicar cdigo en ningn
momento, sino tan slo utilizando o no los distintos fragmentos para cada una de las
posibles configuraciones. Intentemos aclarar esto un poco con un ejemplo.
No quera utilizar el ejemplo tpico que aparece por todos lados, pero en este caso creo que
es el ms ilustrativo. Supongamos una aplicacin de correo electrnico, en la que por un
lado debemos mostrar la lista de correos disponibles, con sus campos clsicos De y Asunto,
y por otro lado debemos mostrar el contenido completo del correo seleccionado. En un
telfono mvil lo habitual ser tener una primera actividad que muestre el listado de
correos, y cuando el usuario seleccione uno de ellos navegar a una nueva actividad que
muestre el contenido de dicho correo. Sin embargo, en un tablet puede existir espacio
suficiente para tener ambas partes de la interfaz en la misma pantalla, por ejemplo en un
tablet en posicin horizontal podramos tener una columna a la izquierda con el listado de
correos y dedicar la zona derecha a mostrar el detalle del correo seleccionado, todo ello sin
tener que cambiar de actividad.
Antes de existir los fragments podramos haber hecho esto implementando diferentes
actividades con diferentes layouts para cada configuracin de pantalla, pero esto nos habra

130

obligado a duplicar gran parte del cdigo en cada actividad. Tras la aparicin de los
fragments, colocaramos el listado de correos en un fragment y la vista de detalle en otro,
cada uno de ellos acompaado de su lgica de negocio asociada, y tan slo nos quedara
definir varios layouts para cada configuracin de pantalla incluyendo [o no] cada uno de
estos fragments.
A modo de aplicacin de ejemplo para este artculo, nosotros vamos a simular la aplicacin
de correo que hemos comentado antes, adaptndola a tres configuraciones distintas:
pantalla normal, pantalla grande horizontal y pantalla grande vertical. Para el primer caso
colocaremos el listado de correos en una actividad y el detalle en otra, mientras que para el
segundo y el tercero ambos elementos estarn en la misma actividad, a derecha/izquierda
para el caso horizontal, y arriba/abajo en el caso vertical.
Definiremos por tanto dos fragments: uno para el listado y otro para la vista de detalles.
Ambos sern muy sencillos. Al igual que una actividad, cada fragment se compondr de un
fichero de layout XML para la interfaz (colocado en alguna carpeta /res/layout) y una clase
java para la lgica asociada.
El primero de los fragment a definir contendr tan slo un control ListView, para el que
definiremos un adaptador personalizado para mostrar dos campos por fila (De y
Asunto). Ya describimos cmo hacer esto en el artculo dedicado al control ListView. El
layout XML (lo llamaremos fragment_listado.xml) quedara por tanto de la siguiente
forma:
1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:orientation="vertical" >
6
<ListView
7
android:id="@+id/LstListado"
8
android:layout_width="match_parent"
9
android:layout_height="wrap_content" >
10
</ListView>
11
</LinearLayout>
Como hemos dicho, todo fragment debe tener asociada, adems del layout, su propia clase
java, que en este caso debe extender de la clase Fragment. Y aqu viene el primer
contratiempo. Los fragment aparecieron con la versin 3 de Android, por lo que en
principio no estaran disponibles para versiones anteriores. Sin embargo, Google pens en
todos y proporcion esta caracterstica tambin como parte de la librera de

131

compatibillidad android-support, que en versiones recientes del plugin de Android para


Eclipse se aade por defecto a todos los proyectos creados.

Si no fuera as, tambin puede incluirse manualmente en el proyecto mediante la opcin


Add Support Library del men contextual del proyecto.

Hecho esto, ya no habra ningn problema para utilizar la clase Fragment, y otras que
comentaremos ms adelante, para utilizar fragmentos compatibles con la mayora de
versiones de Android. Veamos cmo quedara nuestra clase asociada al fragment de listado.
1
package net.sgoliver.android.fragments;
2
3
import android.app.Activity;
4
import android.os.Bundle;
5
import android.support.v4.app.Fragment;
6
import android.view.LayoutInflater;
7
import android.view.View;
8
import android.view.ViewGroup;
9
import android.widget.AdapterView;
10
import android.widget.ArrayAdapter;
11
import android.widget.ListView;
12
import android.widget.TextView;
13
14
public class FragmentListado extends Fragment {

132

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

private Correo[] datos =


new Correo[]{
new Correo("Persona 1", "Asunto del correo 1", "Texto del correo 1"),
new Correo("Persona 2", "Asunto del correo 2", "Texto del correo 2"),
new Correo("Persona 3", "Asunto del correo 3", "Texto del correo 3"),
new Correo("Persona 4", "Asunto del correo 4", "Texto del correo 4"),
new Correo("Persona 5", "Asunto del correo 5", "Texto del correo 5")};
private ListView lstListado;
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_listado, container, false);
}
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
lstListado = (ListView)getView().findViewById(R.id.LstListado);
lstListado.setAdapter(new AdaptadorCorreos(this));
}
class AdaptadorCorreos extends ArrayAdapter<Correo> {
Activity context;
AdaptadorCorreos(Fragment context) {
super(context.getActivity(), R.layout.listitem_correo, datos);
this.context = context.getActivity();
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View item = inflater.inflate(R.layout.listitem_correo, null);
TextView lblDe = (TextView)item.findViewById(R.id.LblDe);
lblDe.setText(datos[position].getDe());
TextView lblAsunto = (TextView)item.findViewById(R.id.LblAsunto);
lblAsunto.setText(datos[position].getAsunto());

133

62
63
64
65

return(item);
}
}
}

La clase Correo es una clase sencilla, que almacena los campos De, Asunto y Texto de un
correo.
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

package net.sgoliver.android.fragments;
public class Correo
{
private String de;
private String asunto;
private String texto;
public Correo(String de, String asunto, String texto){
this.de = de;
this.asunto = asunto;
this.texto = texto;
}
public String getDe(){
return de;
}
public String getAsunto(){
return asunto;
}
public String getTexto(){
return texto;
}
}

Si observamos con detenimiento las clases anteriores veremos que no existe casi ninguna
diferencia con los temas ya comentados en artculos anteriores del curso sobre utilizacin
de controles de tipo lista y adaptadores personalizados. La nica diferencia que
encontramos aqu respecto a ejemplos anteriores, donde definamos actividades en vez de
fragments, son los mtodos que sobrescribimos. En el caso de los fragment son
normalmente dos: onCreateView() y onActivityCreated().
El primero de ellos, onCreateView(), es el equivalente al onCreate() de las actividades, es
decir, que dentro de l es donde normalmente asignaremos un layout determinado al
fragment. En este caso tendremos que inflarlo mediante el mtodo inflate() pasndole
como parmetro el ID del layout correspondiente, en nuestro caso fragment_listado.

134

El segundo de los mtodos, onActivityCreated(), se ejecutar cuando la actividad


contenedora del fragment est completamente creada. En nuestro caso, estamos
aprovechando este evento para obtener la referencia al control ListView y asociarle su
adaptador. Sobre la definicin del adaptador personalizadoAdaptadorCorreos no
comentaremos nada porque es idntico al ya descrito en el artculo sobre listas.
Con esto ya tenemos creado nuestro fragment de listado, por lo que podemos pasar al
segundo, que como ya dijimos se encargar de mostrar la vista de detalle. La definicin de
este fragment ser an ms sencilla que la anterior. El layout,
llamaremos fragment_detalle.xml, tan slo se compondr de un cuadro de texto:
1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:orientation="vertical"
6
android:background="#FFBBBBBB" >
7
8
<TextView
9
android:id="@+id/TxtDetalle"
10
android:layout_width="wrap_content"
11
android:layout_height="wrap_content" />
12
13
</LinearLayout>

que

Y por su parte, la clase java asociada, se limitar a inflar el layout de la interfaz.


Adicionalmente aadiremos un mtodo pblico, llamado mostrarDetalle(), que nos ayude
posteriormente a asignar el contenido a mostrar en el cuadro de texto.
1
package net.sgoliver.android.fragments;
2
3
import android.os.Bundle;
4
import android.support.v4.app.Fragment;
5
import android.view.LayoutInflater;
6
import android.view.View;
7
import android.view.ViewGroup;
8
import android.widget.TextView;
9
10
public class FragmentDetalle extends Fragment {
11
12
@Override
13
public View onCreateView(LayoutInflater inflater,
14
ViewGroup container,
15
Bundle savedInstanceState) {
16
17
return inflater.inflate(R.layout.fragment_detalle, container, false);
18
}

135

19
20
public void mostrarDetalle(String texto) {
21
TextView txtDetalle =
22
(TextView)getView().findViewById(R.id.TxtDetalle);
23
24
txtDetalle.setText(texto);
25
}
26
}
Una vez definidos los dos fragments, ya tan solo nos queda definir las actividades de
nuestra aplicacin, con sus respectivos layouts que harn uso de los fragments que
acabamos de implementar.
Para la actividad principal definiremos 3 layouts diferentes: el primero de ellos para los
casos en los que la aplicacin se ejecute en una pantalla normal (por ejemplo un telfono
mvil) y dos para pantallas grandes (uno pensado para orientacin horizontal y otro para
vertical). Todos se llamar activity_main.xml, y lo que marcar la diferencia ser la carpeta
en la que colocamos cada uno. As, el primero de ellos lo colocaremos en la carpeta por
defecto /res/layout, y los otros dos en las carpetas /res/layout-large(pantalla grande)
y /res/latout-large-port (pantalla grande con orientacin vertical) respectivamente. De esta
forma, segn el tamao y orientacin de la pantalla Android utilizar un layout u otro de
forma automtica sin que nosotros tengamos que hacer nada.
Para el caso de pantalla normal, la actividad principal mostrar tan slo el listado de
correos, por lo que el layout incluir tan slo el fragment FragmentListado.
1
<?xml version="1.0" encoding="utf-8"?>
2
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
3
class="net.sgoliver.android.fragments.FragmentListado"
4
android:id="@+id/FrgListado"
5
android:layout_width="match_parent"
6
android:layout_height="match_parent" />
Como podis ver, para incluir un fragment en un layout utilizaremos una
etiqueta <fragment> con un atributo class que indique la ruta completa de la clase java
correspondiente
al
fragment,
en
este
primer
caso
net.sgoliver.android.fragments.FragmentListado. Los dems atributos utilizados son los
que ya conocemos de id, layout_width y layout_height.
En este caso de pantalla normal, la vista de detalle se mostrar en una segunda actividad,
por lo que tambin tendremos que crear su layout, que llamaremos activity_detalle.xml.
Veamos rpidamente su implementacin:
1
<?xml version="1.0" encoding="utf-8"?>
2
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
3
class="net.sgoliver.android.fragments.FragmentDetalle"

136

4
5
6

android:id="@+id/FrgDetalle"
android:layout_width="match_parent"
android:layout_height="match_parent" />

Como vemos es anloga a la anterior, con la nica diferencia de que aadimos el fragment
de detalle en vez de el de listado.
Por su parte, el layout para el caso de pantalla grande horizontal, ser de la siguiente forma:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="net.sgoliver.android.fragments.FragmentListado"
android:id="@+id/FrgListado"
android:layout_weight="30"
android:layout_width="0px"
android:layout_height="match_parent" />
<fragment class="net.sgoliver.android.fragments.FragmentDetalle"
android:id="@+id/FrgDetalle"
android:layout_weight="70"
android:layout_width="0px"
android:layout_height="match_parent" />
</LinearLayout>

Como veis en este caso incluimos los dos fragment en la misma pantalla, ya que tendremos
espacio de sobra, ambos dentro de un LinearLayout horizontal, asignando al primero de
ellos un peso (propiedadlayout_weight) de 30 y al segundo de 70 para que la columna de
listado ocupe un 30% de la pantalla a la izquierda y la de detalle ocupe el resto.
Por ltimo, para el caso de pantalla grande vertical ser practicamente igual, slo que
usaremos unLinearLayout vertical.
1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout
3
xmlns:android="http://schemas.android.com/apk/res/android"
4
android:orientation="vertical"
5
android:layout_width="match_parent"
6
android:layout_height="match_parent">
7
8
<fragment class="net.sgoliver.android.fragments.FragmentListado"
9
android:id="@+id/FrgListado"
10
android:layout_weight="40"

137

11
12
13
14
15
16
17
18
19
20

android:layout_width="match_parent"
android:layout_height="0px" />
<fragment class="net.sgoliver.android.fragments.FragmentDetalle"
android:id="@+id/FrgDetalle"
android:layout_weight="60"
android:layout_width="match_parent"
android:layout_height="0px" />
</LinearLayout>

Hecho esto, ya podramos ejecutar la aplicacin en el emulador y comprobar si se


selecciona automticamente el layout correcto dependiendo de las caractersticas del AVD
que estemos utilizando. En mi caso he definido 2 AVD, uno con pantalla normal y otro
grande al que durante las pruebas he modificado su orientacin (pulsando Ctrl+F12). El
resultado fue el siguiente:
Pantalla normal (Galaxy Nexus 4.7 pulgadas):

Pantalla grande vertical (Nexus 7 7.3 pulgadas):

138

Pantalla grande horizontal (Nexus 7 7.3 pulgadas):

Como vemos en las imgenes anteriores, la interfaz se ha adaptado perfectamente a la


pantalla en cada caso, mostrndose uno o ambos fragments, y en caso de mostrarse ambos
distribuyndose horizontal o verticalmente.
Lo que an no hemos implementado en la lgica de la aplicacin es lo que debe ocurrir al
pulsarse un elemento de la lista de correos. Para ello, empezaremos asignando el
evento onItemClick() a la lista dentro del mtodo onActivityCreated() de la
clase FragmentListado. Lo que hagamos al capturar este evento depender de si en la
pantalla se est viendo el fragment de detalle o no:
1. Si existe el fragment de detalle habra que obtener una referencia a l y llamar a su
mtodomostrarDetalle() con el texto del correo seleccionado.
2. En caso contrario, tendramos que navegar a la actividad secundaria DetalleActivity para
mostrar el detalle.
Sin embargo existe un problema, un fragment no tiene por qu conocer la existencia de
ningn otro, es ms, deberan disearse de tal forma que fueran lo ms independientes

139

posible, de forma que puedan reutilizarse en distintas situaciones sin problemas de


dependencias con otros elementos de la interfaz. Por este motivo, el patrn utilizado
normalmente en estas circunstancias no ser tratar el evento en el propio fragment, sino
definir y lanzar un evento personalizado al pulsarse el item de la lista y delegar a la
actividad contenedora la lgica del evento, ya que ella s debe conocer qu fragments
componen su interfaz. Cmo hacemos esto? Pues de forma anloga a cuando definimos
eventos personalizados para un control. Definimos una interfaz con el mtodo asociado al
evento,
en
este
caso
llamada CorreosListener con
un
nico
mtodo
llamado onCorreoSeleccionado(), declaramos un atributo de la clase con esta interfaz y
definimos un mtodo set() para poder asignar el evento desde fuera de la clase. Veamos
cmo quedara:
1
public class FragmentListado extends Fragment {
2
3
//...
4
5
private CorreosListener listener;
6
7
//..
8
9
@Override
10
public void onActivityCreated(Bundle state) {
11
super.onActivityCreated(state);
12
13
lstListado = (ListView)getView().findViewById(R.id.LstListado);
14
15
lstListado.setAdapter(new AdaptadorCorreos(this));
16
17
lstListado.setOnItemClickListener(new OnItemClickListener() {
18
@Override
19
public void onItemClick(AdapterView<?> list, View view, int pos, long id) {
20
if (listener!=null) {
21
listener.onCorreoSeleccionado(
22
(Correo)lstListado.getAdapter().getItem(pos));
23
}
24
}
25
});
26
}
27
28
public interface CorreosListener {
29
void onCorreoSeleccionado(Correo c);
30
}
31
32
public void setCorreosListener(CorreosListener listener) {

140

33
34
35

this.listener=listener;
}
}

Como vemos, una vez definida toda esta parafernalia, lo nico que deberemos hacer en el
eventoonItemClick() de
la
lista
ser
lanzar
nuestro
evento
personalizado onCorreoSeleccionado()pasndole como parmetro el contenido del correo,
que en este caso obtendremos accediendo al adaptador con getAdapter() y recuperando el
elemento con getItem().
Hecho esto, el siguiente paso ser tratar este evento en la clase java de nuestra actividad
principal. Para ello, en el onCreate() de nuestra actividad, obtendremos una referencia al
fragment mediante el mtodogetFragmentById() del fragment manager (componente
encargado de gestionar los fragments) y asignaremos el evento mediante su
mtodo setCorreosListener() que acabamos de definir.
1
package net.sgoliver.android.fragments;
2
3
import net.sgoliver.android.fragments.FragmentListado.CorreosListener;
4
import android.content.Intent;
5
import android.os.Bundle;
6
import android.view.Menu;
7
import android.support.v4.app.FragmentActivity;
8
9
public class MainActivity extends FragmentActivity implements CorreosListener {
10
11
@Override
12
protected void onCreate(Bundle savedInstanceState) {
13
super.onCreate(savedInstanceState);
14
setContentView(R.layout.activity_main);
15
16
FragmentListado frgListado
17
=(FragmentListado)getSupportFragmentManager()
18
.findFragmentById(R.id.FrgListado);
19
20
frgListado.setCorreosListener(this);
21
}
22
23
@Override
24
public void onCorreoSeleccionado(Correo c) {
25
boolean hayDetalle =
26
(getSupportFragmentManager().findFragmentById(R.id.FrgDetalle) != null);
27
28
if(hayDetalle) {
29
((FragmentDetalle)getSupportFragmentManager()
30
.findFragmentById(R.id.FrgDetalle)).mostrarDetalle(c.getTexto());
31
}

141

32
33
34
35
36
37
38

else {
Intent i = new Intent(this, DetalleActivity.class);
i.putExtra(DetalleActivity.EXTRA_TEXTO, c.getTexto());
startActivity(i);
}
}
}

Como vemos en el cdigo anterior, en este caso hemos hecho que nuestra actividad herede
de nuestra interfaz CorreosListener, por lo que nos basta pasar this al
mtodo setCorreosListener(). Adicionalmente, un detalle importante a descatar es que la
actividad no hereda de la clase Activity como de costumbre, sino de FragmentActivity.
Esto es as porque estamos utilizando la librera de compatibilidad android-support para
utilizar fragments conservando la compatibilidad con versiones de Android anteriores a la
3.0. En caso de no necesitar esta compatibilidad podras seguir heredando deActivity sin
problemas.
La mayor parte del inters de la clase anterior est en el mtodo onCorreoSeleccionado().
ste es el mtodo que se ejecutar cuando el fragment de listado nos avise de que se ha
seleccionado un determinado item de la lista. Esta vez s, la lgica ser la ya mencionada,
es decir, si en la pantalla existe el fragment de detalle simplemente lo actualizaremos
mediante mostrarDetalle() y en caso contrario navegaremos a la actividad DetalleActivity.
Para este segundo caso, crearemos un nuevo Intent con la referencia a dicha clase, y le
aadiremos como parmetro extra un campo de texto con el contenido del correo
seleccionado. Finalmente llamamos a startActivity() para iniciar la nueva actividad.
Y ya slo nos queda comentar la implementacin de esta segunda
actividad, DetalleActivity. El cdigo ser muy sencillo, y se limitar a recuperar el
parmetro extra pasado desde la actividad anterior y mostrarlo en el fragment de detalle
mediante su mtodo mostrarDetalle(), todo ello dentro de su mtodo onCreate().
1
package net.sgoliver.android.fragments;
2
3
import android.os.Bundle;
4
import android.support.v4.app.FragmentActivity;
5
6
public class DetalleActivity extends FragmentActivity {
7
8
public static final String EXTRA_TEXTO =
9
"net.sgoliver.android.fragments.EXTRA_TEXTO";
10
11
@Override
12
protected void onCreate(Bundle savedInstanceState) {
13
super.onCreate(savedInstanceState);
14
setContentView(R.layout.activity_detalle);

142

15
16
17
18
19
20
21
22
23

FragmentDetalle detalle =
(FragmentDetalle)getSupportFragmentManager()
.findFragmentById(R.id.FrgDetalle);
detalle.mostrarDetalle(
getIntent().getStringExtra(EXTRA_TEXTO));
}
}

Y con esto finalizaramos nuestro ejemplo. Si ahora volvemos a ejecutar la aplicacin en el


emulador podremos comprobar el funcionamiento de la seleccin en las distintas
configuraciones de pantalla.

Action Bar en Android (I)


by Sgoliver on 12/03/2013 in Android, Programacin
La action bar de Android es la barra de ttulo y herramientas que aparece en la parte
superior de muchas aplicaciones actuales. Normalmente muestra un icono, el ttulo de la
actividad en la que nos encontramos, una serie de botones de accin, y un men
desplegable (men de overflow) donde se incluyen ms acciones que no tienen espacio para
mostrarse como botn o simplemente no se quieren mostrar como tal.

La action bar de Android es uno de esos componentes que Google no ha tratado demasiado
bien al no incluirla en la librera de compatibilidad android-support. Esto significa que de
forma nativa tan slo es compatible con versiones de Android 3.0 o superiores. En este
artculo nos centraremos nicamente en esta versin nativa de la plataforma
(configuraremos nuestra aplicacin de ejemplo para ejecutarse sobre Android 4).
Tan slo a modo de referencia dir que existe una librera muy popular
llamada ActionBarSherlock que proporciona una implementacin alternativa de este
componente que es compatible con versines de Android a partir de la 2.0. Otra alternativa
para compatibilizar nuestras aplicaciones con versiones de Android anteriores a la 3.0 sera
utilizar la implementacin que Google proporciona como parte de los ejemplos de la
plataforma, llamada ActionBarCompat, que puedes encontrar en la siguiente carpeta de tu
instalacin del SDK: <carpeta-sdk>\samples\android-nn\ActionBarCompat

143

Cuando se crea un proyecto con alguna de las ltimas versiones del plugin ADT para
Eclipse, la aplicacin creada por defecto ya incluye de serie su action bar
correspondiente. De hecho, si creamos un proyecto nuevo y directamente lo ejecutamos
sobre un AVD con Android 3.0 o superior veremos como no solo se incluye la action bar
sino que tambin aparece una accin Settings como muestra la siguiente imagen.

Vale, pero donde est todo esto definido en nuestro proyecto? La action bar de Android
toma su contenido de varios sitios diferentes. En primer lugar muestra el icono de la
aplicacin (definido en elAndroidManifest mediante el atributo android:icon del
elemento <application>) y el ttulo de la actividad actual (definido en el AndroidManifest
mediante el atributo android:label de cada elemento<activity>). El resto de elementos se
definen de la misma forma que los antiguos mens de aplicacin que se utilizaban en
versiones de Android 2.x e inferiores. De hecho, es exactamente la misma implementacin.
Nosotros definiremos un men, y si la aplicacin se ejecuta sobre Android 2.x las acciones
se mostrarn como elementos del men como tal (ya que la action bar no se ver al no ser
compatible) y si se ejecuta sobre Android 3.0 o superior aparecern como acciones de la
action bar, ya sea en forma de botn de accin o incluidas en el men de overflow. En
definitiva, una bonita forma de mantener cierta compatibilidad con versiones anteriores de
Android, aunque en unas ocasiones se muestre la action bar y en otras no.
Y bien, cmo se define un men de aplicacin? Pues en el curso hay un artculo dedicado
exclusivamente a ello donde poder profundizar, pero de cualquier forma, en este artculo
dar las directrices generales para definir uno sin mucha dificultad.

Un men se define, como la mayora de los recursos de Android, mediante un fichero


XML, y se colocar en la carpeta /res/menu. El men se definir mediante un elemento
raiz <menu> y contendr una serie de elementos <item> que representarn cada una de las
opciones. Los elementos <item> por su parte podrn incluir varios atributos que lo definan,
entre los que destacan los siguientes:
android:id. El ID identificativo del elemento, con el que podremos hacer referencia dicha
opcin.
android:title. El texto que se visualizar para la opcin.
android:icon. El icono asociado a la accin.
android:showAsAction. Si se est mostrando una action bar, este atributo indica si la
opcin de men se mostrar como botn de accin o como parte del men de overflow.
Puede tomar varios valores:

144
o
o

ifRoom. Se mostrar como botn de accin slo si hay espacio disponible.


withText. Se mostrar el texto de la opcin junto al icono en el caso de que ste se est
mostrando como botn de accin.
o never. La opcin siempre se mostrar como parte del men de overflow.
o always. La opcin siempre se mostrar como botn de accin. Este valor puede provocar
que los elementos se solapen si no hay espacio suficiente para ellos.
As, por ejemplo, si abrimos el men definido por defecto en el proyecto nuevo (llamado
normalmente/res/menu/activity_main.xml si no lo hemos cambiado de nombre durante la
creacin del proyecto) veremos el siguiente cdigo:
1 <menu xmlns:android="http://schemas.android.com/apk/res/android" >
2
3
<item
4
android:id="@+id/menu_settings"
5
android:showAsAction="never"
6
android:title="@string/menu_settings"/>
7
8 </menu>
Como vemos se define un men con una nica opcin, con el texto Settings y con el
atributoshowAsAction=never de forma que sta siempre aparezca en el men de
overflow.
Esta opcin por defecto se incluye solo a modo de ejemplo, por lo que podramos
eliminarla sin problemas para incluir las nuestras propias. En mi caso la voy a conservar
pero voy a aadir dos ms de ejemplo: Save y New, la primera de ellas para que se
muestre, si hay espacio, como botn con su icono correspondiente, y la segunda igual pero
adems acompaada de su ttulo de accin:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<menu xmlns:android="http://schemas.android.com/apk/res/android" >


<item
android:id="@+id/menu_settings"
android:showAsAction="never"
android:title="@string/menu_settings"/>
<item
android:id="@+id/menu_save"
android:showAsAction="ifRoom"
android:icon="@android:drawable/ic_menu_save"
android:title="@string/menu_guardar"/>
<item
android:id="@+id/menu_new"
android:showAsAction="ifRoom|withText"
android:icon="@android:drawable/ic_menu_add"

145

18
android:title="@string/menu_nuevo"/>
19 </menu>
Como podis ver en la segunda opcin, se pueden combinar varios valores
de showAsAction utilizando el caracter |.
Una vez definido el men en su fichero XML correspondiente tan slo queda asociarlo a
nuestra
actividad
principal.
Esto
se
realiza
sobrescribiendo
el
mtodo OnCreateOptionsMenu() de la actividad, dentro del cual lo nico que tenemos que
hacer normalmente es inflar el men llamando al mtodo inflate()pasndole como
parmetros el ID del fichero XML donde se ha definido. Este trabajo suele venir hecho ya
al crear un proyecto nuevo desde Eclipse:
1
public class MainActivity extends Activity {
2
3
...
4
5
@Override
6
public boolean onCreateOptionsMenu(Menu menu) {
7
// Inflate the menu; this adds items to the action bar if it is present.
8
getMenuInflater().inflate(R.menu.activity_main, menu);
9
return true;
10
}
11 }
Ejecutemos la aplicacin ahora a ver qu ocurre.

Como podemos observar, la opcin Settings sigue estando dentro del men de overflow,
y ahora aparecen como botones de accin las dos opciones que hemos marcado
como showAsAction=ifRoom, pero para la segunda no aparece el texto. Y por qu?
Porque no hay espacio disponible con la pantalla en vertical. Pero si rotamos el emulador
para ver qu ocurre con la pantalla en horizontal (pulsando Ctrl + F12) vemos lo siguiente:

Con la pantalla en horizontal s se muestra el texto de la segunda opcin, tal como


habamos solicitado con el valor withText del atributo showAsAction.
Ahora que ya sabemos definir los elementos de nuestra action bar queda saber cmo
responder a las pulsaciones que haga el usuario sobre ellos. Para esto, al igual que se hace
con los mens tradicionales (ver artculo sobre mens para ms detalles), sobrescribiremos
el mtodo OnOptionsItemSelected(), donde consultaremos la opcin de men que se ha

146

pulsado mediante el mtodo getItemId() de la opcin de men recibida como parmetro y


actuaremos en consecuencia. En mi caso de ejemplo tan slo escribir un mensaje al log.
1
@Override
2
public boolean onOptionsItemSelected(MenuItem item) {
3
switch (item.getItemId()) {
4
case R.id.menu_new:
5
Log.i("ActionBar", "Nuevo!");
6
return true;
7
case R.id.menu_save:
8
Log.i("ActionBar", "Guardar!");;
9
return true;
10
case R.id.menu_settings:
11
Log.i("ActionBar", "Settings!");;
12
return true;
13
default:
14
return super.onOptionsItemSelected(item);
15
}
16 }
Si ejecutamos ahora la aplicacin y miramos el log mientras pulsamos las distintas
opciones de la action bar veremos como se muestran los mensajes definidos.
Y bien, esto es todo lo que habra que contar a nivel bsico sobre la action bar, si no fuera
porque los chicos de Android pensaron que tambin sera interesante integrar con ella de
alguna forma la barra de pestaas, en caso de utilizarse este tipo de interfaz en la
aplicacin. Para no alargar demasiado este artculo, de momento pararemos aqu y dedicar
una segunda parte a este tema.
Action Bar en Android (II)
by Sgoliver on 14/03/2013 in Android, Programacin
Como coment en el artculo anterior del curso Android proporciona un mecanismo que
nos permite integrar (por llamarlo de alguna manera) una barra de pestaas con la action
bar.
En este momento muchos de vosotros seguro que habris pensando: Yo ya he aprendido a
usar el controlTabWidget en un artculo anterior de tu curso. por qu iba a querer
aprender otra forma de utilizar pestaas?. Pues bien, adems de convertirnos
instantaneamente en programadores mucho ms modernos y elegantes por
utilizar fragments para nuestros tabs :), el hecho de enlazar una lista de pestaas con la
action bar tiene una serie de ventajas adicionales, relacionadas sobre todo con la adaptacin
de tu interfaz a distintos tamaos y configuraciones de pantalla. As, por ejemplo, si

147

Android detecta que hay suficiente espacio disponible en la action bar, integrar las
pestaas dentro de la propia action bar de forma que no ocupen espacio extra en la pantalla.
Si por el contrario no hubiera espacio suficiente colocara las pestaas bajo la action bar
como de costumbre. Ms tarde veremos un ejemplo grfico de esto, pero ahora empecemos.
Lo primero que debemos hacer ser crear un nuevo fragment para albergar el contenido de
cada una de las pestaas que tendr nuestra aplicacin. Como ya vimos en el artculo
dedicado a los fragments, necesitaremos crear como mnimo una pareja de ficheros para
cada fragment, el primero para el layout XML y el segundo para su cdigo java asociado.
Para el caso de ejemplo de este artculo, que partir del ya construido en el artculo anterior,
incluir tan slo dos pestaas, y los ficheros asociados a ellas los llamar de la siguiente
forma:

Pestaa 1:
Tab1Fragment.java
fragment1.xml
Pestaa 2:
Tab2Fragment.java
fragment2.xml
La interfaz de los fragmets ser mnima para no complicar el ejemplo, y contendrn
nicamente una etiqueta de texto Tab 1 o Tab 2 para poder diferenciarlas. Por ejemplo
para la pestaa 1 tendramos un fichero fragment1.xml con el siguiente contenido:
1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
android:layout_width="match_parent"
4
android:layout_height="match_parent"
5
android:orientation="vertical" >
6
7
<TextView
8
android:id="@+id/textView1"
9
android:layout_width="wrap_content"
10
android:layout_height="wrap_content"
11
android:text="@string/tab_1" />
12
13 </LinearLayout>
Por su parte, su clase java asociada Tab1Fragment.java no tendr ninguna funcionalidad,
por lo que el cdigo se limita a inflar el layout y poco ms:
1
package net.sgoliver.android.actionbartabs;
2
3
import android.app.Fragment;
4
import android.os.Bundle;
5
import android.view.LayoutInflater;
6
import android.view.View;

148

7
8
9
10
11
12
13
14
15

import android.view.ViewGroup;
public class Tab1Fragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}

Con esto ya tendramos preparado el contenido de nuestras pestaas, y nos quedara


aadirlas a nuestra aplicacin, enlazarlas con la action bar, y asignarles un listener desde el
que poder responder a los eventos que se produzcan.
Vamos a empezar por el ltimo paso, crear el listener. Como he comentado, este listener
debe contener el cdigo asociado a los eventos clsicos de las pestaas (normalmente la
seleccin, reseleccin, o deseleccin de pestaas) y entre otras cosas tendr que encargarse
de mostrar en cada momento el fragment correspondiente a la pestaa seleccionada. Hay
muchas formas de implementar este listener, y s que la que yo voy a mostrar aqu no es la
mejor de todas, pero s es de las ms sencillas para empezar. Vamos a crear nuestro listener
creando una nueva clase que extienda de ActionBar.TabListener, en mi caso la
llamar MiTabListener (en un alarde de genialidad). Dentro de esta clase tendremos que
sobrescribir
los
mtodos
de
los
eventos onTabSelected(), onTabUnselected() y onTabReselected(). Creo que el nombre de
cada uno explica por s solo lo que hacen. Veamos primero el cdigo:
1
package net.sgoliver.android.actionbartabs;
2
3
import android.app.ActionBar;
4
import android.app.Fragment;
5
import android.app.FragmentTransaction;
6
import android.app.ActionBar.Tab;
7
import android.util.Log;
8
9
public class MiTabListener implements ActionBar.TabListener {
10
11
private Fragment fragment;
12
13
public MiTabListener(Fragment fg)
14
{
15
this.fragment = fg;
16
}
17
18
@Override
19
public void onTabReselected(Tab tab, FragmentTransaction ft) {

149

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Log.i("ActionBar", tab.getText() + " reseleccionada.");


}
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Log.i("ActionBar", tab.getText() + " seleccionada.");
ft.replace(R.id.contenedor, fragment);
}
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
Log.i("ActionBar", tab.getText() + " deseleccionada.");
ft.remove(fragment);
}
}

Como podis ver, en cada uno de estos mtodos lo nico que haremos en nuestro caso ser
mostrar u ocultar nuestros fragments de forma que quede visible el correspondiente a la
pestaa seleccionada. As, en el evento onTabSelected() reemplazaremos el fragment
actualmente visible con el de la pestaa seleccionada (que ser un atributo de nuestra clase,
despus veremos dnde y cmo se asigna), y en el mtodo onTabUnselected() ocultamos el
fragment asociado a la pestaa ya que est habr sido deseleccionada. Ambas acciones se
realizan llamando al mtodo correspondiente deFragmentTransaction, que nos llega
siempre como parmetro y nos permite gestionar los fragments de la actividad. En el primer
caso se usar el mtodo replace() y en el segundo el mtodo remove(). Adems de todo
esto, he aadido mensajes de log en cada caso para poder comprobar que se lanzan los
eventos de forma correcta.
Implementado el listener tan slo nos queda crear las pestaas, asociarle sus fragments
correspondientes, y enlazarlas a nuestra action bar. Todo esto lo haremos en el
mtodo onCreate() de la actividad principal. Son muchos pasos, pero sencillos todos ellos.
Comenzaremos obteniendo una referencia a la action bar mediante el
mtodo getActionBar(). Tras esto estableceremos su mtodo de navegacin
a NAVIGATION_MODE_TABS para que sepa que debe mostrar las pestaas. A
continuaci creamos las pestaas el mtodo newTab() de la action bar y estableceremos su
texto con setText(). Lo siguiente ser instanciar los dos fragments y asociarlos a cada
pestaa a travs de la clase listener que hemos creado, llamando para ello
a setTabListener(). Y por ltimo aadiremos las pestaas a la action bar mediante addTab().
Estoy seguro que con el cdigo se entender mucho mejor:
1
@Override
2
protected void onCreate(Bundle savedInstanceState) {
3
super.onCreate(savedInstanceState);

150

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

setContentView(R.layout.activity_main);
//Obtenemos una referencia a la actionbar
ActionBar abar = getActionBar();
//Establecemos el modo de navegacin por pestaas
abar.setNavigationMode(
ActionBar.NAVIGATION_MODE_TABS);
//Ocultamos si queremos el ttulo de la actividad
//abar.setDisplayShowTitleEnabled(false);
//Creamos las pestaas
ActionBar.Tab tab1 =
abar.newTab().setText("Tab 1");
ActionBar.Tab tab2 =
abar.newTab().setText("Tab 2");
//Creamos los fragments de cada pestaa
Fragment tab1frag = new Tab1Fragment();
Fragment tab2frag = new Tab2Fragment();
//Asociamos los listener a las pestaas
tab1.setTabListener(new MiTabListener(tab1frag));
tab2.setTabListener(new MiTabListener(tab2frag));
//Aadimos las pestaas a la action bar
abar.addTab(tab1);
abar.addTab(tab2);
}

Con esto habramos acabado. Si ejecutamos la aplicacin en el emulador veremos algo


como lo siguiente:

Como vemos, Android ha colocado nuestras pestaas bajo la action bar porque no ha
encontrado sitio suficiente arriba. Pero si probamos con la orientacin horizontal
(Ctrl+F12) pasa lo siguiente (click para ampliar):

151

Dado que ya haba espacio suficiente en la propia action bar, Android ha colocado las
pestaas directamente sobre ella de forma que hemos ganado espacio para el resto de la
interfaz. Esto no lo habramos podido conseguir utilizando el control TabWidget.
Por ltimo, si cambiamos varias veces de pestaa deberamos comprobar que se muestra en
cada caso el fragment correcto y que el en log aparecen los mensajes de depuracin que
hemos incluido.
Espero que con estos dos ltimos artculos hayis aprendido al menos a aprovechar la
funcionalidad bsica de la action bar. Habra por supuesto ms cosas que contar, por
ejemplo algunas cuestiones de navegacin entre actividades, pero eso lo dejaremos para
ms adelante.
Action Bar en Android (III): ActionBar Compat
by Admin on 03/08/2013 in Android, Programacin
Hace ya algn tiempo que hablamos en el curso sobre uno de los elementos visuales ms
representativos de las aplicaciones Android actuales, la Action Bar. Por aquel entonces
coment que Google haba maltratado un poco a este componente al no incluirlo en la
librera de compatibilidad android-support, lo que haca que no fuera compatible con
versiones de Android anteriores a la 3.0. Esto obligaba a recurrir a libreras externas
como ActioBarSherlock para hacer nuestra aplicacin compatible con cualquier versin de
Android.
Y hemos tenido que esperar hasta hace unos das (julio de 2013) para ver por fin a la Action
Bar debutar, de serie, en la librera de compatibilidad de Android. A partir de la revisin 18
de esta librera contamos con una versin de la Action Bar (conocida tambin
como ActionBarCompat) compatible con cualquier versin Android a partir de la 2.1 (API
7). Y es de esto de lo que nos vamos a ocupar en este nuevo artculo, ya que aunque su uso
es muy similar a lo que ya comentamos en el primer artculo sobre la Action Bar, son
necesarios algunos pequeos cambios en el cdigo y en la configuracin del proyecto que
justifican una nueva entrada para este tema.
Lo primero que tendremos que asegurar es que tenemos instalada la ltima versin de la
librera de compatibilidad (Android Support Library). Para ello entramos al SDK Manager
y actualizamos la librera a la ltima revisin (la 18 en el momento de escribir este
artculo).

152

Dado que el componente ActionBar incluido en la librera de compatibilidad contiene


recursos no bastar con incluir el fichero jar de la librera en nuestro proyecto, sino que
para utilizarlo habr que importar previamente los fuentes como proyecto independiente en
Eclipse y despus utilizar dicho proyecto como una dependencia del nuestro. Para ello, el
siguiente paso ser esta importacin de la librera. Los fuentes de la librera se encuentran
en la siguiente ruta:
<ruta-sdk-android>\extras\android\support\v7\appcompat
Para importarlo como proyecto en Eclipse usaremos la opcin File/Import,
seleccionaremos Existing Android Code Into Workspace, indicaremos la ruta anterior y
marcaremos la opcin Copy into workspace.

Hecho esto ya tendramos el proyecto de la librera correctamente importado en Eclipse:

153

Son necesarios un par de pasos ms para dejar correctamente configurado el proyecto de la


librera. Entramos en las propiedades del proyecto, accedemos a la seccin Java Build
Path y despus a la solapa Order and Export. En esta vista marcamos las dos libreras
android-support-v4.jar y android-support-v7-appcompat.jar y desmarcamos Android
Dependencies, quedando de la siguiente forma:

Aceptamos todo y ya tendramos preparada la librera de compatibilidad para poder


aadirla a nuestro proyecto, que ser lo siguiente que vamos a crear.
Crearemos nuestro proyecto como siempre hemos hecho: File/New/Project/Android
Application Project y seguiremos el asistente con la precaucin de seleccionar como
Minimum Required SDK una versin de Android anterior a la 3.0, para poder probar que
efectivamente funciona nuestra Action Bar sobre esas versiones. En mi caso he
seleccionado como versin de compilacin la 4.2, como versin mnima la 2.2, y he
llamado al proyecto android-abcompat, el resto de opciones por defecto.
Accedemos ahora a las propiedades del nuevo proyecto, y despus a la seccin Android.
Aqu vamos a aadir la referencia a la librera de compatibilidad que hemos importado y

154

preparado antes como proyecto independiente. En la seccin Library pulsamos Add y


seleccionamos el proyecto importado antes (android-support-v7-appcompat). Finalmente
aceptamos y ya tenemos todo configurado para empezar a hacer uso de la action bar.

Ms temas a tener en cuenta. Tendremos que sustituir (o extender) el tema visual utilizado
en la aplicacin por alguno de los definidos especficamente para la librera de
compatibilidad:

Theme.AppCompat
Theme.AppCompat.Light
Theme.AppCompat.Light.DarkActionBar
En mi caso voy a utilizar el ltimo de ellos. Modificaremos para ello nuestro fichero
AndroidManifest.xml indicando el nuevo tema a utilizar en el atributo android:theme del
elemento <application> (aunque tambin se puede definir a nivel de actividades).
1
...
2
<application
3
android:allowBackup="true"
4
android:icon="@drawable/ic_launcher"
5
android:label="@string/app_name"
6
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" >
7
<activity
8
android:name="net.sgoliver.android.abcompat.MainActivity"
9
android:label="@string/app_name" >
10
<intent-filter>
11
<action android:name="android.intent.action.MAIN" />
12
<category android:name="android.intent.category.LAUNCHER" />
13
</intent-filter>
14
</activity>
15
</application>
16 ...

155

Tenemos que modificar tambin la clase de la que heredan nuestras actividades,


sustituyendo la claseActivity por la nueva clase ActionBarActivity de la librera androidsupport.
1
...
2
import android.support.v7.app.ActionBarActivity;
3
4
public class MainActivity extends ActionBarActivity {
5
6
@Override
7
protected void onCreate(Bundle savedInstanceState) {
8
super.onCreate(savedInstanceState);
9
setContentView(R.layout.activity_main);
10
}
11
12
//...
13 }
Por ltimo, la forma de aadir las opciones a la action bar no cambia, contina siendo
mediante la definicin de mens XML (encontrars ms informacin en el artculo
original del curso sobre la Action Bar). Pero s vara un poco la forma en que debemos
establecer algunos atributos de los mens.
Dado que en versiones anteriores a Android 3.0 no existan los atributos de los mens
destinados a definir la funcionalidad de la action bar, por ejemplo el
atributo android:showAsAction, no podemos utilizarlos libremente ahora que queremos que
nuestra aplicacin funcione sobre dichas versiones. Para solucionar esto lo que haremos
ser utilizar dichos atributos indicando un espacio de nombres personalizado, que
definiremos en el elemento <menu> y posteriormente usaremos en los elementos <item>.
Veamos cmo.
Definiremos el nuevo espacio de nombres en el elemento <menu> aadiendo un nuevo
atributo de esta forma:
xmlns:nombre_del_nuevo_espacio_de_nombres=http://schemas.android.com/apk/resauto
Podemos utilizar el nombre que queramos, yo por ejemplo usar sgoliver:
xmlns:sgoliver=http://schemas.android.com/apk/res-auto
Posteriormente, en los elementos <item> del men precederemos los atributos especficos
de la action bar con este nuevo espacio de nombres (namespace).
En mi caso, he aadido dos acciones al men, una de ellas (settings) que quiero que
aparezca siempre en el men de overflow, y la segunda (search) que quiero que aparezca

156

como botn de accin si hay espacio para ella. De esta forma, la definicin completa de mi
men quedara de la siguiente forma:
1
<menu xmlns:android="http://schemas.android.com/apk/res/android"
2
xmlns:sgoliver="http://schemas.android.com/apk/res-auto" >
3
4
<item
5
android:id="@+id/action_settings"
6
android:orderInCategory="100"
7
sgoliver:showAsAction="never"
8
android:title="@string/action_settings"/>
9
10
<item
11
android:id="@+id/action_search"
12
android:orderInCategory="100"
13
sgoliver:showAsAction="ifRoom"
14
android:icon="@drawable/ic_action_search"
15
android:title="@string/action_search"/>
16 </menu>
Como puede verse en el cdigo anterior, al margen de la definicin del nuevo espacio de
nombres y de la utilizacin del atributo showAsAction asociado a l, el resto del cdigo es
exactamente igual que el que ya vimos en el artculo original sobre la action bar.
Por ltimo, para responder a las pulsaciones sobre las distintas acciones que hemos incluido
en la action bar podemos seguir utilizando el evento onOptionsItemSelected, en el que
dependiendo del ID de la opcin pulsada ejecutaremos las acciones que corresponda. En mi
caso voy simplemente a mostrar un toast con la opcin seleccionada:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId())
{
case R.id.action_settings:
Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show();;
break;
case R.id.action_search:
Toast.makeText(this, "Search", Toast.LENGTH_SHORT).show();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}

157

Llegados a este punto ya podemos ejecutar nuestro proyecto en el emulador para ver que
todo funciona correctamente. En primer lugar lo vamos a hacer sobre un AVD con Android
4.2. Podemos ver cmo no existe ninguna diferencia, ni en aspecto ni en comportamiento,
con la action bar tradicional.

Ejecutamos ahora sobre Android 2.2 y esto es lo que nos aparece:

Todo parece correcto, salvo porque no aparece el men de overflow dnde estn las
opciones que hemos configurado para que aparezcan en el men de la action bar? Pues
siguen presentes, pero no en el mismo lugar. Android establece que si el dispositivo tiene
un botn fsico de Men, el botn de overflow desaparece y se sustituye por un men
tradicional que aparece en la zona inferior cuando se pulsa el botn Men del dispositivo.
Este ser el caso de la mayora de dispositivos que funcionen con Android 2.x. Si pulsamos
el botn men del emulador veremos como ahora s aparece nuestra accin Settings.

158

Y hasta aqu el artculo. Espero que hayan quedado claros los pasos necesarios para utilizar
esta nueva versin de la Action Bar compatible con la mayora de verisiones de Android.
Todo sea por que ms personas puedan disfrutar de nuestras aplicaciones en su totalidad.
Interfaz de Usuario en Android: Navigation Drawer
by Admin on 10/08/2013 in Android, Programacin
En el artculo anterior hablamos sobre una de las ltimas novedades del SDK de Android,
la nueva versin de la Action Bar incluida en la librera de compatibilidad android-support.
En este nuevo artculo vamos a tratar otra de las novedades presentadas en el Google I/O de
este ao relacionadas la capa de presentacin de nuestras aplicaciones, el men lateral
deslizante, o dicho de una forma mucho ms moderna y elegante: el Navigation Drawer.
Este tipo de mens de navegacin ya llevaban tiempo utilizndose y proliferando por el
market en numerosas aplicaciones, pero igual que pasaba con la action bar en versiones de
Android anteriores a la 3.0, se haca gracias a libreras externas que implementaban este
componente, o a diversas implementaciones ad-hoc, todas ellas distintas e incoherentes
entre s, tanto en diseo como en funcionalidad. Google ha querido acabar con esto
aportando su propia implementacin de este componente y definiendo su comportamiento
en las guas de diseo de la plataforma.
En este caso, en la documentacin oficial de Android (en ingls, por supuesto) est bastante
bien explicado cmo incluir este elemento en nuestras aplicaciones, pero an as voy a
detallarlo paso a paso en este artculo intercalando algunas notas que no aparecen en la
documentacin y que pueden evitaros algunas sorpresas.
El navigation drawer est disponible como parte de la librera de compatibilidad androidsupport. Para poder utilizarlo en nuestras aplicaciones tendremos que asegurarnos que
tenemos incluida en nuestro proyecto la librera android-support-v4.jar (debe ser la revisin
18 o superior, podemos comprobar cul tenemos instalada en el SDK Manager). Si tenemos
instalada una versin reciente del plugin de Android en Eclipse, al crear un nuevo proyecto
se aade directamente esta librera a la carpeta /lib.
Como ejemplo para este artculo, partir del cdigo ya construdo en el artculo
anterior sobre la Action Bar Compat, ya que es interesante ver cmo interactan ambos
elementos en la misma aplicacin y cmo podemos hacerlo compatible con todas las
versiones de Android.
Comprobado que tenemos incluida la librera android-suport, ya podemos comenzar a crear
nuestra aplicacin, y comenzaremos como siempre creando la interfaz de usuario. Para
aadir el navigation drawer a una actividad debemos hacer que el elemento raz del layout

159

XML sea del tipo<android.support.v4.widget.DrawerLayout>. Y dentro de este elemento


colocaremos nicamente 2 componentes (en el orden indicado):
1. Un FrameLayout, que nos servir ms tarde como contenedor de la interfaz real de la
actividad, que crearemos a base de fragments. Ajustaremos el ancho y el alto para que
ocupe todo el espacio disponible (match_parent).
2. Un ListView, que har las veces de contenedor de las distintas opciones del men lateral.
En este caso ajustaremos el alto para ocupar todo el espacio, y el ancho a un tamao no
superior a 320dp, de forma que cuando est el men abierto no oculte totalmente el
contenido principal de la pantalla.
En mi caso de ejemplo quedara como sigue (/res/layout/activity_main.xml):
1
<android.support.v4.widget.DrawerLayout
2
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:id="@+id/drawer_layout"
4
android:layout_width="match_parent"
5
android:layout_height="match_parent" >
6
7
<!-- Contenido Principal -->
8
<FrameLayout
9
android:id="@+id/content_frame"
10
android:layout_width="match_parent"
11
android:layout_height="match_parent" />
12
13
<!-- Men Lateral -->
14
<ListView
15
android:id="@+id/left_drawer"
16
android:layout_width="240dp"
17
android:layout_height="match_parent"
18
android:layout_gravity="start"
19
android:background="#111"
20
android:choiceMode="singleChoice" />
21
22 </android.support.v4.widget.DrawerLayout>
Definida la interfaz XML, nos centramos ya en la parte java de la actividad. Lo primero que
haremos ser aadir al men (recordemos que se trata realmente de un ListView)
opciones que queremos que aparezcan disponibles. Para no complicar el ejemplo
aadir directamente desde un array java convencional (como alternativa,
la documentacin de Google tenis un ejemplo de cmo cargar las opciones desde

las
las
en
un

XML). La forma de incluir estas opciones a la lista es mediante un adaptador normal, igual
que hemos comentado en ocasiones anteriores. Lo haremos por ejemplo dentro del
mtodo onCreate() de la actividad:
1
//...
2
import android.widget.ArrayAdapter;
3
import android.widget.ListView;

160

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

import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
public class MainActivity extends ActionBarActivity {
private String[] opcionesMenu;
private DrawerLayout drawerLayout;
private ListView drawerList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
opcionesMenu = new String[] {"Opcin 1", "Opcin 2", "Opcin 3"};
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
drawerList = (ListView) findViewById(R.id.left_drawer);
drawerList.setAdapter(new ArrayAdapter<String>(
getSupportActionBar().getThemedContext(),
android.R.layout.simple_list_item_1, opcionesMenu));
}
//...
}

Como podis ver en el cdigo anterior, el contexto pasado como parmetro al adaptador lo
hemos obtenido mediante una llamada al mtodo getThemedContext() de la action bar
(relevante tambin el haber usado getSupportActionBar() en vez de getActionBar() por
estar utilizando la versin de la action bar incluida en la librera de compatibilidad). Por
decirlo una forma sencilla, esto nos asegurar que los elementos de la lista se muestren
acordes al estilo de la action bar. Adems, vemos que como layout de los elementos de la
lista he utilizado el estandar android.R.layout.simple_list_item_1. Esto nos asegura
compatibilidad con la mayora de versiones de Android sin complicarnos mucho, aunque
tiene algunos problemas. Por ejemplo, la opcin seleccionada no se mantendr resaltada en
el men una vez se cierre y se vuelva a abrir. Para conseguir esto en Android 4 podemos
simplemente usar el layout android.R.layout.simple_list_item_activated_1, aunque

debemos tener en cuenta que la aplicacin generar un error si se ejecuta sobre versiones
anteriores. Para evitar este problema de compatibilidad tenemos varias opciones:
Utilizar un layout u otro dependiendo de la versin de Android sobre la que se est
ejecutando la aplicacin, asumiendo as que la opcin seleccionada se mantendr resaltada
en Android 4 pero no en versiones anteriores. Sera algo as:
1 drawerList.setAdapter(new
2 ArrayAdapter<String>(getSupportActionBar().getThemedContext(),

161

3
4

(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ?


android.R.layout.simple_list_item_activated_1 :
android.R.layout.simple_list_item_1, opcionesMenu));
Utilizar un layout alternativo que mantenga la opcin reslatada aunque no se muestre igual
que en Android 4. Por ejemplo si usamos el layout simple_list_item_checked en Android
2.x la opcin se mantendr resaltada con una check a la derecha de su nombre. En este caso
quedara as:
drawerList.setAdapter(new
1
ArrayAdapter<String>(getSupportActionBar().getThemedContext(),
2
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ?
3
android.R.layout.simple_list_item_activated_1 :
4
android.R.layout.simple_list_item_checked, opcionesMenu));
Implementar un adaptador personalizado para establecer manualmente el estilo de la opcin
seleccionada de la lista. No entrar en los detalles de esta opcin porque se sale un poco del
objetivo de este artculo, pero tienes informacin sobre cmo crear adaptadores
personalizados en artculos anteriores.
Para no complicar nuestro caso de ejemplo vamos a dejarlo tal como est, por lo que la
opcin seleccionada no quedar resaltada en el men.
A continuacin vamos a crear los fragments que mostraremos al seleccionar cada una de las
tres opciones del men de navegacin. Y en este paso no nos vamos a complicar ya que no
es el objetivo de este artculo. Voy a crear un fragment por cada opcin, que contenga tan
slo una etiqueta de texto indicando la opcin a la que pertenece. Obviamente en la prctica
esto no ser tan simple y habr que definir cada fragment para que se ajuste a las
necesidades de la aplicacin.
Como ejemplo muestro el layout XML y la implementacin java de uno de los layout.
Primero el layout (fragment_1.xml) :
1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout
3
xmlns:android="http://schemas.android.com/apk/res/android"
4
android:layout_width="match_parent"
5
android:layout_height="match_parent"
6
android:orientation="vertical" >
7
8
<TextView
9
android:id="@+id/TxtDetalle"
10
android:layout_width="wrap_content"
11
android:layout_height="wrap_content"
12
android:text="@string/fragment1" />
13
14 </LinearLayout>
Y su clase java asociada (Fragment1.java), que se limitar a inflar el layout anterior:

162

1
package net.sgoliver.android.navdrawer;
2
3
import android.os.Bundle;
4
import android.support.v4.app.Fragment;
5
import android.view.LayoutInflater;
6
import android.view.View;
7
import android.view.ViewGroup;
8
9
public class Fragment1 extends Fragment {
10
11
@Override
12
public View onCreateView(
13
LayoutInflater inflater, ViewGroup container,
14
Bundle savedInstanceState) {
15
16
return inflater.inflate(R.layout.fragment_1, container, false);
17
}
18 }
Los dos fragments restantes sern completamente anlogos al mostrado.
Ya tenemos listo el men y los fragments asociados a cada opcin. Lo siguiente ser
implementar la lgica necesaria para responder a los eventos del men de forma que
cambiemos de fragment al pulsar cada opcin. Esto lo haremos implementando el
evento onItemClick del control ListView del men, lgica que aadiremos al final del
mtodo onCreate() de nuestra actividad principal.
1
@Override
2
protected void onCreate(Bundle savedInstanceState) {
3
4
//...
5
6
drawerList.setOnItemClickListener(new OnItemClickListener() {
7
@Override
8
public void onItemClick(AdapterView parent, View view,
9
int position, long id) {
10
11
Fragment fragment = null;
12
13
switch (position) {
14
case 1:
15
fragment = new Fragment1();
16
break;
17
case 2:
18
fragment = new Fragment2();
19
break;
20
case 3:

163

21
fragment = new Fragment3();
22
break;
23
}
24
25
FragmentManager fragmentManager =
26
getSupportFragmentManager();
27
28
fragmentManager.beginTransaction()
29
.replace(R.id.content_frame, fragment)
30
.commit();
31
32
drawerList.setItemChecked(position, true);
33
34
tituloSeccion = opcionesMenu[position];
35
getSupportActionBar().setTitle(tituloSeccion);
36
37
drawerLayout.closeDrawer(drawerList);
38
}
39
});
40 }
Comentemos un poco el cdigo anterior. En primer lugar lo que hacemos es crear el nuevo
fragment a mostrar dependiendo de la opcin pulsada en el men de navegacin, que nos
llega como parmetro (position) del evento onItemClick. En el siguiente paso hacemos uso
del Fragment Manager (con getSupportFragmentManager() para hacer uso una vez ms de
la librera de compatibilidad) para sustituir el contenido del FrameLayout que definimos en
el layout de la actividad principal por el nuevo fragment creado. Posteriormente marcamos
como seleccionada la opcin pulsada de la lista mediante el mtodosetItemChecked(),
actualizamos el ttulo de la action bar por el de la opcin seleccionada, y por ltimo
cerramos el men llamando a closeDrawer().
Bien, pues ya tenemos la funcionalidad bsica implementada. Ahora nos quedara ajustar
algunos detalles para respetar las pautas definidas en la gua de diseo del componente
Navigation Drawer. Segn las recomendaciones de esta esta gua deberamos mostrar un
indicador en la action bar que evidencia al usuario la existencia del men lateral,
deberamos adems permitir al usuario abrirlo haciendo click en el icono de la aplicacin
(adems del gesto de deslizar desde el borde izquierdo hacia la derecha), y adicionalmente
cuando est abierto el men deberamos actualizar el ttulo de la action bar y ocultar
aquellas acciones relacionadas exclusivamente con el contenido principal actual
(semioculto por el men). La mayora de estas tareas las vamos a realizar ayudndonos de
una clase auxiliar llamadaActionBarDrawerToggle. Vayamos por partes.

164

Vamos a comenzar creando un objeto de esta clase ActionBarDrawerToggle y asocindolo


a nuestro navigation drawer mediante el mtodo setDrawerListener() para, entre otras
cosas, responder a travs de l a los eventos de apertura y cierre del men. Para esto ltimo
sobrescribiremos sus mtodosonDrawerOpened() y onDrawerClosed(). Todo esto lo
haremos tambin dentro del mtodo onCreate()de nuestra clase principal. Veamos el cdigo
y a continuacin lo comentaremos:
1
@Override
2
protected void onCreate(Bundle savedInstanceState) {
3
4
//...
5
6
tituloApp = getTitle();
7
8
drawerToggle = new ActionBarDrawerToggle(this,
9
drawerLayout,
10
R.drawable.ic_navigation_drawer,
11
R.string.drawer_open,
12
R.string.drawer_close) {
13
14
public void onDrawerClosed(View view) {
15
getSupportActionBar().setTitle(tituloSeccion);
16
ActivityCompat.invalidateOptionsMenu(MainActivity.this);
17
}
18
19
public void onDrawerOpened(View drawerView) {
20
getSupportActionBar().setTitle(tituloApp);
21
ActivityCompat.invalidateOptionsMenu(MainActivity.this);
22
}
23
};
24
25
drawerLayout.setDrawerListener(drawerToggle);
26 }
En primer lugar vemos que el constructor de la clase ActionBarDrawerToggle recibe 5
parmetros: como casi siempre el contexto actual, una referencia al navigation drawer, el
ID del icono a utilizar como indicador del navigation drawer, y los ID de dos cadenas de
caracteres que se utilizar a efectos de accesibilidad de la aplicacin (en mi caso las defino
simplemente con los valores Men Abierto y Men Cerrado). Para obtener un icono
apropiado para el indicador del navigation drawer podis utilizar la utilidad Navigation
Drawer Indicator Generator del Android Asset Studio, que os permitir generar y descagar
el icono de forma que tan slo tenis que copiarlo a las carpetas /res/drawable-xxx de
vuestro proyecto.

165

A continuacin se implementan los eventos de apertura y cierre del men lateral. Lo nico
que haremos en estos eventos ser actualizar el ttulo de la action bar para mostrar el ttulo
de la aplicacin (cuando el men est abierto) o el ttulo de la opcin seleccionada
actualmente (cuando el men est cerrado). Adems, al final de cada uno de ellos hacemos
una llamada a invalidateOptionsMenu() para provocar que se ejecute el
evento onPrepareOptionsMenu() de la actividad, donde nos ocuparemos de ocultar las
acciones de la action bar que no apliquen cuando el men lateral est abierto. Importante
llamar a este mtodo haciendo uso de nuevo de su alternativa incluida en la librera de
compatibilidad, como mtodo de la clase ActivityCompat, ya que el
mtodo invalidateOptionsMenu() apareci con la API 11 (Android 3.0) y no funcionara en
versiones anteriores.
En nuestro caso de ejemplo ocultaremos la accin de buscar:
1
@Override
2
public boolean onPrepareOptionsMenu(Menu menu) {
3
4
boolean menuAbierto = drawerLayout.isDrawerOpen(drawerList);
5
6
if(menuAbierto)
7
menu.findItem(R.id.action_search).setVisible(false);
8
else
9
menu.findItem(R.id.action_search).setVisible(true);
10
11
return super.onPrepareOptionsMenu(menu);
12 }
Con esto ya cumplimos la mayora de las recomendaciones de la gua de diseo, pero an
nos falta permitir al usuario abrir el men pulsando sobre el icono de la aplicacin de la
action bar.
Para ello, al final del mtodo onCreate() habilitaremos la pulsacin del icono llamando a
los mtodossetDisplayHomeAsUpEnabled() y setHomeButtonEnabled(), y aadiremos al
eventoonOptionsItemSelected() (el encargado de procesar las pulsaciones sobre la action
bar,
una
llamada
inicial
al
mtodo onOptionsItemSelected() del
objeto ActionBarDrawerToggle creado anteriormente, de forma que si ste devuelve true
(significara que se ha gestionado una pulsacin sobre el icono de la aplicacin) salgamos
directamente de este mtodo.
1
public void onCreate(Bundle savedInstanceState) {
2
3
//...
4

166

5
6
7
8
9
10
11
12
13
14
15
16
17

getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
//...
}

Por ltimo, dos indicaciones ms incluidas en la documentacin oficial de la

claseActionBarDrawerToggle:
Implementar el evento onPostCreate() de la actividad, donde llamando al
mtodo syncState() del objeto ActionBarDrawerToggle.
Implementar el evento onConfigurationChanged() de la actividad, donde llamaremos al
mtodo homlogo del objeto ActionBarDrawerToggle.
Veamos cmo quedaran el cdigo de estos dos ltimos pasos:
1
2
3
4
5
6
7
8
9
10
11

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}

Llegados aqu, podemos ejecutar el proyecto y ver si todo funciona correctamente.


Verificaremos que el men se abra, que contiene las opciones indicadas y que al pulsar
sobre ellas aparece en pantalla el contenido asociado. Verificaremos adems que el ttulo y
acciones de la action bar se va actualizando segn el estado del men y la opcin
seleccionada.
Si por ejemplo lo ejecutamos sobre Android 4 lo veremos como se muestra en las
siguientes capturas (sobre Android 2.x se vera de forma casi idntica). Men cerrado /
Men abierto:

167

Espero que estos dos ltimos artculos os hayan servido para aprender a construir
aplicaciones utilizando los componentes de diseo ms representativos de la plataforma
Android (la Action Bar y el Navigation Drawer) sin que por ello haya que restringirse a
versiones recientes del sistema operativo ni recurrir a libreras externas.

Mens en Android
Mens en Android (I): Conceptos bsicos
by Sgoliver on 21/03/2011 in Android, Programacin
En los dos siguientes artculos del Curso de Programacin Android nos vamos a centrar en
la creacin de mens de opciones en sus diferentes variantes.
NOTA IMPORTANTE: A partir de la versin 3 de Android los mens han caido en
desuso debido a la aparicin de la Action Bar. De hecho, si compilas tu aplicacin con un
target igual o superior a la API 11 (Android 3.0) vers como los mens que definas
aparecen, no en su lugar habitual en la parte inferior de la pantalla, sino en el men
desplegable de la action bar en la parte superior derecha. Por todo esto, lo que leers en este
artculo sobre mens y submens aplica tan slo a aplicaciones realizadas para Android 2.x
o inferior. Si quieres conocer ms detalles sobre la action bar tienes dos artculos dedicados
a ello en elndice del curso. De cualquier forma, este artculo sigue siendo til ya que la
forma de trabajar con la action bar se basa en la API de mens y en los recursos que
comentaremos en este texto.
En Android podemos encontrar 3 tipos diferentes de mens:

168

Mens Principales. Los ms habituales, aparecen en la zona inferior de la pantalla al


pulsar el botn menu del telfono.
Submens. Son mens secundarios que se pueden mostrar al pulsar sobre una opcin de un
men principal.
Mens Contextuales. tiles en muchas ocasiones, aparecen al realizar una pulsacin larga
sobre algn elemento de la pantalla.
En este primer artculo sobre el tema veremos cmo trabajar con los dos primeros tipos de
mens. En el siguiente, comentaremos los mens contextuales y algunos caractersticas ms
avanzadas.
Como casi siempre, vamos a tener dos alternativas a la hora de mostrar un men en nuestra
aplicacin Android. La primera de ellas mediante la definicin del men en un fichero
XML, y la segunda creando el men directamente mediante cdigo. En este artculo
veremos ambas alternativas.
Veamos en primer lugar cmo crear un men a partir de su definicin en XML. Los
ficheros XML de men se deben colocar en la carpeta res\menu de nuestro proyecto y
tendrn una estructura anloga a la del siguiente ejemplo (si hemos creado el proyecto con
una versin reciente del plugin de Android para Eclipse es posible que ya tengamos un
men por defecto creado en esta carpeta, por lo que simplemente tendremos que
modificarlo):
1 <menu xmlns:android="http://schemas.android.com/apk/res/android" >
2
<item android:id="@+id/MnuOpc1" android:title="Opcion1"
3
android:icon="@android:drawable/ic_menu_preferences"></item>
4
<item android:id="@+id/MnuOpc2" android:title="Opcion2"
5
android:icon="@android:drawable/ic_menu_compass"></item>
6
<item android:id="@+id/MnuOpc3" android:title="Opcion3"
7
android:icon="@android:drawable/ic_menu_agenda"></item>
8 </menu>
Como vemos, la estructura bsica de estos ficheros es muy sencilla. Tendremos un
elemento principal<menu> que contendr una serie de elementos <item> que se
correspondern con las distintas opciones a mostrar en el men. Estos
elementos <item> tendrn a su vez varias propiedades bsicas, como su ID (android:id), su
texto (android:title) o su icono (android:icon). Los iconos utilizados debern estar por
supuesto en las carpetas res\drawable- de nuestro proyecto (al final del artculo os paso
unos enlaces donde podis conseguir algunos iconos de men Android gratuitos) o bien
utilizar alguno de los que ya vienen de fbrica con la plataforma Android. En nuestro
caso de ejemplo he utilizado esta segunda opcin, por lo que haremos referencia a los

169

recursos con el prefijo @android:drawable/. Si quisiramos utilizar un recurso propio


escribiramos directamente @drawable/ seguido del nombre del recurso.
Una vez definido el men en el fichero XML, tendremos que implementar el
eventoonCreateOptionsMenu() de la actividad que queremos que lo muestre (esto tambin
es posible que lo tengamos ya incluido por defecto en nuestra actividad principal). En este
evento deberemos inflar el men de forma parecida a cmo ya hemos hecho otras veces
con otro tipo de layouts. Primero obtendremos una referencia al inflater mediante el
mtodo getMenuInflater() y posteriormente generaremos la estructura del men llamando a
su mtodo infate() pasndole como parmetro el ID del menu definido en XML, que mi
nuestro caso ser R.menu.activity_main. Por ltimo devolveremos el valor true para
confirmar que debe mostrarse el men.
1 @Override
2 public boolean onCreateOptionsMenu(Menu menu) {
3
//Alternativa 1
4
getMenuInflater().inflate(R.menu.activity_main, menu);
5
return true;
6 }
Y ya hemos terminado, con estos sencillos pasos nuestra aplicacin ya debera mostrar sin
problemas el men que hemos construdo, aunque todava nos faltara implementar la
funcionalidad de cada una de las opciones mostradas.

Como hemos comentado antes, este mismo men tambin lo podramos crear directamente
mediante cdigo, tambin desde el evento onCreateOptionsMenu(). Para ello, para aadir
cada opcin del men podemos utilizar el mtodo add() sobre el objeto de tipo Menu que
nos llega como parmetro del evento. Este mtodo recibe 4 parmetros: ID del grupo

170

asociado a la opcin (veremos qu es esto en un prximo artculo, por ahora


utilizaremos Menu.NONE), un ID nico para la opcin (que declararemos como constantes
de la clase), el orden de la opcin (que no nos interesa por ahora,
utilizaremos Menu.NONE) y el texto de la opcin. Por otra parte, el icono de cada opcin
lo estableceremos mediante el mtodosetIcon() pasndole el ID del recurso. Veamos cmo
quedara el cdigo utilizando esta alternativa, que generara un men exactamente igual al
del ejemplo anterior:
1
private static final int MNU_OPC1 = 1;
2
private static final int MNU_OPC2 = 2;
3
private static final int MNU_OPC3 = 3;
4
5
//...
6
7
@Override
8
public boolean onCreateOptionsMenu(Menu menu) {
9
//Alternativa 2
10
menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion1")
11
.setIcon(android.R.drawable.ic_menu_preferences);
12
menu.add(Menu.NONE, MNU_OPC2, Menu.NONE, "Opcion2")
13
.setIcon(android.R.drawable.ic_menu_compass);
14
menu.add(Menu.NONE, MNU_OPC3, Menu.NONE, "Opcion3")
15
.setIcon(android.R.drawable.ic_menu_agenda);
16
return true;
17 }
Construido el men, la implementacin de cada una de las opciones se incluir en el
eventoonOptionsItemSelected() de la actividad que mostrar el men. Este evento recibe
como parmetro el item de men que ha sido pulsado por el usuario, cuyo ID podemos
recuperar con el mtodogetItemId(). Segn este ID podremos saber qu opcin ha sido
pulsada y ejecutar unas acciones u otras. En nuestro caso de ejemplo, lo nico que haremos
ser modificar el texto de una etiqueta (lblMensaje) colocada en la pantalla principal de la
aplicacin.
1
@Override
2
public boolean onOptionsItemSelected(MenuItem item) {
3
switch (item.getItemId()) {
4
case R.id.MnuOpc1:
5
lblMensaje.setText("Opcion 1 pulsada!");
6
return true;
7
case R.id.MnuOpc2:
8
lblMensaje.setText("Opcion 2 pulsada!");;
9
return true;
10
case R.id.MnuOpc3:
11
lblMensaje.setText("Opcion 3 pulsada!");;

171

12
13
14
15
16

return true;
default:
return super.onOptionsItemSelected(item);
}
}

Ojo, el cdigo anterior sera vlido para el men creado mediante XML. Si hubiramos
utilizado
la
implementacin por cdigo tendramos que
sustituir
las
constantes R.id.MnuOpc_ por nuestras constantesMNU_OPC_.
Con esto, hemos conseguido ya un men completamente funcional. Si ejecutamos el
proyecto en el emulador comprobaremos cmo al pulsar el botn de menu del telfono
aparece el men que hemos definido y que al pulsar cada opcin se muestra el mensaje de
ejemplo.
Pasemos ahora a comentar los submens. Un submen no es ms que un men secundario
que se muestra al pulsar una opcin determinada de un men principal. Los submens en
Android se muestran en forma de lista emergente, cuyo ttulo contiene el texto de la opcin
elegida en el men principal. Como ejemplo, vamos a aadir un submen a la Opcin 3 del
ejemplo anterior, al que aadiremos dos nuevas opciones secundarias. Para ello, bastar con
insertar en el XML de men un nuevo elemento <menu>dentro del item correspondiente a
la opcin 3. De esta forma, el XML quedara ahora como sigue:
1
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
2
<item android:id="@+id/MnuOpc1" android:title="Opcion1"
3
android:icon="@android:drawable/ic_menu_preferences"></item>
4
<item android:id="@+id/MnuOpc2" android:title="Opcion2"
5
android:icon="@android:drawable/ic_menu_compass"></item>
6
<item android:id="@+id/MnuOpc3" android:title="Opcion3"
7
android:icon="@android:drawable/ic_menu_agenda">
8
<menu>
9
<item android:id="@+id/SubMnuOpc1"
10
android:title="Opcion 3.1" />
11
<item android:id="@+id/SubMnuOpc2"
12
android:title="Opcion 3.2" />
13
</menu>
14
</item>
15 </menu>
Si volvemos a ejecutar ahora el proyecto y pulsamos la opcin 3 nos aparecer el
correspondiente submen con las dos nuevas opciones aadidas. Lo vemos en la siguiente
imagen:

172

Comprobamos como efectivamente aparecen las dos nuevas opciones en la lista emergente,
y que el ttulo de la lista se corresponde con el texto de la opcin elegida en el men
principal (Opcion3).
Para conseguir esto mismo mediante cdigo procederamos de forma similar a la anterior,
con la nica diferencia de que la opcin de men 3 la aadiremos utilizando el
mtodo addSubMenu() en vez de add(), y guardando una referencia al submenu. Sobre el
submen aadido insertaremos las dos nuevas opciones utilizando una vez ms el
mtodo add(). Vemos cmo quedara:
1
//Alternativa 2
2
menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion1")
3
.setIcon(android.R.drawable.ic_menu_preferences);
4
menu.add(Menu.NONE, MNU_OPC2, Menu.NONE, "Opcion2")
5
.setIcon(android.R.drawable.ic_menu_compass);
6
7
SubMenu smnu = menu.
8
addSubMenu(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion3")
9
.setIcon(android.R.drawable.ic_menu_agenda);
10 smnu.add(Menu.NONE, SMNU_OPC1, Menu.NONE, "Opcion 3.1");
11 smnu.add(Menu.NONE, SMNU_OPC2, Menu.NONE, "Opcion 3.2");
En cuanto a la implementacin de estas opciones de submen no habra diferencia con todo
lo
comentado
anteriormente
ya
que
tambin
se
tratan
desde
el
evento onOptionsItemSelected(), identificndolas por su ID.
Por tanto, con esto habramos terminado de comentar las opciones bsicas a la hora de crear
mens y submenus en nuestras aplicaciones Android. En el siguiente artculo veremos
algunas opciones algo ms avanzadas que, aunque menos frecuentes, puede que nos hagan
falta para desarrollar determinadas aplicaciones.

173

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.
Si necesitis iconos para mostrar en los mens aqu tenis varios enlaces con algunos
gratuitos que podis utilizar en vuestras aplicaciones Android:

http://www.androidicons.com/freebies.php
http://www.glyfx.com/products/free_android2.html

Mens en Android (II): Mens Contextuales


by Sgoliver on 30/03/2011 in Android, Programacin
En el artculo anterior del curso ya vimos cmo crear mens y submens bsicos para
nuestras aplicaciones Android. Sin embargo, existe otro tipo de mens que nos pueden ser
muy tiles en determinados contextos: los mens contextuales. Este tipo de men siempre
va asociado a un control concreto de la pantalla y se muestra al realizar una pulsacin larga
sobre ste. Suele mostrar opciones especficas disponibles nicamente para el elemento
pulsado. Por ejemplo, en un control de tipo lista podramos tener un men contextual que
apareciera al pulsar sobre un elemento concreto de la lista y que permitiera editar su texto o
eliminarlo de la coleccin.
NOTA [Android 3.0 o superior]: a diferencia de los mens de aplicacin, para los que ya
dijimos que entraron en desuso a partir de la versin 3 de Android en favor de la action bar,
los mens contextuales pueden seguir utilizndose sin ningn problema aunque tambin se
recomienda sustituirlos por acciones contextuales en la action bar, pero este tema no lo
trataremos en este artculo.
Pues bien, la creacin y utilizacin de este tipo de mens es muy parecida a lo que ya
vimos para los mens y submens bsicos, pero presentan algunas particularidades que
hacen interesante tratarlos al margen del resto en este nuevo artculo.
Empecemos por un caso sencillo. Vamos a partir de un proyecto nuevo, que ya debe
contener por defecto una etiqueta de texto con un Hello World).
Vamos a aadir en primer lugar un men contextual que aparezca al pulsar sobre la etiqueta
de texto. Para ello, lo primero que vamos a hacer es indicar en el mtodo onCreate() de
nuestra actividad principal que la etiqueta tendr asociado un men contextual. Esto lo
conseguimos con una llamada aregisterForContextMenu():
1
public void onCreate(Bundle savedInstanceState) {
2
super.onCreate(savedInstanceState);
3
setContentView(R.layout.main);
4

174

5
//Obtenemos las referencias a los controles
6
lblMensaje = (TextView)findViewById(R.id.LblMensaje);
7
8
//Asociamos los mens contextuales a los controles
9
registerForContextMenu(lblMensaje);
10 }
A continuacin, igual que hacamos con onCreateOptionsMenu() para los mens bsicos,
vamos a sobreescribir en nuestra actividad el evento encargado de construir los mens
contextuales asociados a los diferentes controles de la aplicacin. En este caso el evento se
llama onCreateContextMenu(), y a diferencia de onCreateOptionsMenu() ste se llama
cada vez que se necesita mostrar un men contextual, y no una sola vez al inicio de la
aplicacin. En este evento actuaremos igual que para los mnus bsicos, inflando el men
XML que hayamos creado con las distintas opciones, o creando a mano el men mediante
el mtodo add() [para ms informacin leer el artculo anterior]. En nuestro ejemplo hemos
definido un men en XML llamado menu_ctx_etiqueta.xml:
1
<?xml version="1.0" encoding="utf-8"?>
2
<menu
3
xmlns:android="http://schemas.android.com/apk/res/android">
4
5
<item android:id="@+id/CtxLblOpc1"
6
android:title="OpcEtiqueta1"></item>
7
<item android:id="@+id/CtxLblOpc2"
8
android:title="OpcEtiqueta2"></item>
9
10 </menu>
Por su parte el evento onCreateContextMenu() quedara de la siguiente forma:
1 @Override
2 public void onCreateContextMenu(ContextMenu menu, View v,
3
ContextMenuInfo menuInfo)
4 {
5
super.onCreateContextMenu(menu, v, menuInfo);
6
7
MenuInflater inflater = getMenuInflater();
8
inflater.inflate(R.menu.menu_ctx_etiqueta, menu);
9 }
Por ltimo, para implementar las acciones a realizar tras pulsar una opcin determinada del
men contextual vamos a implementar el evento onContextItemSelected() de forma
anloga a cmo hacamos con onOptionsItemSelected() para los mens bsicos:
1
@Override
2
public boolean onContextItemSelected(MenuItem item) {
3
4
switch (item.getItemId()) {
5
case R.id.CtxLblOpc1:

175

6
7
8
9
10
11
12
13
14

lblMensaje.setText("Etiqueta: Opcion 1 pulsada!");


return true;
case R.id.CtxLblOpc2:
lblMensaje.setText("Etiqueta: Opcion 2 pulsada!");
return true;
default:
return super.onContextItemSelected(item);
}
}

Con esto, ya tendramos listo nuestro men contextual para la etiqueta de texto de la
actividad principal, y como veis todo es prcticamente anlogo a cmo construimos los
mens y submens bsicos en el artculo anterior. En este punto ya podramos ejecutar el
proyecto en el emulador y comprobar su funcionamiento. Para ello, una vez iniciada la
aplicacin tan slo tendremos que realizar una pulsacin larga sobre la etiqueta de texto. En
ese momento debera aparecer el men contextual, donde podremos seleccionar cualquier
de las dos opciones definidas.
Ahora vamos con algunas particularidades. Los mens contextuales se utilizan a menudo
con controles de tipo lista, lo que aade algunos detalles que conviene mencionar. Para ello
vamos a aadir a nuestro ejemplo una lista con varios datos de muestra y vamos a asociarle
un nuevo men contextual. Modificaremos el layout XML de la ventana principal para
aadir el control ListView y modificaremos el mtodo onCreate() para obtener la referencia
al control, insertar vaios datos de ejemplo, y asociarle un men contextual:
1
public void onCreate(Bundle savedInstanceState) {
2
super.onCreate(savedInstanceState);
3
setContentView(R.layout.main);
4
5
//Obtenemos las referencias a los controles
6
lblMensaje = (TextView)findViewById(R.id.LblMensaje);
7
lstLista = (ListView)findViewById(R.id.LstLista);
8
9
//Rellenamos la lista con datos de ejemplo
10
String[] datos =
11
new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};
12
13
ArrayAdapter<String> adaptador =
14
new ArrayAdapter<String>(this,
15
android.R.layout.simple_list_item_1, datos);
16
17
lstLista.setAdapter(adaptador);
18
19
//Asociamos los mens contextuales a los controles
20
registerForContextMenu(lblMensaje);

176

21
registerForContextMenu(lstLista);
22 }
Como en el caso anterior, vamos a definir en XML otro men para asociarlo a los
elementos de la lista, lo llamaremos menu_ctx_lista:
1
<?xml version="1.0" encoding="utf-8"?>
2
<menu
3
xmlns:android="http://schemas.android.com/apk/res/android">
4
5
<item android:id="@+id/CtxLstOpc1"
6
android:title="OpcLista1"></item>
7
<item android:id="@+id/CtxLstOpc2"
8
android:title="OpcLista2"></item>
9
10 </menu>
Como siguiente paso, y dado que vamos a tener varios mens contextuales en la misma
actividad, necesitaremos modificar el evento onCreateContextMenu() para que se construya
un men distinto dependiendo del control asociado. Esto lo haremos obteniendo el ID del
control al que se va a asociar el men contextual, que se recibe en forma de parmetro
(View v) en el evento onCreateContextMenu(). Utilizaremos para ello una llamada al
mtodo getId() de dicho parmetro:
1
@Override
2
public void onCreateContextMenu(ContextMenu menu, View v,
3
ContextMenuInfo menuInfo)
4
{
5
super.onCreateContextMenu(menu, v, menuInfo);
6
7
MenuInflater inflater = getMenuInflater();
8
9
if(v.getId() == R.id.LblMensaje)
10
inflater.inflate(R.menu.menu_ctx_etiqueta, menu);
11
else if(v.getId() == R.id.LstLista)
12
{
13
AdapterView.AdapterContextMenuInfo info =
14
(AdapterView.AdapterContextMenuInfo)menuInfo;
15
16
menu.setHeaderTitle(
17
lstLista.getAdapter().getItem(info.position).toString());
18
19
inflater.inflate(R.menu.menu_ctx_lista, menu);
20
}
21 }
Vemos cmo en el caso del men para el control lista hemos ido adems un poco ms all,
y hemos personalizado el ttulo del men contextual [mediante setHeaderTitle()] para que

177

muestre el texto del elemento seleccionado en la lista. Para hacer esto nos hace falta saber
la posicin en la lista del elemento seleccionado, algo que podemos conseguir haciendo uso
del ltimo parmetro recibido en el eventoonCreateContextMenu(), llamado menuInfo.
Este parmetro contiene informacin adicional del control que se ha pulsado para mostrar
el men contextual, y en el caso particular del control ListView contiene la posicin del
elemento concreto de la lista que se ha pulsado. Para obtenerlo, convertimos el
parmetromenuInfo a un objeto de tipo AdapterContextMenuInfo y accedemos a su
atributo position tal como vemos en el cdigo anterior.
La respuesta a este nuevo men se realizar desde el mismo evento que el anterior, todo
dentro deonContextItemSelected(). Por tanto, incluyendo las opciones del nuevo men
contextual para la lista el cdigo nos quedara de la siguiente forma:
1
@Override
2
public boolean onContextItemSelected(MenuItem item) {
3
4
AdapterContextMenuInfo info =
5
(AdapterContextMenuInfo) item.getMenuInfo();
6
7
switch (item.getItemId()) {
8
case R.id.CtxLblOpc1:
9
lblMensaje.setText("Etiqueta: Opcion 1 pulsada!");
10
return true;
11
case R.id.CtxLblOpc2:
12
lblMensaje.setText("Etiqueta: Opcion 2 pulsada!");
13
return true;
14
case R.id.CtxLstOpc1:
15
lblMensaje.setText("Lista[" + info.position + "]: Opcion 1 pulsada!");
16
return true;
17
case R.id.CtxLstOpc2:
18
lblMensaje.setText("Lista[" + info.position + "]: Opcion 2 pulsada!");
19
return true;
20
default:
21
return super.onContextItemSelected(item);
22
}
23 }
Como
vemos,
aqu
tambin
utilizamos
la
informacin
del
objeto AdapterContextMenuInfo para saber qu elemento de la lista se ha pulsado, con la
nica diferencia de que en esta ocasin lo obtenemos mediante una llamada al
mtodo getMenuInfo() de la opcin de men (MenuItem) recibida como parmetro.
Si volvemos a ejecutar el proyecto en este punto podremos comprobar el aspecto de nuestro
men contextual al pulsar cualquier elemento de la lista:

178

En Android 4 sera muy similar:

A modo de resumen, en este artculo hemos visto cmo crear mens contextuales asociados
a determinados elementos y controles de nuestra interfaz de la aplicacin. Hemos visto
cmo crear mens bsicos y algunas particularidades que existen a la hora de asociar mens
contextuales a elementos de un control de tipo lista. Para no alargar este artculo
dedicaremos un tercero a comentar algunas opciones menos frecuentes, pero igualmente
tiles, de los mens en Android.
Mens en Android (III): Opciones avanzadas
by Sgoliver on 06/10/2011 in Android, Programacin

179

En los artculos anteriores del curso ya hemos visto cmo crear mens bsicos para nuestras
aplicaciones, tanto mens principales como de tipo contextual. Sin embargo, se nos
quedaron en el tintero un par de temas que tambin nos pueden ser necesarios o interesantes
a la hora de desarrollar una aplicacin. Por un lado veremos los grupos de opciones, y por
otro la actualizacin dinmica de un men segn determinadas condiciones.
Los grupos de opciones son un mecanismo que nos permite agrupar varios elementos de un
men de forma que podamos aplicarles ciertas acciones o asignarles determinadas
caractersticas o funcionalidades de forma conjunta. De esta forma, podremos por ejemplo
habilitar o deshabilitar al mismo tiempo un grupo de opciones de men, o hacer que slo se
pueda seleccionar una de ellas. Lo veremos ms adelante.
Veamos primero cmo definir un grupo de opciones de men. Como ya comentamos,
Android nos permite definir un men de dos formas distintas: mediante un fichero XML, o
directamente a travs de cdigo. Si elegimos la primera opcin, para definir un grupo de
opciones nos basta con colocar dicho grupo dentro de un elemento <group>, al que
asignaremos un ID. Veamos un ejemplo. Vamos a definir un men con 3 opciones
principales, donde la ltima opcin abre un submen con 2 opciones que formen parte de
un grupo. A todas las opciones le asignaremos un ID y un texto, y a las opciones
principales asignaremos adems una imagen.
1
<menu
2
xmlns:android="http://schemas.android.com/apk/res/android">
3
4
<item android:id="@+id/MnuOpc1" android:title="Opcion1"
5
android:icon="@android:drawable/ic_menu_preferences"></item>
6
<item android:id="@+id/MnuOpc2" android:title="Opcion2"
7
android:icon="@android:drawable/ic_menu_compass"></item>
8
<item android:id="@+id/MnuOpc3" android:title="Opcion3"
9
android:icon="@android:drawable/ic_menu_agenda">
10
<menu>
11
<group android:id="@+id/grupo1">
12
13
<item android:id="@+id/SubMnuOpc1"
14
android:title="Opcion 3.1" />
15
<item android:id="@+id/SubMnuOpc2"
16
android:title="Opcion 3.2" />
17
18
</group>
19
</menu>
20 </item>
21
22 </menu>

180

Como vemos, las dos opciones del submen se han incluido dentro de un
elemento <group>. Esto nos permitir ejecutar algunas acciones sobre todas las opciones
del grupo de forma conjunta, por ejemplo deshabilitarlas u ocultarlas:
1 //Deshabilitar todo el grupo
2 mnu.setGroupEnabled(R.id.grupo1, false);
3
4 //Ocultar todo el grupo
5 mnu.setGroupVisible(R.id.grupo1, false);
Adems de estas acciones, tambin podemos modificar el comportamiento de las opciones
del grupo de forma que tan slo se pueda seleccionar una de ellas, o para que se puedan
seleccionar varias. Con esto convertiramos el grupo de opciones de men en el equivalente
a un conjunto de controles RadioButton oCheckBox respectivamente. Esto lo conseguimos
utilizando el atributo android:checkableBehavior del elemento <group>, al que podemos
asignar el valor single (seleccin exclusiva, tipo RadioButton) o all (seleccin mltiple,
tipo CheckBox). En nuestro caso de ejemplo vamos a hacer seleccionable slo una de las
opciones del grupo:
1 <group android:id="@+id/grupo1" android:checkableBehavior="single">
2
3
<item android:id="@+id/SubMnuOpc1"
4
android:title="Opcion 3.1" />
5
<item android:id="@+id/SubMnuOpc2"
6
android:title="Opcion 3.2" />
7
8 </group>
Si optamos por construir el men directamente mediante cdigo debemos utilizar el
mtodosetGroupCheckable() al que pasaremos como parmetros el ID del grupo y el tipo
de seleccin que deseamos (exclusiva o no). As, veamos el mtodo de construccin del
men anterior mediante cdigo:
1
private static final int MNU_OPC1 = 1;
2
private static final int MNU_OPC2 = 2;
3
private static final int MNU_OPC3 = 3;
4
private static final int SMNU_OPC1 = 31;
5
private static final int SMNU_OPC2 = 32;
6
7
private static final int GRUPO_MENU_1 = 101;
8
9
private int opcionSeleccionada = 0;
10
11 //...
12
13 private void construirMenu(Menu menu)
14 {

181

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

menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion1")


.setIcon(android.R.drawable.ic_menu_preferences);
menu.add(Menu.NONE, MNU_OPC2, Menu.NONE, "Opcion2")
.setIcon(android.R.drawable.ic_menu_compass);
SubMenu
smnu
=
menu.addSubMenu(Menu.NONE,
Menu.NONE, "Opcion3")
.setIcon(android.R.drawable.ic_menu_agenda);

MNU_OPC3,

smnu.add(GRUPO_MENU_1, SMNU_OPC1, Menu.NONE, "Opcion 3.1");


smnu.add(GRUPO_MENU_1, SMNU_OPC2, Menu.NONE, "Opcion 3.2");
//Establecemos la seleccin exclusiva para el grupo de opciones
smnu.setGroupCheckable(GRUPO_MENU_1, true, true);
//Marcamos la opcin seleccionada actualmente
if(opcionSeleccionada == 1)
smnu.getItem(0).setChecked(true);
else if(opcionSeleccionada == 2)
smnu.getItem(1).setChecked(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
construirMenu(menu);
return true;
}

Como vemos, al final del mtodo nos ocupamos de marcar manualmente la opcin
seleccionada actualmente, que debemos conservar en algn atributo interno (en mi caso lo
he llamadoopcionSeleccionada) de nuestra actividad. Esta marcacin manual la hacemos
mediante el mtodogetItem() para obtener una opcin determinada del submen y sobre
sta el mtodo setChecked()para establecer su estado. Por qu debemos hacer esto? No
guarda Android el estado de las opciones de menu seleccionables? La respuesta es s, s lo
hace, pero siempre que no reconstruyamos el men entre una visualizacin y otra. Pero no
dijimos que la creacin del men slo se realiza una vez en la primera llamada
a onCreateOptionsMenu()? Tambin es cierto, pero despus veremos cmo tambin es
posible preparar nuestra aplicacin para poder modificar de forma dinmica un men segn
determinadas condiciones, lo que s podra implicar reconstruirlo previamente a cada
visualizacin. En definitiva, si guardamos y restauramos nosotros mismos el estado de las

182

opciones de men seleccionables estaremos seguros de no perder su estado bajo ninguna


circunstancia.
Por supuesto, para mantener el estado de las opciones har falta actualizar el
atributoopcionSeleccionada tras cada pulsacin a una de las opciones. Esto lo haremos
como siempre en el mtodo onOptionItemSelected().
1
@Override
2
public boolean onOptionsItemSelected(MenuItem item) {
3
switch (item.getItemId()) {
4
5
//...
6
//Omito el resto de opciones por simplicidad
7
8
case SMNU_OPC1:
9
opcionSeleccionada = 1;
10
item.setChecked(true);
11
return true;
12
case SMNU_OPC2:
13
opcionSeleccionada = 2;
14
item.setChecked(true);
15
return true;
16
17
//...
18
}
19 }
Con esto ya podramos probar cmo nuestro men funciona de la forma esperada,
permitiendo marcar slo una de las opciones del submen. Si visualizamos y marcamos
varias veces distintas opciones veremos cmo se mantiene correctamente el estado de cada
una de ellas entre diferentes llamadas.

El segundo tema que quera desarrollar en este artculo trata sobre la modificacin dinmica
de un men durante la ejecucucin de la aplicacin de forma que ste sea distinto segun
determinadas condiciones. Supongamos por ejemplo que normalmente vamos a querer
mostrar nuestro men con 3 opciones, pero si tenemos marcada en pantalla una
determinada opcin queremos mostrar en el men una opcin adicional. Cmo hacemos

183

esto si dijimos que el evento onCreateOptionsMenu() se ejecuta una sola vez? Pues esto es
posible
ya
que
adems
del
evento
indicado
existe
otro
llamado onPrepareOptionsMenu() que se ejecuta cada vez que se va a mostrar el men de
la aplicacin, con lo que resulta el lugar ideal para adaptar nuestro men a las condiciones
actuales de la aplicacin.
Para mostrar el funcionamiento de esto vamos a colocar en nuestra aplicacin de ejemplo
un nuevo checkbox (lo llamar en mi caso chkMenuExtendido). Nuestra intencin es que si
este checkbox est marcado el men muestre una cuarta opcin adicional, y en caso
contrario slo muestre las tres opciones ya vistas en los ejemplos anteriores.
En primer lugar prepararemos el mtodo construirMenu() para que reciba un parmetro
adicional que indique si queremos construir un men extendido o no, y slo aadiremos la
cuarta opcin si este parmetro llega activado.
private void construirMenu(Menu menu, boolean extendido)
1
{
2
menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion1")
3
.setIcon(android.R.drawable.ic_menu_preferences);
4
menu.add(Menu.NONE, MNU_OPC2, Menu.NONE, "Opcion2")
5
.setIcon(android.R.drawable.ic_menu_compass);
6
7
SubMenu
smnu
=
menu.addSubMenu(Menu.NONE,
MNU_OPC3,
8
Menu.NONE, "Opcion3")
9
.setIcon(android.R.drawable.ic_menu_agenda);
10
11
smnu.add(GRUPO_MENU_1, SMNU_OPC1, Menu.NONE, "Opcion 3.1");
12
smnu.add(GRUPO_MENU_1, SMNU_OPC2, Menu.NONE, "Opcion 3.2");
13
14
//Establecemos la seleccin exclusiva para el grupo de opciones
15
smnu.setGroupCheckable(GRUPO_MENU_1, true, true);
16
17
if(extendido)
18
menu.add(Menu.NONE, MNU_OPC4, Menu.NONE, "Opcion4")
19
.setIcon(android.R.drawable.ic_menu_camera);
20
21
//Marcamos la opcin seleccionada actualmente
22
if(opcionSeleccionada == 1)
23
smnu.getItem(0).setChecked(true);
24
else if(opcionSeleccionada == 2)
25
smnu.getItem(1).setChecked(true);
26
}
Adems de esto, implementaremos el evento onPrepareOptionsMenu() para que llame a
este mtodo de una forma u otra dependiendo del estado del nuevo checkbox.
1
@Override

184

2
3
4
5
6
7
8
9
10
11
12

public boolean onPrepareOptionsMenu(Menu menu)


{
menu.clear();
if(chkMenuExtendido.isChecked())
construirMenu(menu, true);
else
construirMenu(menu, false);
return super.onPrepareOptionsMenu(menu);
}

Como vemos, en primer lugar debemos resetear el men mediante el mtodo clear() y
posteriormente llamar de nuevo a nuestro mtodo de construccin del men indicando si
queremos un men extendido o no segn el valor de la check.
Si ejecutamos nuevamente la aplicacin de ejemplo, marcamos el checkbox y mostramos la
tecla de men podremos comprobar cmo se muestra correctamente la cuarta opcin
aadida.

Y con esto cerramos ya todos los temas referentes a mens que tena intencin de incluir en
este Curso de Programacin en Android. Espero que sea suficiente para cubrir las
necesidades de muchas de vuestras aplicaciones.

Widgets en Android
Interfaz de usuario en Android: Widgets (I)
by Sgoliver on 23/02/2011 in Android, Programacin

185

En los dos prximos artculos del Curso de Programacin Android vamos a describir cmo
crear un widget de escritorio (home screen widget).
En esta primera parte construiremos un widget esttico (no ser interactivo, ni contendr
datos actualizables, ni responder a eventos) muy bsico para entender claramente la
estructura interna de un componente de este tipo, y en el siguiente artculo completaremos
el ejercicio aadiendo una ventana de configuracin inicial para el widget, aadiremos
algn dato que podamos actualizar periodicamente, y haremos que responda a pulsaciones
del usuario.
Como hemos dicho, en esta primera parte vamos a crear un widget muy bsico, consistente
en un simple marco rectangular negro con un mensaje de texto predeterminado (Mi Primer
Widget). La sencillez del ejemplo nos permitir centrarnos en los pasos principales de la
construccin de un widget Android y olvidarnos de otros detalles que nada tienen que ver
con el tema que nos ocupa (grficos, datos, ). Para que os hagis una idea, ste ser el
aspecto final de nuestro widget de ejemplo:

Los pasos principales para la creacin de un widget Android son los siguientes:
1. Definicin de su interfaz grfica (layout).
2. Configuracin XML del widget (AppWidgetProviderInfo).
3. Implementacin de la funcionalidad del widget (AppWidgetProvider) , especialmente su
evento de actualizacin.
4. Declaracin del widget en el Android Manifest de la aplicacin.
En el primer paso no nos vamos a detener mucho ya que es anlogo a cualquier definicin
de layout de las que hemos visto hasta ahora en el curso. En esta ocasin, la interfaz del
widget estar compuesta nicamente por un par de frames (FrameLayout), uno negro
exterior y uno blanco interior algo ms pequeo para simular el marco, y una etiqueta de

186

texto (TextView) que albergar el mensaje a mostrar. Veamos cmo queda el layout xml,
que para este ejemplo llamaremos miwidget.xml:
1
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
android:layout_width="match_parent"
3
android:layout_height="match_parent"
4
android:background="#000000"
5
android:padding="10dp"
6
android:layout_margin="5dp" >
7
8
<FrameLayout android:id="@+id/frmWidget"
9
android:layout_width="match_parent"
10
android:layout_height="match_parent"
11
android:background="#FFFFFF"
12
android:padding="5dp" >
13
14
<TextView android:id="@+id/txtMensaje"
15
android:layout_width="match_parent"
16
android:layout_height="match_parent"
17
android:textColor="#000000"
18
android:text="@string/mi_primer_widget" />
19
20
</FrameLayout>
21
22 </FrameLayout>
Cabe destacar aqu que, debido a que el layout de los widgets de Android est basado en un
tipo especial de componentes llamados RemoteViews, no es posible utilizar en su interfaz

todos los contenedores y controles que hemos visto en artculos anteriores sino slo unos
pocos bsicos que se indican a continuacin:
Contenedores: FrameLayout, LinearLayout, RelativeLayout y GridLayout (ste ltimo a
partir de Android 4).
Controles: Button, ImageButton, ImageView, TextView, ProgressBar, Chronometer, Analo
gClocky ViewFlipper. A partir de Android 3 tambin podemos utilizar ListView,
GridView, StackView y AdapterViewFlipper, aunque su uso tiene algunas particularidades.
En este artculo no trataremos este ltimo caso, pero si necesitas informacin puedes
empezar por la documentacin oficial sobre el tema.
Aunque la lista de controles soportados no deja de ser curiosa (al menos en mi humilde
opinin), debera ser suficiente para crear todo tipo de widgets.
Como segundo paso del proceso de construccin vamos a crear un nuevo fichero XML
donde definiremos un conjunto de propiedades del widget, como por ejemplo su tamao en
pantalla o su frecuencia de actualizacin. Este XML se deber crear en la
carpeta \res\xml de nuestro proyecto. En nuestro caso de ejemplo lo llamaremos
miwidget_wprovider.xml y tendr la siguiente estructura:

187

1 <?xml version="1.0" encoding="utf-8"?>


2 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
3
android:initialLayout="@layout/miwidget"
4
android:minWidth="110dip"
5
android:minHeight="40dip"
6
android:label="@string/mi_primer_widget"
7
android:updatePeriodMillis="3600000"
8 />
Para nuestro widget estamos definiendo las siguientes propiedades:

initialLayout: referencia al layout XML que hemos creado en el paso anterior.


minWidth: ancho mnimo del widget en pantalla, en dp (density-independent pixels).
minHeight: alto mnimo del widget en pantalla, en dp (density-independent pixels).
label: nombre del widget que semostrar en el men de seleccin de Android.
updatePeriodMillis: frecuencia de actualizacin del widget, en milisegundos.
Existen varias propiedades ms que se pueden definir, por ejemplo el icono de vista previa
del widget (android:previewImage, slo para Android >3.0) o el indicativo de si el widget
ser redimensionable (android:resizeMode, slo para Android >3.1) o la actividad de
configuracin del widget (android:configure). En el siguiente artculo utilizaremos alguna
de ellas, el resto se pueden consultar en la documentacin oficial de la clase
AppWidgetProviderInfo.
Como sabemos, la pantalla inicial de Android se divide en un mnimo de 44 celdas (segn
el dispositivo pueden ser ms) donde se pueden colocar aplicaciones, accesos directos y
widgets. Teniendo en cuenta las diferentes dimensiones de estas celdas segn el dispositivo
y la orientacin de la pantalla, existe una frmula sencilla para ajustar las dimensiones de
nuestro widget para que ocupe un nmero determinado de celdas sea cual sea la
orientacin:

ancho_mnimo = (num_celdas * 70) 30


alto_mnimo = (num_celdas * 70) 30
Atendiendo a esta frmula, si queremos que nuestro widget ocupe por ejemplo un tamao
mnimo de 2 celdas de ancho por 1 celda de alto, deberemos indicar unas dimensiones de
110dp x 40dp.
Vamos ahora con el tercer paso. ste consiste en implementar la funcionalidad de nuestro
widget en su clase java asociada. Esta clase deber heredar de AppWidgetProvider, que a
su vez no es ms que una clase auxiliar derivada de BroadcastReceiver, ya que los widgets
de Android no son ms que un caso particular de este tipo de componentes.
En esta clase deberemos implementar los mensajes a los que vamos a responder desde
nuestro widget, entre los que destacan:

188

onEnabled(): lanzado cuando se crea la primera instancia de un widget.


onUpdate(): lanzado periodicamente cada vez que se debe actualizar un widget, por
ejemplo cada vez que se cumple el periodo de tiempo definido por el
parmetro updatePeriodMillis antes descrito, o cuando se aade el widget al escritorio.
onDeleted(): lanzado cuando se elimina del escritorio una instancia de un widget.
onDisabled(): lanzado cuando se elimina del escritorio la ltima instancia de un widget.
En la mayora de los casos, tendremos que implementar como mnimo el
evento onUpdate(). El resto de mtodos dependern de la funcionalidad de nuestro widget.
En nuestro caso particular no nos har falta ninguno de ellos ya que el widget que estamos
creando no contiene ningn dato actualizable, por lo que crearemos la clase,
llamada MiWidget, pero dejaremos vaco por el momento el mtodo onUpdate(). En el
siguiente artculo veremos qu cosas podemos hacer dentro de estos mtodos.
1
package net.sgoliver.android.widgets;
2
3
import android.appwidget.AppWidgetManager;
4
import android.appwidget.AppWidgetProvider;
5
import android.content.Context;
6
7
public class MiWidget extends AppWidgetProvider {
8
@Override
9
public void onUpdate(Context context,
10
AppWidgetManager appWidgetManager,
11
int[] appWidgetIds) {
12
//Actualizar el widget
13
//...
14
}
15 }
El ltimo paso del proceso ser declarar el widget dentro del manifest de nuestra aplicacin.
Para ello, editaremos el fichero AndroidManifest.xml para incluir la siguiente declaracin
dentro del elemento<application>:
<application>
1
...
2
<receiver android:name=".MiWidget" android:label="Mi Primer Widget">
3
<intent-filter>
4
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"
5
/>
6
</intent-filter>
7
<meta-data
8
android:name="android.appwidget.provider"
9
android:resource="@xml/miwidget_wprovider" />
10
</receiver>
11
</application>

189

El widget se declarar como un elemento <receiver> y deberemos aportar la siguiente


informacin:
Atributo name: Referencia a la clase java de nuestro widget, creada en el paso anterior.
Elemento <intent-filter>, donde indicaremos los eventos a los que responder nuestro
widget, normalmente aadiremos el evento APPWIDGET_UPDATE, para detectar la
accin de actualizacin.
Elemento <meta-data>, donde haremos referencia con su atributo resource al XML de
configuracin que creamos en el segundo paso del proceso.
Con esto habramos terminado de escribir los distintos elementos necesarios para hacer
funcionar nuestro widget bsico de ejemplo. Para probarlo, podemos ejecutar el proyecto de
Eclipse en el emulador de Android, esperar a que se ejecute la aplicacin principal (que
estar vaca, ya que no hemos incluido ninguna funcionalidad para ella), ir a la pantalla
principal del emulador y aadir nuestro widget al escritorio tal cmo lo haramos en nuestro
dispositivo fsico:

En Android 2: pulsacin larga sobre el escritorio o tecla Men, seleccionar la opcin


Widgets, y por ltimo seleccionar nuestro Widget.
En Android 4: accedemos al men principal, pulsamos la pestaa Widgets, buscamos el
nuestro en la lista y realizamos sobre l una pulsacin larga hasta que el sistema nos deja
arrastrarlo y colocarlo sobre el escritorio.
Con esto ya hemos conseguido la funcionalidad bsica de un widget, es posible aadir
varias instancias al escritorio, desplazarlos por la pantalla y eliminarlos envindolos a la
papelera.
En el prximo artculo veremos cmo podemos mejorar este widget aadiendo una pantalla
de configuracin inicial, mostraremos algn dato que se actualice peridicamente, y
aadiremos la posibilidad de capturar eventos de pulsacin sobre el widget.
Interfaz de usuario en Android: Widgets (II)
by Sgoliver on 17/03/2011 in Android, Programacin
En un artculo anterior del curso ya vimos cmo construir un widget bsico para Android, y
prometimos que dedicaramos un artculo adicional a comentar algunas caractersticas ms
avanzadas de este tipo de componentes. Pues bien, en este segundo artculo sobre el tema

vamos a ver cmo podemos aadir los siguientes elementos y funcionalidades al widget
bsico que ya construmos:
Pantalla de configuracin inicial.
Datos actualizables de forma periodica.
Eventos de usuario.

190

Como sabis, intento simplificar al mximo todos los ejemplos que utilizo en este curso
para que podamos centrar nuestra atencin en los aspectos realmente importantes. En esta
ocasin utilizar el mismo criterio y las nicas caractersticas (aunque suficientes para
demostrar los tres conceptos anteriores) que aadiremos a nuestro widget sern las
siguientes:
1. Aadiremos una pantalla de configuracin inicial del widget, que aparecer cada vez que se
aada una nueva instancia del widget a nuestro escritorio. En esta pantalla podr
configurarse nicamente el mensaje de texto a mostrar en el widget.
2. Aadiremos un nuevo elemento de texto al widget que muestre la hora actual. Esto nos
servir para comprobar que el widget se actualiza periodicamente.
3. Aadiremos un botn al widget, que al ser pulsado forzar la actualizacin inmediata del
mismo.
Empecemos por el primer punto, la pantalla de configuracin inicial del widget. Y
procederemos igual que para el diseo de cualquier otra actividad android, definiendo su
layout xml. En nuestro caso ser muy sencilla, un cuadro de texto para introducir el
mensaje a personalizar y dos botones, uno para aceptar la configuracin y otro para
cancelar (en cuyo caso el widget no se aade al escritorio). En esta ocasin llamaremos a
este layout widget_config.xml. Veamos como queda:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView android:id="@+id/LblMensaje"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/mensaje_personalizado" />
<EditText android:id="@+id/TxtMensaje"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<Button android:id="@+id/BtnAceptar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/aceptar" />

191

<Button android:id="@+id/BtnCancelar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancelar" />
</LinearLayout>
</LinearLayout>
Una vez diseada la interfaz de nuestra actividad de configuracin tendremos que
implementar su funcionalidad en java. Llamaremos a la clase WidgetConfig, su estructura
ser anloga a la de cualquier actividad de Android, y las acciones a realizar sern las
comentadas a continuacin. En primer lugar nos har falta el identificador de la instancia
concreta del widget que se configurar con esta actividad. Este ID nos llega como
parmetro del intent que ha lanzado la actividad. Como ya vimos en un artculo anterior del
curso, este intent se puede recuperar mediante el mtdo getIntent() y sus parmetros
mediante el mtodo getExtras(). Conseguida la lista de parmetros del intent, obtendremos
el
valor
del
ID
del
widget
accediendo
a
la
clave AppWidgetManager.EXTRA_APPWIDGET_ID. Veamos el cdigo hasta este
momento:
public class WidgetConfig extends Activity {
private int widgetId = 0;
private Button btnAceptar;
private Button btnCancelar;
private EditText txtMensaje;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.widget_config);
//Obtenemos el Intent que ha lanzado esta ventana
//y recuperamos sus parmetros
Intent intentOrigen = getIntent();
Bundle params = intentOrigen.getExtras();
//Obtenemos el ID del widget que se est configurando
widgetId = params.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);

192

//Establecemos el resultado por defecto (si se pulsa el botn 'Atrs'


//del telfono ser ste el resultado devuelto).
setResult(RESULT_CANCELED);
//...
}
}
En el cdigo tambin podemos ver como aprovechamos este momento para establecer el
resultado por defecto a devolver por la actividad de configuracin mediante el
mtodo setResult(). Esto es importante porque las actividades de configuracin de widgets
deben devolver siempre un resultado (RESULT_OK en caso de aceptarse la configuracin,
o RESULT_CANCELED en caso de salir de la configuracin sin aceptar los cambios).
Estableciendo aqu ya un resultado RESULT_CANCELED por defecto nos aseguramos de
que si el usuario sale de la configuracin pulsando el botn Atrs del telfono no
aadiremos el widget al escritorio, mismo resultado que si pulsramos el botn Cancelar de
nuestra actividad.
Como siguiente paso recuperamos las referencias a cada uno de los controles de la
actividad de configuracin:
//Obtenemos la referencia a los controles de la pantalla
btnAceptar = (Button)findViewById(R.id.BtnAceptar);
btnCancelar = (Button)findViewById(R.id.BtnCancelar);
txtMensaje = (EditText)findViewById(R.id.TxtMensaje);
Por ltimo, implementaremos las acciones de los botones Aceptar y Cancelar. En principio,
el botn Cancelar no tendra por qu hacer nada, tan slo finalizar la actividad mediante
una llamada al mtodofinish() ya que el resultado CANCELED ya se ha establecido por
defecto anteriormente:
//Implementacin del botn "Cancelar"
btnCancelar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//Devolvemos como resultado: CANCELAR (RESULT_CANCELED)
finish();
}
});
En el caso del botn Aceptar tendremos que hacer ms cosas:
1. Guardar de alguna forma el mensaje que ha introducido el usuario.
2. Actualizar manualmente la interfaz del widget segn la configuracin establecida.
3. Devolver el resultado RESULT_OK aportanto adems el ID del widget.
Para el primer punto nos ayudaremos de la API de Preferencias que describimos en
el artculo anterior. En nuestro caso, guardaremos una sla preferencia cuya clave seguir el

193

patrn msg_IdWidget, esto nos permitir distinguir el mensaje configurado para cada
instancia del widget que aadamos a nuestro escritorio de Android.
El segundo paso indicado es necesario debido a que si definimos una actividad de
configuracin para un widget, ser sta la que tenga la responsabilidad de realizar la
primera actualizacin del mismo en caso de ser necesario. Es decir, tras salir de la actividad
de configuracin no se lanzar automticamente el eventoonUpdate() del widget (s se
lanzar posteriormente y de forma peridica segn la configuracin del
parmetro updatePeriodMillis del provider que veremos ms adelante), sino que tendr que
ser la propia actividad quien fuerce la primera actualizacin. Para ello, simplemente
obtendremos una referencia al widget manager de nuestro contexto mediente el
mtodo AppWidgetManager.getInstance() y con esta referencia llamaremos al mtodo
esttico de actualizacin del widgetMiWidget.actualizarWidget(), que actualizar los datos
de todos los controles del widget (lo veremos un poco ms adelante).
Por ltimo, al resultado a devolver (RESULT_OK) deberemos aadir informacin sobre el
ID de nuestro widget. Esto lo conseguimos creando un nuevo Intent que contenga como
parmetro el ID del widget que recuperamos antes y establecindolo como resultado de la
actividad mediante el mtodosetResult(resultado, intent). Por ltimo llamaremos al
mtodo finish() para finalizar la actividad.
Con estas indicaciones, veamos cmo quedara el cdigo del botn Aceptar:
//Implementacin del botn "Aceptar"
btnAceptar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//Guardamos el mensaje personalizado en las preferencias
SharedPreferences prefs =
getSharedPreferences("WidgetPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("msg_" + widgetId, txtMensaje.getText().toString());
editor.commit();
//Actualizamos el widget tras la configuracin
AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(WidgetConfig.this);
MiWidget.actualizarWidget(WidgetConfig.this, appWidgetManager, widgetId);
//Devolvemos como resultado: ACEPTAR (RESULT_OK)
Intent resultado = new Intent();
resultado.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
setResult(RESULT_OK, resultado);
finish();
}

194

});
Ya hemos terminado de implementar nuestra actividad de configuracin. Pero para su
correcto funcionamiento an nos quedan dos detalles ms por modificar. En primer lugar
tendremos que declarar esta actividad en nuestro fichero AndroidManifest.xml, indicando
que debe responder a los mensajes de tipo APPWIDGET_CONFIGURE:
<activity android:name=".WidgetConfig">
<intent-filter>
<action android:name="android.apwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
Por ltimo, debemos indicar en el XML de configuracin de nuestro widget
(xml\miwidget_wprovider.xml) que al aadir una instancia de este widget debe mostrarse la
actividad de configuracin que hemos creado. Esto se consigue estableciendo el
atributo android:configure del provider. Aprovecharemos adems este paso para establecer
el tiempo de actualizacin automtica del widget al mnimo permitido por este parmetro
(30 minutos) y el tamao del widget a 32 celdas. Veamos cmo quedara finalmente:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/miwidget"
android:minWidth="180dip"
android:minHeight="110dip"
android:label="@string/mi_primer_widget"
android:updatePeriodMillis="3600000"
android:configure="net.sgoliver.android.widgets.WidgetConfig"
/>
Con esto, ya tenemos todo listo para que al aadir nuestro widget al escritorio se muestre
automticamente la pantalla de configuracin que hemos construido. Podemos ejecutar el
proyecto en este punto y comprobar que todo funciona correctamente.
Como siguiente paso vamos a modificar el layout del widget que ya construimos en el
artculo anterior para aadir una nueva etiqueta de texto donde mostraremos la hora actual,
y un botn que nos servir para forzar la actualizacin de los datos del widget:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:padding="10dp"
android:layout_margin="5dp" >
<LinearLayout android:id="@+id/FrmWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"

195

android:background="#FFFFFF"
android:padding="5dp"
android:orientation="vertical">
<TextView android:id="@+id/LblMensaje"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:text="" />
<TextView android:id="@+id/LblHora"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:text="" />
<Button android:id="@+id/BtnActualizar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:text="@string/actualizar" />
</LinearLayout>
</FrameLayout>
Hecho esto, tendremos que modificar la implementacin de nuestro provider
(MiWidget.java) para que en cada actualizacin del widget se actualicen sus controles con
los datos correctos (recordemos que en el artculo anterior dejamos este evento de
actualizacin vaco ya que no mostrbamos datos actualizables en el widget). Esto lo
haremos dentro del evento onUpdate() de nuestro provider.
Como ya dijimos, los componentes de un widget se basan en un tipo especial de vistas que
llamamos Remote Views. Pues bien, para acceder a la lista de estos componentes que
constituyen la interfaz del widget construiremos un nuevo objeto RemoteViews a partir del
ID del layout del widget. Obtenida la lista de componentes, tendremos disponibles una serie
de mtodos set (uno para cada tipo de datos bsicos) para establecer las propiedades de
cada control del widget. Estos mtodos reciben como parmetros el ID del control, el
nombre del mtodo que queremos ejecutar sobre el control, y el valor a establecer. Adems
de estos mtodos, contamos adicionalmente con una serie de mtodos ms especficos para
establecer directamente el texto y otras propiedades sencillas de los
controles TextView, ImageView, ProgressBar yChronometer,
como
por
ejemplo setTextViewText(idControl, valor) para establecer el textode un control TextView.

196

Pueden consultarse todos los mtodos disponibles en la documentacin oficial de la


clase RemoteViews. De esta forma, si por ejemplo queremos establecer el texto del control
cuyo id esLblMensaje haramos lo siguiente:
RemoteViews
controles
=
new
RemoteViews(context.getPackageName(),
R.layout.miwidget);
controles.setTextViewText(R.id.LblMensaje, "Mensaje de prueba");
El proceso de actualizacin habr que realizarlo por supuesto para todas las instancias del
widget que se hayan aadido al escritorio. Recordemos aqu que el
evento onUpdate() recibe como parmetro la lista de widgets que hay que actualizar.
Dicho esto, creo que ya podemos mostrar cmo quedara el cdigo de actualizacin de
nuestro widget:
@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
//Iteramos la lista de widgets en ejecucin
for (int i = 0; i < appWidgetIds.length; i++)
{
//ID del widget actual
int widgetId = appWidgetIds[i];
//Actualizamos el widget actual
actualizarWidget(context, appWidgetManager, widgetId);
}
}
public static void actualizarWidget(Context context,
AppWidgetManager appWidgetManager, int widgetId)
{
//Recuperamos el mensaje personalizado para el widget actual
SharedPreferences prefs =
context.getSharedPreferences("WidgetPrefs", Context.MODE_PRIVATE);
String mensaje = prefs.getString("msg_" + widgetId, "Hora actual:");
//Obtenemos la lista de controles del widget actual
RemoteViews controles =
new RemoteViews(context.getPackageName(), R.layout.miwidget);
//Actualizamos el mensaje en el control del widget
controles.setTextViewText(R.id.LblMensaje, mensaje);
//Obtenemos la hora actual

197

Calendar calendario = new GregorianCalendar();


String hora = calendario.getTime().toLocaleString();
//Actualizamos la hora en el control del widget
controles.setTextViewText(R.id.LblHora, hora);
//Notificamos al manager de la actualizacin del widget actual
appWidgetManager.updateAppWidget(widgetId, controles);
}
Como vemos, todo el trabajo de actualzacin para un widget lo hemos extraido a un mtodo
esttico independiente, de forma que tambin podamos llamarlo desde otras partes de la
aplicacin (como hacemos por ejemplo desde la actividad de configuracin para forzar la
primera actualizacin del widget).
Adems quiero destacar la ltima linea del cdigo, donde llamamos al
mtodo updateAppWidget() delwidget manager. Esto es importante y necesario, ya que de
no hacerlo la actualizacin de los controles no se reflejar correctamente en la interfaz del
widget.
Tras esto, ya slo nos queda implementar la funcionalidad del nuevo botn que hemos
incluido en el widget para poder forzar la actualizacin del mismo. A los controles
utilizados en los widgets de Android, que ya sabemos que son del tipo RemoteView, no
podemos asociar eventos de la forma tradicional que hemos visto en mltiples ocasiones
durante el curso. Sin embargo, en su lugar, tenemos la posibilidad de asociar a un evento
(por ejemplo, el click sobre un botn) un determinado mensaje (Pending Intent) de
tipobroadcast que ser lanzado cada vez que se produzca dicho evento. Adems, podremos
configurar el widget (que como ya indicamos no es ms que un componente de
tipo broadcast receiver) para que capture esos mensajes, e implementar en su
evento onReceive() las acciones necesarias a ejecutar tras capturar el mensaje. Con estas
tres acciones simularemos la captura de eventos sobre controles de un widget.
Vamos por partes. En primer lugar hagamos que se lance un intent de tipo broadcast cada
vez
que
se
pulse
el
botn
del
widget.
Para
ello,
en
el
mtodo actualizarWidget() construiremos un nuevo Intentasocindole una accin
personalizada,
que
en
nuestro
caso
llamaremos
por
ejemplo
net.sgoliver.android.widgets.ACTUALIZAR_WIDGET.
Como
parmetro
del
nuevo Intentinsertaremos mediante putExtra() el ID del widget actual de forma que ms
tarde podamos saber el widget concreto que ha lanzado el mensaje (recordemos que
podemos tener varias instancias del mismo widget en el escritorio). Por ltimo crearemos
el PendingIntent mediante el mtodo getBroadcast() y lo asociaremos al evento onClick del

198

control llamando a setOnClickPendingIntent() pasndole el ID del control, en nuestro caso


el botn de Actualizar. Veamos cmo queda todo esto dentro del
mtodoactualizarWidget():
Intent intent = new Intent("net.sgoliver.android.widgets.ACTUALIZAR_WIDGET");
intent.putExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
PendingIntent pendingIntent =
PendingIntent.getBroadcast(context, widgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
controles.setOnClickPendingIntent(R.id.BtnActualizar, pendingIntent);
Tambin podemos hacer por ejemplo que si pulsamos en el resto del espacio del widget (el
no ocupado por el botn) se abra automticamente la actividad principal de nuestra
aplicacin. Se hara de forma anloga, con la nica diferencia que en vez de
utilizar getBroadcast() utilizaramos getActivity() y el Intent lo construiramos a partir de la
clase de la actividad principal:
Intent intent2 = new Intent(context, MainActivity.class);
PendingIntent pendingIntent2 =
PendingIntent.getActivity(context, widgetId,
intent2, PendingIntent.FLAG_UPDATE_CURRENT);
controles.setOnClickPendingIntent(R.id.FrmWidget, pendingIntent2);
Ahora vamos a declarar en el Android Manifest este mensaje personalizado, de forma que
el widget sea capaz de capturarlo. Para ello, aadiremos simplemente un nuevo
elemento <intent-filter> con nuestro nombre de accin personalizado dentro del
componente <receiver> que ya tenamos definido:
<receiver android:name=".MiWidget" android:label="Mi Primer Widget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<intent-filter>
<action android:name="net.sgoliver.android.widgets.ACTUALIZAR_WIDGET"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/miwidget_wprovider" />
</receiver>
Por ltimo, vamos a implementar el evento onReceive() del widget para actuar en caso de
recibir nuestro mensaje de actualizacin personalizado. Dentro de este evento
comprobaremos si la accin del menasje recibido es la nuestra, y en ese caso recuperaremos

199

el ID del widget que lo ha lanzado, obtendremos una referencia al widget manager, y por
ltimo llamaremos a nuestro mtodo esttico de actualizacin pasndole estos datos.
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("net.sgoliver.android.widgets.ACTUALIZAR_WIDGET"))
{
//Obtenemos el ID del widget a actualizar
int widgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
//Obtenemos el widget manager de nuestro contexto
AppWidgetManager widgetManager =
AppWidgetManager.getInstance(context);
//Actualizamos el widget
if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
actualizarWidget(context, widgetManager, widgetId);
}
}
Con esto, por fin, hemos ya finalizado la construccin de nuestro widget android y
podemos ejecutar el proyecto de Eclipse para comprobar que todo funciona correctamente,
tanto para una sola instancia como para varias instancias simultaneas.
Cuando aadamos el widget al escritorio nos aparecer la pantalla de configuracin que
hemos definido:

200

Una vez introducido el mensaje que queremos mostrar, pulsaremos el botn Aceptar y el
widget aparecer automticamente en el escritorio con dicho mensaje, la fecha-hora actual
y el botn Actualizar.

Un comentario final, la actualizacin automtica del widget se ha establecido a la


frecuencia mnima que permite el atributo updatePeriodMillis del widget provider, que son
30 minutos. Por tanto es dificil y aburrido esperar para verla en funcionamiento mientras
probamos el widget en el emulador. Pero funciona, os lo aseguro. De cualquier forma, esos
30 minutos pueden ser un periodo demasiado largo de tiempo segn la funcionalidad que
queramos dar a nuestro widget, que puede requerir tiempos de actualizacin mucho ms
cortos (ojo con el rendimiento y el gasto de batera). Ms adelante, cuando hablemos
deAlarmas, veremos una tcnica que nos permitir actualizar los widgets sin esa limitacin
de 30 minutos. Hasta entonces, espero que el tema os sea de utilidad y que os haya parecido
interesante.

Gestin de Preferencias en Android


Preferencias en Android I: Shared Preferences
by Sgoliver on 14/03/2011 in Android, Programacin
En el artculo anterior del Curso de Programacin en Android vimos como construir un
widget bsico y prometimos dedicar un segundo artculo a comentar otras funcionalidades
ms avanzadas de este tipo de componentes. Sin embargo, antes de esto he decidido hacer
un pequeo alto en el camino para hablar de un tema que nos ser de ayuda ms adelante, y

201

no slo para la construccin de widgets, sino para cualquier tipo de aplicacin Android.
Este tema es la administracin de preferencias.
Las preferencias no son ms que datos que una aplicacin debe guardar para personalizar la
experiencia del usuario, por ejemplo informacin personal, opciones de presentacin, etc.
En artculos anteriores vimos ya uno de los mtodos disponibles en la plataforma Android
para almacenar datos, como son las bases de datos SQLite. Las preferencias de una
aplicacin se podran almacenar por su puesto utilizando este mtodo, y no tendra nada de
malo, pero Android proporciona otro mtodo alternativo diseado especficamente para
administrar este tipo de datos: las preferencias compartidas o shared preferences. Cada
preferencia se almacenar en forma de clave-valor, es decir, cada una de ellas estar
compuesta por un identificador nico (p.e. email) y un valor asociado a dicho
identificador (p.e. prueba@email.com). Adems, y a diferencia de SQLite, los datos no
se guardan en un fichero binario de base de datos, sino en ficheros XML como veremos al
final de este artculo.
La API para el manejo de estas preferencias es muy sencilla. Toda la gestin se centraliza
en la claseSharedPrefences, que representar a una coleccin de preferencias. Una
aplicacin Android puede gestionar varias colecciones de preferencias, que se diferenciarn
mediante un identificador nico. Para obtener una referencia a una coleccin determinada
utilizaremos el mtodo getSharedPrefences() al que pasaremos el identificador de la
coleccin y un modo de acceso. El modo de acceso indicar qu aplicaciones tendrn
acceso a la coleccin de preferencias y qu operaciones tendrn permitido realizar sobre

ellas. As, tendremos tres posibilidades principales:


MODE_PRIVATE. Slo nuestra aplicacin tiene acceso a estas preferencias.
MODE_WORLD_READABLE. Todas las aplicaciones pueden leer estas preferencias,
pero slo la nuestra puede modificarlas.
MODE_WORLD_WRITABLE. Todas las aplicaciones pueden leer y modificar estas
preferencias.
Las dos ltimas opciones son relativamente peligrosas por lo que en condiciones
normales no deberan usarse. De hecho, se han declarado como obsoletas en la API 17
(Android 4.2).
Teniedo todo esto en cuenta, para obtener una referencia a una coleccin de preferencias
llamada por ejemplo MisPreferencias y como modo de acceso exclusivo para nuestra
aplicacin haramos lo siguiente:
SharedPreferences prefs =
getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);

202

Una vez hemos obtenido una referencia a nuestra coleccin de preferencias, ya podemos
obtener, insertar o modificar preferencias utilizando los mtodos get o put correspondientes
al tipo de dato de cada preferencia. As, por ejemplo, para obtener el valor de una
preferencia llamada email de tipo Stringescribiramos lo siguiente:
SharedPreferences prefs =
getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
String correo = prefs.getString("email", "por_defecto@email.com");
Como vemos, al mtodo getString() le pasamos el nombre de la preferencia que queremos
recuperar y un segundo parmetro con un valor por defecto. Este valor por defecto ser el
devuelto por el mtodogetString() si la preferencia solicitada no existe en la coleccin.
Adems del mtodo getString(), existen por supuesto mtodos anlogos para el resto de
tipos de datos bsicos, por ejemplo getInt(),getLong(), getFloat(), getBoolean(),
Para actualizar o insertar nuevas preferencias el proceso ser igual de sencillo, con la nica
diferencia de que la actualizacin o insercin no la haremos directamente sobre el
objeto SharedPreferences, sino sobre su objeto de edicin SharedPreferences.Editor. A este
ltimo objeto accedemos mediante el mtodo edit() de la clase SharedPreferences. Una vez
obtenida la referencia al editor, utilizaremos los mtodos put correspondientes al tipo de
datos de cada preferencia para actualizar/insertar su valor, por ejemplo putString(clave,
valor), para actualizar una preferencia de tipo String. De forma anloga a los
mtodos get que ya hemos visto, tendremos disponibles mtodos put para todos los tipos de
datos
bsicos: putInt(), putFloat(), putBoolean(),
etc.
Finalmente,
una
vez
actualizados/insertados todos los datos necesarios llamaremos al mtodo commit() para
confirmar los cambios. Veamos un ejemplo sencillo:
SharedPreferences prefs =
getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("email", "modificado@email.com");
editor.putString("nombre", "Prueba");
editor.commit();
Pero donde se almacenan estas preferencias compartidas? Como dijimos al comienzo del
artculo, las preferencias no se almacenan en ficheros binarios como las bases de datos
SQLite, sino en ficheros XML. Estos ficheros XML se almacenan en una ruta que sigue el
siguiente patrn:
/data/data/paquete.java/shared_prefs/nombre_coleccion.xml
As, por ejemplo, en nuestro caso encontraramos nuestro fichero de preferencias en la ruta:

203

/data/data/net.sgoliver.android.preferences1/shared_prefs/MisPreferencias.xml
Sirva una imagen del explorador de archivos del DDMS como prueba:

Si descargamos este fichero desde el DDMS y lo abrimos con cualquier editor de texto
veremos un contenido como el siguiente:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="nombre">prueba</string>
<string name="email">modificado@email.com</string>
</map>
En este XML podemos observar cmo se han almacenado las dos preferencias de ejemplo
que insertamos anteriormente, con sus claves y valores correspondientes.
Y nada ms, as de fcil y prctico. Con esto hemos aprendido una forma sencilla de
almacenar determinadas opciones de nuestra aplicacin sin tener que recurrir para ello a
definir bases de datos SQLite, que aunque tampoco aaden mucha dificultad s que
requieren algo ms de trabajo por nuestra parte.
Se aporta una pequea aplicacin de ejemplo para este artculo que tan slo incluye dos
botones, el primero de ellos para guardar las preferencias tal como hemos descrito, y el
segundo para recuperarlas y mostrarlas en el log.
En una segunda parte de este tema dedicado a las preferencias veremos cmo Android nos
ofrece otra forma de gestionar estos datos, que se integra adems fcilmente con la interfaz
grfica necesaria para solicitar los datos al usuario.
Preferencias en Android II: PreferenceActivity
by Sgoliver on 13/10/2011 in Android, Programacin
Ya hemos visto durante el curso algn artculo dedicado a las preferencias
compartidas (shared preferences), un mecanismo que nos permite gestionar fcilmente las
opciones de una aplicacin permitindonos guardarlas en XML de una forma transparente
para el programador. En aquel momento slo vimos cmo hacer uso de ellas mediante

204

cdigo, es decir, creando nosotros mismos los objetos necesarios (SharedPreferences) y


aadiendo, modificando y/o recuperando a mano los valores de las opciones a travs de
los mtodos correspondientes (getString(), putString(), ). Sin embargo, ya avisamos de
que Android ofrece una forma alternativa de definir mediante XML un conjunto de
opciones para una aplicacin y crear por nosotros las pantallas necesarias para permitir al
usuario modificarlas a su antojo. A esto dedicaremos este segundo artculo sobre
preferencias.
Si nos fijamos en cualquier pantalla de preferencias estandar de Android veremos que todas
comparten una interfaz comun, similar por ejemplo a las que se muestran en las imgenes
siguientes para Android 2.x y 4.x respectivamente:

Si atendemos por ejemplo a la primera imagen vemos cmo las diferentes opciones se
organizan dentro de la pantalla de opciones en varias categoras (General Settings y
Slideshow Settings). Dentro de cada categora pueden aparecer varias opciones de
diversos tipos, como por ejemplo de tipo checkbox (Confirm deletions) o de tipo lista de
seleccin (Display size). He resaltado las palabras pantalla de opciones, categoras, y
tipos de opcin porque sern estos los tres elementos principales con los que vamos a
definir el conjunto de opciones o preferencias de nuestra aplicacin. Empecemos.
Como hemos indicado, nuestra pantalla de opciones la vamos a definir mediante un XML,
de forma similar a como definimos cualquier layout, aunque en este caso deberemos
colocarlo en la carpeta /res/xml. El contenedor principal de nuestra pantalla de preferencias
ser el elemento <PreferenceScreen>. Este elemento representar a la pantalla de opciones
en s, dentro de la cual incluiremos el resto de elementos. Dentro de ste podremos incluir

205

nuestra lista de opciones organizadas por categoras, que se representarn mediante el


elemento <PreferenceCategory> al que daremos un texto descriptivo utilizando su
atributo android:title. Dentro de cada categora podremos aadir cualquier nmero de

opciones, las cuales pueden ser de distintos tipos, entre los que destacan:
CheckBoxPreference. Marca seleccionable.
EditTextPreference. Cadena simple de texto.
ListPreference. Lista de valores seleccionables (exclusiva).
MultiSelectListPreference. Lista de valores seleccionables (mltiple).
Cada uno de estos tipos de preferencia requiere la definicin de diferentes atributos, que
iremos viendo en los siguientes apartados.

CheckBoxPreference
Representa un tipo de opcin que slo puede tomar dos valores distintos: activada o
desactivada. Es el equivalente a un control de tipo checkbox. En este caso tan slo
tendremos que especificar los atributos: nombre interno de la opcin (android:key), texto a
mostrar (android:title) y descripcin de la opcin (android:summary). Veamos un ejemplo:
<CheckBoxPreference
android:key="opcion1"
android:title="Preferencia 1"
android:summary="Descripcin de la preferencia 1" />

EditTextPreference
Representa un tipo de opcin que puede contener como valor una cadena de texto. Al pulsar
sobre una opcin de este tipo se mostrar un cuadro de dilogo sencillo que solicitar al
usuario el texto a almacenar. Para este tipo, adems de los tres atributos comunes a todas
las opciones (key, title y summary) tambin tendremos que indicar el texto a mostrar en el
cuadro de dilogo, mediante el atributoandroid:dialogTitle. Un ejemplo sera el siguiente:
<EditTextPreference
android:key="opcion2"
android:title="Preferencia 2"
android:summary="Descripcin de la preferencia 2"
android:dialogTitle="Introduce valor" />

ListPreference
Representa un tipo de opcin que puede tomar como valor un elemento, y slo uno,
seleccionado por el usuario entre una lista de valores predefinida. Al pulsar sobre una
opcin de este tipo se mostrar la lista de valores posibles y el usuario podr seleccionar
uno de ellos. Y en este caso seguimos aadiendo atributos. Adems de los cuatro ya
comentados (key, title, summary y dialogTitle) tendremos que aadir dos ms, uno de ellos

206

indicando la lista de valores a visualizar en la lista y el otro indicando los valores internos
que utilizaremos para cada uno de los valores de la lista anterior (Ejemplo: al usuario
podemos mostrar una lista con los valores Espaol y Francs, pero internamente
almacenarlos como ESP y FRA).
Estas listas de valores las definiremos tambin como ficheros XML dentro de la
carpeta /res/xml. Definiremos para ello los recursos de tipos <string-array> necesarios, en
este caso dos, uno para la lista de valores visibles y otro para la lista de valores internos,
cada uno de ellos con su ID nico correspondiente. Veamos cmo quedaran dos listas de
ejemplo, en un fichero llamado codigospaises.xml:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string-array name="pais">
<item>Espaa</item>
<item>Francia</item>
<item>Alemania</item>
</string-array>
<string-array name="codigopais">
<item>ESP</item>
<item>FRA</item>
<item>ALE</item>
</string-array>
</resources>
En la preferencia utilizaremos los atributos android:entries y android:entryValues para
hacer referencia a estas listas, como vemos en el ejemplo siguiente:
<ListPreference
android:key="opcion3"
android:title="Preferencia 3"
android:summary="Descripcin de la preferencia 3"
android:dialogTitle="Indicar Pais"
android:entries="@array/pais"
android:entryValues="@array/codigopais" />
MultiSelectListPreference
[A partir de Android 3.0.x / Honeycomb] Las opciones de este tipo son muy similares a las
ListPreference, con la diferencia de que el usuario puede seleccionar varias de las opciones
de la lista de posibles valores. Los atributos a asignar son por tanto los mismos que para el
tipo anterior.
<MultiSelectListPreference
android:key="opcion4"
android:title="Preferencia 4"
android:summary="Descripcin de la preferencia 4"
android:dialogTitle="Indicar Pais"

207

android:entries="@array/pais"
android:entryValues="@array/codigopais" />
Como ejemplo completo, veamos cmo quedara definida una pantalla de opciones con las
3 primeras opciones comentadas (ya que probar con Android 2.2), divididas en 2
categoras llamadas por simplicidad Categora 1 y Categora 2. Llamaremos al fichero
opciones.xml.
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Categora 1">
<CheckBoxPreference
android:key="opcion1"
android:title="Preferencia 1"
android:summary="Descripcin de la preferencia 1" />
<EditTextPreference
android:key="opcion2"
android:title="Preferencia 2"
android:summary="Descripcin de la preferencia 2"
android:dialogTitle="Introduce valor" />
</PreferenceCategory>
<PreferenceCategory android:title="Categora 2">
<ListPreference
android:key="opcion3"
android:title="Preferencia 3"
android:summary="Descripcin de la preferencia 3"
android:dialogTitle="Indicar Pais"
android:entries="@array/pais"
android:entryValues="@array/codigopais" />
</PreferenceCategory>
</PreferenceScreen>
Ya tenemos definida la estructura de nuestra pantalla de opciones, pero an nos queda un
paso ms para poder hacer uso de ella desde nuestra aplicacin. Adems de la definicin
XML de la lista de opciones, debemos implementar una nueva actividad, que ser a la que
hagamos referencia cuando queramos mostrar nuestra pantalla de opciones y la que se
encargar internamente de gestionar todas las opciones, guardarlas, modificarlas, etc, a
partir de nuestra definicin XML.
Android nos facilita las cosas ofrecindonos una clase de la que podemos derivar
facilmente la nuestra propia y que hace casi todo el trabajo por nosotros. Esta clase se
llama PreferenceActivity. Tan slo deberemos crear una nueva actividad (yo la he
llamado OpcionesActivity)

que

extienda

esta

clase,

implementar

su

evento onCreate() para aadir una llamada al mtodoaddPreferencesFromResource(),

208

mediante el que indicaremos el fichero XML en el que hemos definido la pantalla de


opciones. Lo vemos mejor directamente en el cdigo:
public class OpcionesActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.opciones);
}
}
As de sencillo, nuestra nueva actividad, al extender a PreferenceActivity, se encargar por
nosotros de crear la interfaz grfica de nuestra lista de opciones segn hemos la definido en
el XML y se preocupar por nosotros de mostrar, modificar y guardar las opciones cuando
sea necesario tras la accin del usuario.
Aunque esto contina funcionando sin problemas en versiones recientes de Android, la API
11 trajo consigo una nueva forma de definir las pantallas de preferencias haciendo uso
de fragments. Para ello, basta simplemente con definir la clase java del fragment, que
deber extender de PreferenceFragment y aadir a su mtodo onCreate() una llamada
a addPreferencesFromResource() igual que ya hemos visto antes.
public static class OpcionesFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.opciones);
}
}
Hecho esto ya no ser necesario que la clase de nuestra pantalla de preferencias extienda
dePreferenceActivity, sino que podr ser una actividad normal. Para mostrar el fragment
creado como contenido principal de la actividad utilizaramos el fragment manager para
sustituir el contenido de la pantalla (android.R.id.content) por el de nuestro fragment de
preferencias recin definido:
public class SettingsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new OpcionesFragment())
.commit();
}
}

209

Sea cual se la opcin elegida para definir la pantalla de preferencias, el siguiente paso ser
aadir esta actividad al fichero AndroidManifest.xml, al igual que cualquier otra actividad
que utilicemos en la aplicacin.
<activity android:name=".OpcionesActivity"
android:label="@string/app_name">
</activity>
Ya slo nos queda aadir a nuestra aplicacin algn mecanismo para mostrar la pantalla de
preferencias. Esta opcin suele estar en un men (para Android 2.x) o en el men de
overflow de la action bar (para Android 3 o superior), pero por simplificar el ejemplo
vamos a aadir simplemente un botn (btnPreferencias) que abra la ventana de
preferencias.
Al pulsar este botn llamaremos a la ventana de preferencias mediante el
mtodo startActivity(), como ya hemos visto en alguna ocasin, al que pasaremos como
parmetros el contexto de la aplicacin (nos vale con nuestra actividad principal) y la clase
de la ventana de preferencias (OpcionesActivity.class).
btnPreferencias = (Button)findViewById(R.id.BtnPreferencias);
btnPreferencias.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,
OpcionesActivity.class));
}
});
Y esto es todo, ya slo nos queda ejecutar la aplicacin en el emulador y pulsar el botn de
preferencias para mostrar nuestra nueva pantalla de opciones. Debe quedar como muestran
las imgenes siguientes para Android 2.x y 4.x respectivamente:

210

La primera opcin podemos marcarla o desmarcarla directamente pulsando sobre la check


de su derecha. La segunda, de tipo texto, nos mostrar al pulsarla un pequeo formulario
para solicitar el valor de la opcin.

211

Por ltimo, la opcin 3 de tipo lista, nos mostrar una ventana emergente con la lista de
valores posibles, donde podremos seleccionar slo uno de ellos.

Una vez establecidos los valores de las preferencias podemos salir de la ventana de
opciones simplemente pulsando el botn Atrs del dispositivo o del emulador. Nuestra
actividad OpcionesActivity se habr ocupado por nosotros de guardar correctamente los
valores de las opciones haciendo uso de la API de preferencias compartidas (Shared
Preferences). Y para comprobarlo vamos a aadir otro botn (btnObtenerOpciones) a la

212

aplicacin de ejemplo que recupere el valor actual de las 3 preferencias y los escriba en el
log de la aplicacin.
La forma de acceder a las preferencias compartidas de la aplicacin ya la vimos en
el artculo anteriorsobre este tema. Obtenemos la lista de preferencias mediante el
mtodogetDefaultSharedPreferences() y posteriormente utilizamos
los distintos
mtodos get() para recuperar el valor de cada opcin dependiendo de su tipo.
btnObtenerPreferencias.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences pref =
PreferenceManager.getDefaultSharedPreferences(
AndroidPrefScreensActivity.this);
Log.i("", "Opcin 1: " + pref.getBoolean("opcion1", false));
Log.i("", "Opcin 2: " + pref.getString("opcion2", ""));
Log.i("", "Opcin 3: " + pref.getString("opcion3", ""));
}
});
Si ejecutamos ahora la aplicacin, establecemos las preferencias y pulsamos el nuevo botn
de consulta que hemos creado veremos cmo en el log de la aplicacin aparecen los valores
correctos de cada preferencia. Se mostrara algo como lo siguiente:
10-08 09:27:09.681: INFO/(1162): Opcin 1: true
10-08 09:27:09.681: INFO/(1162): Opcin 2: prueba
10-08 09:27:09.693: INFO/(1162): Opcin 3: FRA
Y hasta aqu hemos llegado con el tema de las preferencias, un tema muy interesante de
controlar ya que casi ninguna aplicacin se libra de hacer uso de ellas. Existen otras muchas
opciones de configuracin de las pantallas de preferencias, sobre todo con la llegada de
Android 4, pero con lo que hemos visto aqu podremos cubrir la gran mayora de casos.

Bases de Datos en Android


Bases de Datos en Android (I): Primeros pasos
by Sgoliver on 31/01/2011 in Android, Programacin
En los siguientes artculos de este tutorial de programacin Android, nos vamos a detener
en describir las distintas opciones de acceso a datos que proporciona la plataforma y en
cmo podemos realizar las tareas ms habituales dentro de este apartado.

213

La plataforma Android proporciona dos herramientas pricipales para el almacenamiento y


consulta de datos estructurados:

Bases de Datos SQLite


Content Providers
En estos prximos artculos nos centraremos en la primera opcin, SQLite, que abarcar
todas las tareas relacionadas con el almacenamiento de los datos propios de nuestra
aplicacin. El segundo de los mecanismos, los Content Providers, que trataremos ms
adelante, nos facilitarn la tarea de hacer visibles esos datos a otras aplicaciones y, de
forma recproca, de permitir la consulta de datos publicados por terceros desde nuestra
aplicacin.
SQLite es un motor de bases de datos muy popular en la actualidad por ofrecer
caractersticas tan interesantes como su pequeo tamao, no necesitar servidor, precisar
poca configuracin, sertransaccional y por supuesto ser de cdigo libre.
Android incorpora de serie todas las herramientas necesarias para la creacin y gestin de
bases de datos SQLite, y entre ellas una completa API para llevar a cabo de manera sencilla
todas las tareas necesarias. Sin embargo, en este primer artculo sobre bases de datos en
Android no vamos a entrar en mucho detalle con esta API. Por el momento nos limitaremos
a ver el cdigo necesario para crear una base de datos, insertaremos algn dato de prueba, y
veremos cmo podemos comprobar que todo funciona correctamente.
En Android, la forma tpica para crear, actualizar, y conectar con una base de datos SQLite
ser a travs de una clase auxiliar llamada SQLiteOpenHelper, o para ser ms exactos, de
una clase propia que derive de ella y que debemos personalizar para adaptarnos a las
necesidades concretas de nuestra aplicacin.
La clase SQLiteOpenHelper tiene tan slo un constructor, que normalmente no
necesitaremos sobrescribir, y dos mtodos abstractos, onCreate() y onUpgrade(), que
deberemos personalizar con el cdigo necesario para crear nuestra base de datos y para
actualizar su estructura respectivamente.
Como ejemplo, nosotros vamos a crear una base de datos muy sencilla
llamada BDUsuarios, con una sla tabla llamada Usuarios que contendr slo dos
campos: nombre e email. Para ellos, vamos a crear una clase derivada
de SQLiteOpenHelper que llamaremos UsuariosSQLiteHelper, donde sobrescribiremos los
mtodos onCreate() y onUpgrade() para adaptarlos a la estructura de datos indicada:
1
package net.sgoliver.android.bd;
2
3
import android.content.Context;

214

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

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class UsuariosSQLiteHelper extends SQLiteOpenHelper {
//Sentencia SQL para crear la tabla de Usuarios
String sqlCreate = "CREATE TABLE Usuarios (codigo INTEGER, nombre TEXT)";
public UsuariosSQLiteHelper(Context contexto, String nombre,
CursorFactory factory, int version) {
super(contexto, nombre, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
//Se ejecuta la sentencia SQL de creacin de la tabla
db.execSQL(sqlCreate);
}
@Override
public void onUpgrade(SQLiteDatabase db, int versionAnterior, int versionNueva) {
//NOTA: Por simplicidad del ejemplo aqu utilizamos directamente la opcin de
//
eliminar la tabla anterior y crearla de nuevo vaca con el nuevo formato.
//
Sin embargo lo normal ser que haya que migrar datos de la tabla antigua
//
a la nueva, por lo que este mtodo debera ser ms elaborado.
//Se elimina la versin anterior de la tabla
db.execSQL("DROP TABLE IF EXISTS Usuarios");
//Se crea la nueva versin de la tabla
db.execSQL(sqlCreate);
}
}

Lo primero que hacemos es definir una variable llamado sqlCreate donde almacenamos la
sentencia SQL para crear una tabla llamada Usuarios con los campos
alfanumricos nombre e email. NOTA: No es objetivo de este tutorial describir la sintaxis
del lenguaje SQL ni las particularidades del motor de base de datos SQLite, por lo que no
entrar a describir las sentencias SQL utilizadas. Para ms informacin sobre SQLite
puedes consultar la documentacin oficial o empezar por leer una pequea introduccin que
hice en este mismo blog cuando trat el tema de utilizar SQLite desde aplicaciones .NET
El
mtodo onCreate() ser
ejecutado
automticamente
por
nuestra
clase UsuariosDBHelper cuando sea necesaria la creacin de la base de datos, es decir,
cuando an no exista. Las tareas tpicas que deben hacerse en este mtodo sern la creacin

215

de todas las tablas necesarias y la insercin de los datos iniciales si son necesarios. En
nuestro caso, slo vamos a crear la tabla Usuarios descrita anteriomente. Para la creacin de
la tabla utilizaremos la sentencia SQL ya definida y la ejecutaremos contra la base de datos
utilizando el mtodo ms sencillo de los disponibles en la API de SQLite proporcionada
por Android, llamado execSQL(). Este mtodo se limita a ejecutar directamente el cdigo
SQL que le pasemos como parmetro.
Por su parte, el mtodo onUpgrade() se lanzar automticamente cuando sea necesaria una
actualizacin de la estructura de la base de datos o una conversin de los datos. Un ejemplo
prctico: imaginemos que publicamos una aplicacin que utiliza una tabla con los
campos usuario e email (llammoslo versin 1 de la base de datos). Ms adelante,
ampliamos la funcionalidad de nuestra aplicacin y necesitamos que la tabla tambin
incluya un campo adicional como por ejemplo con la edad del usuario (versin 2 de nuestra
base de datos). Pues bien, para que todo funcione correctamente, la primera vez que
ejecutemos la versin ampliada de la aplicacin necesitaremos modificar la estructura de la
tabla Usuarios para aadir el nuevo campo edad. Pues este tipo de cosas son las que se
encargar de hacer automticamente el mtodo onUpgrade() cuando intentemos abrir una
versin concreta de la base de datos que an no exista. Para ello, como parmetros recibe la
versin actual de la base de datos en el sistema, y la nueva versin a la que se quiere
convertir. En funcin de esta pareja de datos necesitaremos realizar unas acciones u otras.
En nuestro caso de ejemplo optamos por la opcin ms sencilla: borrar la tabla actual y
volver a crearla con la nueva estructura, pero como se indica en los comentarios del cdigo,
lo habitual ser que necesitemos algo ms de lgica para convertir la base de datos de una
versin a otra y por supuesto para conservar los datos registrados hasta el momento.
Una vez definida nuestra clase helper, la apertura de la base de datos desde nuestra
aplicacin resulta ser algo de lo ms sencillo. Lo primero ser crear un objeto de la
clase UsuariosSQLiteHelper al que pasaremos el contexto de la aplicacin (en el ejemplo

una referencia a la actividad principal), el nombre de la base de datos, un


objeto CursorFactory que tpicamente no ser necesario (en ese caso pasaremos el
valor null), y por ltimo la versin de la base de datos que necesitamos. La simple creacin
de este objeto puede tener varios efectos:
Si la base de datos ya existe y su versin actual coincide con la solicitada simplemente se
realizar la conexin con ella.
Si la base de datos existe pero su versin actual es anterior a la solicitada, se llamar
automticamente al mtodo onUpgrade() para convertir la base de datos a la nueva versin
y se conectar con la base de datos convertida.
Si la base de datos no existe, se llamar automticamente al mtodo onCreate() para crearla
y se conectar con la base de datos creada.

216

Una vez tenemos una referencia al objeto UsuariosSQLiteHelper, llamaremos a su


mtodogetReadableDatabase() o getWritableDatabase() para obtener una referencia a la
base de datos, dependiendo si slo necesitamos consultar los datos o tambin necesitamos
realizar modificaciones, respectivamente.
Ahora que ya hemos conseguido una referencia a la base de datos (objeto de
tipo SQLiteDatabase) ya podemos realizar todas las acciones que queramos sobre ella. Para
nuestro ejemplo nos limitaremos a insertar 5 registros de prueba, utilizando para ello el
mtodo ya comentado execSQL() con las sentenciasINSERT correspondientes. Por ltimo
cerramos la conexin con la base de datos llamando al mtodoclose().
1
package net.sgoliver.android.bd;
2
3
import android.app.Activity;
4
import android.database.sqlite.SQLiteDatabase;
5
import android.os.Bundle;
6
7
public class AndroidBaseDatos extends Activity
8
{
9
@Override
10
public void onCreate(Bundle savedInstanceState)
11
{
12
super.onCreate(savedInstanceState);
13
setContentView(R.layout.main);
14
15
//Abrimos la base de datos 'DBUsuarios' en modo escritura
16
UsuariosSQLiteHelper usdbh =
17
new UsuariosSQLiteHelper(this, "DBUsuarios", null, 1);
18
19
SQLiteDatabase db = usdbh.getWritableDatabase();
20
21
//Si hemos abierto correctamente la base de datos
22
if(db != null)
23
{
24
//Insertamos 5 usuarios de ejemplo
25
for(int i=1; i<=5; i++)
26
{
27
//Generamos los datos
28
int codigo = i;
29
String nombre = "Usuario" + i;
30
31
//Insertamos los datos en la tabla Usuarios
32
db.execSQL("INSERT INTO Usuarios (codigo, nombre) " +
33
"VALUES (" + codigo + ", '" + nombre +"')");
34
}
35

217

36
37
38
39
40

//Cerramos la base de datos


db.close();
}
}
}

Vale, y ahora qu? dnde est la base de datos que acabamos de crear? cmo podemos
comprobar que todo ha ido bien y que los registros se han insertado correctamente?
Vayamos por partes.
En primer lugar veamos dnde se ha creado nuestra base de datos. Todas las bases de datos
SQLite creadas por aplicaciones Android utilizando este mtodo se almacenan en la
memoria del telfono en un fichero con el mismo nombre de la base de datos situado en una
ruta que sigue el siguiente patrn:
/data/data/paquete.java.de.la.aplicacion/databases/nombre_base_datos
En el caso de nuestro ejemplo, la base de datos se almacenara por tanto en la ruta
siguiente:
/data/data/net.sgoliver.android.bd/databases/DBUsuarios
Para comprobar esto podemos hacer lo siguiente. Una vez ejecutada por primera vez desde
Eclipse la aplicacin de ejemplo sobre el emulador de Android (y por supuesto antes de
cerrarlo) podemos ir a la perspectiva DDMS (Dalvik Debug Monitor Server) de Eclipse y
en la solapa File Explorer podremos acceder al sistema de archivos del emulador, donde
podremos buscar la ruta indicada de la base de datos. Podemos ver esto en la siguiente
imagen (click para ampliar):

Con esto ya comprobamos al menos que el fichero de nuestra base de datos se ha creado en
la ruta correcta. Ya slo nos queda comprobar que tanto las tablas creadas como los datos
insertados tambin se han incluido correctamente en la base de datos. Para ello podemos
recurrir a dos posibles mtodos:
1. Trasnferir la base de datos a nuestro PC y consultarla con cualquier administrador de bases
de datos SQLite.

218

2. Acceder directamente a la consola de comandos del emulador de Android y utilizar los


comandos existentes para acceder y consultar la base de datos SQLite.
El primero de los mtodos es sencillo. El fichero de la base de datos podemos transferirlo a
nuestro PC utilizando el botn de descarga situado en la esquina superior derecha del
explorador de archivos (remarcado en rojo en la imagen anterior). Junto a este botn
aparecen otros dos para hacer la operacin contraria (copiar un fichero local al sistema de
archivos del emulador) y para eliminar ficheros del emulador. Una vez descargado el
fichero a nuestro sistema local, podemos utilizar cualquier administrador de SQLite para
abrir y consultar la base de datos, por ejemplo SQLite Administrator (freeware).
El segundo mtodo utiliza una estrategia diferente. En vez de descargar la base de datos a
nuestro sistema local, somos nosotros los que accedemos de forma remota al emulador a
travs de su consola de comandos (shell). Para ello, con el emulador de Android an
abierto, debemos abrir una consola de MS-DOS y utilizar la utilidad adb.exe (Android
Debug Bridge) situada en la carpeta platform-tools del SDK de Android (en mi
caso: c:\Users\Salvador\AppData\Local\Android\android-sdk\platform-tools\). En primer
lugar consultaremos los identificadores de todos los emuladores en ejecucin mediante el
comando adb devices. Esto nos debe devolver una nica instancia si slo tenemos un
emulador abierto, que en mi caso particular se llama emulator-5554.
Tras conocer el identificador de nuestro emulador, vamos a acceder a su shell mediante el
comando adb -s identificador-del-emulador shell. Una vez conectados, ya podemos
acceder a nuestra base de datos utilizando el comando sqlite3 pasndole la ruta del fichero,
para nuestro ejemplo sqlite3 /data/data/net.sgoliver.android.bd/databases/DBUsuarios. Si
todo ha ido bien, debe aparecernos el prompt de SQLite sqlite>, lo que nos indicar que
ya podemos escribir las consultas SQL necesarias sobre nuestra base de datos. Nosotros
vamos a comprobar que existe la tabla Usuariosy que se han insertado los cinco registros de
ejemplo. Para ello haremos la siguiente consulta: SELECT * FROM Usuarios;. Si todo es
correcto esta instruccin debe devolvernos los cinco usuarios existentes en la tabla. En la
imagen siguiente se muestra todo el proceso descrito (click para ampliar):

219

Con esto ya hemos comprobado que nuestra base de datos se ha creado correctamente, que
se han insertado todos los registros de ejemplo y que todo funciona segn se espera.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
En los siguientes artculos comentaremos las distintas posibilidades que tenemos a la hora
de manipular los datos de la base de datos (insertar, eliminar y modificar datos) y cmo
podemos realizar consultas sobre los mismos, ya que [como siempre] tendremos varias
opciones disponibles.
Bases de Datos en Android (II): Insertar/Actualizar/Eliminar
by Sgoliver on 03/02/2011 in Android, Programacin
En el artculo anterior del curso de programacin en Android vimos cmo crear una base de
datos para utilizarla desde nuestra aplicacin Android. En este segundo artculo de la serie
vamos a describir las posibles alternativas que proporciona la API de Android a la hora de
insertar, actualizar y eliminar registros de nuestra base de datos SQLite.
La API de SQLite de Android proporciona dos alternativas para realizar operaciones sobre
la
base
de
datos
que
no
devuelven
resultados
(entre
ellas
la
insercin/actualizacin/eliminacin de registros, pero tambin la creacin de tablas, de
ndices, etc).
El primero de ellos, que ya comentamos brevemente en el artculo anterior, es el
mtodo execSQL() de la clase SQLiteDatabase. Este mtodo permite ejecutar cualquier
sentencia SQL sobre la base de datos, siempre que sta no devuelva resultados. Para ello,
simplemente aportaremos como parmetro de entrada de este mtodo la cadena de texto
correspondiente con la sentencia SQL. Cuando creamos la base de datos en el post
anterior ya vimos algn ejemplo de esto para insertar los registros de prueba. Otros
ejemplos podran ser los siguientes:
//Insertar un registro
1
db.execSQL("INSERT INTO Usuarios (codigo,nombre) VALUES (6,'usuariopru')
2
");
3
4
//Eliminar un registro
5
db.execSQL("DELETE FROM Usuarios WHERE codigo=6 ");
6
7
//Actualizar un registro
8
db.execSQL("UPDATE Usuarios SET nombre='usunuevo' WHERE codigo=6 ");

220

La segunda de las alternativas disponibles en la API de Android es utilizar los


mtodos insert(),update() y delete() proporcionados tambin con la clase SQLiteDatabase.
Estos mtodos permiten realizar las tareas de insercin, actualizacin y eliminacin de
registros de una forma algo ms paramtrica que execSQL(), separando tablas, valores y
condiciones en parmetros independientes de estos mtodos.
Empecemos por el mtodo insert() para insertar nuevos registros en la base de datos. Este
mtodo recibe tres parmetros, el primero de ellos ser el nombre de la tabla, el tercero
sern los valores del registro a insertar, y el segundo lo obviaremos por el momento ya que
tan slo se hace necesario en casos muy puntuales (por ejemplo para poder insertar
registros completamente vacos), en cualquier otro caso pasaremos con valor null este
segundo parmetro.
Los valores a insertar los pasaremos como elementos de una coleccin de
tipo ContentValues. Esta coleccin es de tipo diccionario, donde almacenaremos parejas
de clave-valor, donde la clave ser el nombre de cada campo y el valor ser el dato
correspondiente a insertar en dicho campo. Veamos un ejemplo:
1 //Creamos el registro a insertar como objeto ContentValues
2 ContentValues nuevoRegistro = new ContentValues();
3 nuevoRegistro.put("codigo", "6");
4 nuevoRegistro.put("nombre","usuariopru");
5
6 //Insertamos el registro en la base de datos
7 db.insert("Usuarios", null, nuevoRegistro);
Los mtodos update() y delete() se utilizarn de forma muy parecida a sta, con la salvedad
de que recibirn un parmetro adicional con la condicin WHERE de la sentencia SQL. Por
ejemplo, para actualizar el nombre del usuario con cdigo 6 haramos lo siguiente:
1 //Establecemos los campos-valores a actualizar
2 ContentValues valores = new ContentValues();
3 valores.put("nombre","usunuevo");
4
5 //Actualizamos el registro en la base de datos
6 db.update("Usuarios", valores, "codigo=6", null);
Como podemos ver, como tercer parmetro del mtodo update() pasamos directamente la
condicin delUPDATE tal como lo haramos en la clusula WHERE en una sentencia SQL
normal.
El mtodo delete() se utilizara de forma anloga. Por ejemplo para eliminar el registro del
usuario con cdigo 6 haramos lo siguiente:
1 //Eliminamos el registro del usuario '6'
2 db.delete("Usuarios", "codigo=6", null);

221

Como vemos, volvemos a pasar como primer parmetro el nombre de la tabla y en segundo
lugar la condicin WHERE. Por supuesto, si no necesitramos ninguna condicin,
podramos dejar como null en este parmetro (lo que eliminara todos los registros de la
tabla).
Un ltimo detalle sobre estos mtodos. Tanto en el caso de execSQL() como en los casos
de update() odelete() podemos utilizar argumentos dentro de las condiones de la sentencia
SQL. stos no son ms que partes variables de la sentencia SQL que aportaremos en un
array de valores aparte, lo que nos evitar pasar por la situacin tpica en la que tenemos
que construir una sentencia SQL concatenando cadenas de texto y variables para formar el
comando SQL final. Estos argumentos SQL se indicarn con el smbolo ?, y los valores
de dichos argumentos deben pasarse en el array en el mismo orden que aparecen en la
sentencia SQL. As, por ejemplo, podemos escribir instrucciones como la siguiente:
1
//Eliminar un registro con execSQL(), utilizando argumentos
2
String[] args = new String[]{"usuario1"};
3
db.execSQL("DELETE FROM Usuarios WHERE nombre=?", args);
4
5
//Actualizar dos registros con update(), utilizando argumentos
6
ContentValues valores = new ContentValues();
7
valores.put("nombre","usunuevo");
8
9
String[] args = new String[]{"usuario1", "usuario2"};
10 db.update("Usuarios", valores, "nombre=? OR nombre=?", args);
Esta forma de pasar a la sentencia SQL determinados datos variables puede ayudarnos
adems a escribir cdigo ms limpio y evitar posibles errores.
Para este apartado he continuado con la aplicacin de ejemplo del apartado anterior, a la
que he aadido dos cuadros de texto para poder introducir el cdigo y nombre de un usuario
y tres botones para insertar, actualizar o eliminar dicha informacin.

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.

222

En el siguiente artculo veremos cmo consultar la base de datos para recuperar registros
segn un determinado criterio.
Bases de Datos en Android (III): Consultar/Recuperar registros
by Sgoliver on 07/02/2011 in Android, Programacin
En el anterior artculo del curso vimos todas las opciones disponibles a la hora de insertar,
actualizar y eliminar datos de una base de datos SQLite en Android. En esta nueva entrega
vamos a describir la ltima de las tareas importantes de tratamiento de datos que nos queda
por ver, la seleccin y recuperacin de datos.
De forma anloga a lo que vimos para las sentencias de modificacin de datos, vamos a
tener dos opciones principales para recuperar registros de una base de datos SQLite en
Android. La primera de ellas utilizando directamente un comando de seleccin SQL, y
como segunda opcin utilizando un mtodo especfico donde parametrizaremos la consulta
a la base de datos.
Para la primera opcin utilizaremos el mtodo rawQuery() de la clase SQLiteDatabase.
Este mtodo recibe directamente como parmetro un comando SQL completo, donde
indicamos los campos a recuperar y los criterios de seleccin. El resultado de la consulta lo
obtendremos en forma de cursor, que posteriormente podremos recorrer para procesar los
registros recuperados. Sirva la siguiente consulta a modo de ejemplo:
Cursor c = db.rawQuery(" SELECT codigo,nombre FROM Usuarios WHERE
1
nombre='usu1' ", null);
Como en el caso de los mtodos de modificacin de datos, tambin podemos aadir a este
mtodo una lista de argumentos variables que hayamos indicado en el comando SQL con el
smbolo ?, por ejemplo as:
String[] args = new String[] {"usu1"};
1
Cursor c = db.rawQuery(" SELECT codigo,nombre FROM Usuarios WHERE
2
nombre=? ", args);
Ms adelante en este artculo veremos cmo podemos manipular el objeto Cursor para
recuperar los datos obtenidos.
Como segunda opcin para recuperar datos podemos utilizar el mtodo query() de la
claseSQLiteDatabase. Este mtodo recibe varios parmetros: el nombre de la tabla, un array
con los nombre de campos a recuperar, la clusula WHERE, un array con los argumentos
variables incluidos en el WHERE(si los hay, null en caso contrario), la clusula GROUP
BY si existe, la clusula HAVING si existe, y por ltimo la clusula ORDER BY si existe.
Opcionalmente, se puede incluir un parmetro al final ms indicando el nmero mximo de

223

registros que queremos que nos devuelva la consulta. Veamos el mismo ejemplo anterior
utilizando el mtodo query():
1 String[] campos = new String[] {"codigo", "nombre"};
2 String[] args = new String[] {"usu1"};
3
4 Cursor c = db.query("Usuarios", campos, "usuario=?", args, null, null, null);
Como vemos, los resultados se devuelven nuevamente en un objeto Cursor que deberemos
recorrer para procesar los datos obtenidos.

Para recorrer y manipular el cursor devuelto por cualquiera de los dos mtodos
mencionados tenemos a nuestra disposicin varios mtodos de la clase Cursor, entre los que
destacamos dos de los dedicados a recorrer el cursor de forma secuencial y en orden
natural:
moveToFirst(): mueve el puntero del cursor al primer registro devuelto.
moveToNext(): mueve el puntero del cursor al siguiente registro devuelto.
Los mtodos moveToFirst() y moveToNext() devuelven TRUE en caso de haber realizado
el movimiento correspondiente del puntero sin errores, es decir, siempre que exista un
primer registro o un registro siguiente, respectivamente.
Una vez posicionados en cada registro podremos utilizar cualquiera de los
mtodosgetXXX(ndice_columna) existentes para cada tipo de dato para recuperar el dato
de cada campo del registro actual del cursor. As, si queremos recuperar por ejemplo la
segunda columna del registro actual, y sta contiene un campo alfanumrico, haremos la
llamada getString(1) [NOTA: los ndices comienzan por 0 (cero), por lo que la segunda
columna tiene ndice 1], en caso de contener un dato de tipo real llamaramos
a getDouble(1), y de forma anloga para todos los tipos de datos existentes.
Con todo esto en cuenta, veamos cmo podramos recorrer el cursor devuelto por el
ejemplo anterior:
1
2
3
4
5
6
7
8
9
10
11
12
13

String[] campos = new String[] {"codigo", "nombre"};


String[] args = new String[] {"usu1"};
Cursor c = db.query("Usuarios", campos, "nombre=?", args, null, null, null);
//Nos aseguramos de que existe al menos un registro
if (c.moveToFirst()) {
//Recorremos el cursor hasta que no haya ms registros
do {
String codigo= c.getString(0);
String nombre = c.getString(1);
} while(c.moveToNext());
}

224

Adems de los mtodos comentados de la clase Cursor existen muchos ms que nos pueden
ser tiles en muchas ocasiones. Por ejemplo, getCount() te dir el nmero total de registros
devueltos en el cursor,getColumnName(i) devuelve el nombre de la columna con ndice
i, moveToPosition(i) mueve el puntero del cursor al registro con ndice i, etc. Podis
consultar la lista completa de mtodos disponibles en la clase Cursor en la documentacin
oficial de Android.
En este apartado he seguido ampliando la aplicacin de ejemplo anterior para aadir la
posibilidad de recuperar todos los registros de la tabla Usuarios pulsando un nuevo botn
de consulta.

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
Con esto, terminamos la serie de artculos bsicos dedicados a las tareas de mantenimiento
de datos en aplicaciones Android mediante bases de datos SQLite. Soy consciente de que
dejamos en el tintero algunos temas algo ms avanzados (como por ejemplo el uso
de transacciones, que intentar tratar ms adelante), pero con los mtodos descritos
podremos realizar un porcentaje bastante alto de todas las tareas necesarias relativas al
tratamiento de datos estructurados en aplicaciones Android.
Ficheros en Android
Ficheros en Android (I): Memoria Interna
by Sgoliver on 05/07/2011 in Android, Programacin
En artculos anteriores del Curso de Programacin Android hemos visto ya diversos
mtodos para almacenar datos en nuestras aplicaciones, como por ejemplo los ficheros
de preferencias compartidas o las bases de datos SQLite. Estos mecanismos son perfectos
para almacenar datos estructurados, pero en ocasiones nos seguir siendo til poder

225

disponer tambin de otros ficheros auxiliares de datos, probblemente con otro tipo de
contenidos y formatos. Por ello, en Android tambin podremos manipular ficheros
tradicionales de una forma muy similar a como se realiza en Java.
Lo primero que hay que tener en cuenta es dnde queremos almacenar los ficheros y el tipo
de acceso que queremos tener a ellos. As, podremos leer y escribir ficheros localizados en:
1. La memoria interna del dispositivo.
2. La tarjeta SD externa, si existe.
3. La propia aplicacin, en forma de recurso.
En los dos prximos artculos aprenderemos a manipular ficheros almacenados en
cualquiera de estos lugares, comentando las particularidades de cada caso.
Veamos en primer lugar cmo trabajar con la memoria interna del dispositivo. Cuando
almacenamos ficheros en la memoria interna debemos tener en cuenta las limitaciones de
espacio que tienen muchos dispositivos, por lo que no deberamos abusar de este espacio
utilizando ficheros de gran tamao.
Escribir ficheros en la memoria interna es muy sencillo. Android proporciona para ello el
mtodoopenFileOutput(), que recibe como parmetros el nombre del fichero y el modo de
acceso con el que queremos abrir el fichero. Este modo de acceso puede variar
entre MODE_PRIVATE para acceso privado desde nuestra aplicacin (crea el fichero o lo
sobrescribe si ya existe), MODE_APPEND para aadir datos a un fichero ya
existente, MODE_WORLD_READABLE para permitir a otras aplicaciones leer el fichero,
oMODE_WORLD_WRITABLE para permitir a otras aplicaciones escribir sobre el
fichero. Los dos ltimos no deberan utilizarse dada su peligrosidad, de hecho, han sido
declarados como obsoletos (deprecated) en la API 17.
Este mtodo devuelve una referencia al stream de salida asociado al fichero (en forma de
objetoFileOutputStream), a partir del cual ya podremos utilizar los mtodos de
manipulacin de ficheros tradicionales del lenguaje java (api java.io). Como ejemplo,
convertiremos este stream a unOutputStreamWriter para escribir una cadena de texto al
fichero.
1
try
2
{
3
OutputStreamWriter fout=
4
new OutputStreamWriter(
5
openFileOutput("prueba_int.txt", Context.MODE_PRIVATE));
6
7
fout.write("Texto de prueba.");
8
fout.close();

226

9
10
11
12
13

}
catch (Exception ex)
{
Log.e("Ficheros", "Error al escribir fichero a memoria interna");
}

Est bien, ya hemos creado un fichero de texto en la memoria interna, pero dnde
exactamente? Tal como ocurra con las bases de datos SQLite, Android almacena por
defecto los ficheros creados en una ruta determinada, que en este caso seguir el siguiente
patrn:
/data/data/paquete.java/files/nombre_fichero
En mi caso particular, la ruta ser
/data/data/net.sgoliver.android.ficheros/files/prueba_int.txt
Si ejecutamos el cdigo anterior podremos comprobar en el DDMS cmo el fichero se crea
correctamente en la ruta indicada (Al final del artculo hay un enlace a una aplicacin de
ejemplo sencilla donde incluyo un botn por cada uno de los puntos que vamos a comentar
en el artculo).

Por otra parte, leer ficheros desde la memoria interna es igual de sencillo, y procederemos
de forma anloga, con la nica diferencia de que utilizaremos el
mtodo openFileInput() para abrir el fichero, y los mtodos de lectura de java.io para leer el
contenido.
1
try
2
{
3
BufferedReader fin =
4
new BufferedReader(
5
new InputStreamReader(
6
openFileInput("prueba_int.txt")));
7
8
String texto = fin.readLine();
9
fin.close();
10 }
11 catch (Exception ex)
12 {

227

13
Log.e("Ficheros", "Error al leer fichero desde memoria interna");
14 }
La segunda forma de almacenar ficheros en la memoria interna del dispositivo es incluirlos
como recursoen la propia aplicacin. Aunque este mtodo es til en muchos casos, slo
debemos utilizarlo cuando no necesitemos realizar modificaciones sobre los ficheros, ya
que tendremos limitado el acceso a slo lectura.
Para incluir un fichero como recurso de la aplicacin debemos colocarlo en la carpeta
/res/raw de nuestro proyecto de Eclipse. Esta carpeta no suele estar creada por defecto,
por lo que deberemos crearla manualmente en Eclipse desde el men contextual con la
opcin New / Folder.

Una vez creada la carpeta /raw podremos colocar en ella cualquier fichero que queramos
que se incluya con la aplicacin en tiempo de compilacin en forma de recurso. Nosotros
incluiremos como ejemplo un fichero de texto llamado prueba_raw.txt. Ya en tiempo de
ejecucin podremos acceder a este fichero, slo en modo de lectura, de una forma similar a
la que ya hemos visto para el resto de ficheros en memoria interna.
Para acceder al fichero, accederemos en primer lugar a los recursos de la aplicacin con el
mtodogetResources() y
sobre
stos
utilizaremos
el
mtodo openRawResource(id_del_recurso) para abrir el fichero en modo lectura. Este
mtodo devuelve un objeto InputStream, que ya podremos manipular como queramos
mediante los mtodos de la API java.io. Como ejemplo, nosotros convertiremos el stream
en un objeto BufferedReader para leer el texto contenido en el fichero de ejemplo (por
supuesto los ficheros de recurso tambin pueden ser binarios, como por ejemplo ficheros de
imagen, video, etc). Veamos cmo quedara el cdigo:
1
try
2
{
3
InputStream fraw =
4
getResources().openRawResource(R.raw.prueba_raw);
5
6
BufferedReader brin =
7
new BufferedReader(new InputStreamReader(fraw));
8

228

9
String linea = brin.readLine();
10
11
fraw.close();
12 }
13 catch (Exception ex)
14 {
15
Log.e("Ficheros", "Error al leer fichero desde recurso raw");
16 }
Como puede verse en el cdigo anterior, al mtodo openRawResource() le pasamos como
parmetro el ID del fichero incluido como recurso, que seguir el patrn
R.raw.nombre_del_fichero, por lo que en nuestro caso particular ser R.raw.prueba_raw.
La aplicacin de ejemplo de este apartado consiste en una pantalla sencilla con 3 botones
que realizan exactamente las tres tareas que hemos comentado: escribir y leer un fichero en
la memoria interna, y leer un fichero de la carpeta raw.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
Para no alargar mucho el artculo, dejamos la gestin de ficheros en la memoria externa
para el prximo artculo.
Ficheros en Android (II): Memoria Externa (Tarjeta SD)
by Sgoliver on 06/07/2011 in Android, Programacin
En el artculo anterior del curso hemos visto cmo manipular ficheros localizados en la
memoria interna de un dispositivo Android. Sin embargo, como ya indicamos, esta
memoria suele ser relativamente limitada y no es aconsejable almacenar en ella ficheros de
gran tamao. La alternativa natural es utilizar para ello la memoria externa del dispositivo,
constituida normalmente por una tarjeta de memoria SD, aunque en dispositivos recientes
tambin est presente en forma de almacenamiento no extrable del dispositivo, aunque no
por ello debe confundirse con la memoria interna. A diferencia de la memoria interna, el
almacenamiento externo es pblico, es decir, todo lo que escribamos en l podr ser ledo
por otras aplicaciones y por el usuario, por tanto hay que tener cierto cuidado a la hora de
dicidir lo que escribimos en memoria interna y externa.
Una nota rpida antes de empezar con este tema. Para poder probar aplicaciones que hagan
uso de la memoria externa en el emulador de Android necesitamos tener configurado en
Eclipse un AVD que tenga establecido correctamente el tamao de la tarjeta SD. En mi
caso, he definido por ejemplo un tamao de tarjeta de 50 Mb:

229

Seguimos. A diferencia de la memoria interna, la tarjeta de memoria no tiene por qu estar


presente en el dispositivo, e incluso estndolo puede no estar reconocida por el sistema. Por
tanto, el primer paso recomendado a la hora de trabajar con ficheros en memoria externa es
asegurarnos de que dicha memoria est presente y disponible para leer y/o escribir en ella.
Para esto la API de Android proporciona (como mtodo esttico dela clase Environment) el
mtodogetExternalStorageStatus(), que no dice si la memoria externa est disponible y si se

puede leer y escribir en ella. Este mtodo devuelve una serie de valores que nos indicarn el
estado de la memoria externa, siendo los ms importantes los siguientes:
MEDIA_MOUNTED, que indica que la memoria externa est disponible y podemos tanto
leer como escribir en ella.
MEDIA_MOUNTED_READ_ONLY, que indica que la memoria externa est disponible
pero slo podemos leer de ella.
Otra serie de valores que indicarn que existe algn problema y que por tanto no podemos
ni leer ni escribir en la memoria externa (MEDIA_UNMOUNTED, MEDIA_REMOVED,
). Podis consultar todos estos estados en la documentacin oficial de la clase
Environment.
Con todo esto en cuenta, podramos realizar un chequeo previo del estado de la memoria
externa del dispositivo de la siguiente forma:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

boolean sdDisponible = false;


boolean sdAccesoEscritura = false;
//Comprobamos el estado de la memoria externa (tarjeta SD)
String estado = Environment.getExternalStorageState();
if (estado.equals(Environment.MEDIA_MOUNTED))
{
sdDisponible = true;
sdAccesoEscritura = true;
}
else if (estado.equals(Environment.MEDIA_MOUNTED_READ_ONLY))
{
sdDisponible = true;
sdAccesoEscritura = false;
}
else

230

18
19
20
21

{
sdDisponible = false;
sdAccesoEscritura = false;
}

Una vez chequeado el estado de la memoria externa, y dependiendo del resultado obtenido,
ya podremos leer o escribir en ella cualquier tipo de fichero.
Empecemos por la escritura. Para escribir un fichero a la memoria externa tenemos que
obtener en primer lugar la ruta al directorio raz de esta memoria. Para ello podemos
utilizar el mtodogetExternalStorageDirectory() de la clase Environment, que nos
devolver un objeto File con la ruta de dicho directorio. A partir de este objeto, podremos
construir otro con el nombre elegido para nuestro fichero (como ejemplo prueba_sd.txt),
creando un nuevo objeto File que combine ambos elementos. Tras esto, ya slo queda
encapsularlo en algn objeto de escritura de ficheros de la API de java y escribir algn dato
de prueba. En nuestro caso de ejemplo lo convertiremos una vez ms a un
objetoOutputStreamWriter para escribir al fichero un mensaje de texto. Veamos cmo
quedara el cdigo:
1
try
2
{
3
File ruta_sd = Environment.getExternalStorageDirectory();
4
5
File f = new File(ruta_sd.getAbsolutePath(), "prueba_sd.txt");
6
7
OutputStreamWriter fout =
8
new OutputStreamWriter(
9
new FileOutputStream(f));
10
11
fout.write("Texto de prueba.");
12
fout.close();
13 }
14 catch (Exception ex)
15 {
16
Log.e("Ficheros", "Error al escribir fichero a tarjeta SD");
17 }
El cdigo anterior funciona sin problemas pero escribir el fichero directamente en la
carpeta raz de la memoria externa. Esto, aunque en ocasiones puede resultar necesario, no
es una buena prctica. Lo correcto sera disponer de una carpeta propia para nuestra
aplicacin, lo que adems tendr la ventaja de que al desinstalar la aplicacin tambin se
liberar este espacio. Esto lo conseguimos utilizando el mtodogetExternalFilesDir(null) en
vez

de getExternalStorageDirectory().

El

mtodogetExternalFilesDir() nos

devuelve

231

directamente la ruta de una carpeta especfica para nuestra aplicacin dentro de la memoria
externa siguiendo el siguiente patrn:
<raz_mem_ext>/Android/data/nuestro.paquete.java/files
Si en vez de null le indicamos como parmetro un tipo de datos determinado
(DIRECTORY_MUSIC,DIRECTORY_PICTURES, DIRECTORY_MOVIES, DIRECTO
RY_RINGTONES, DIRECTORY_ALARMS,DIRECTORY_NOTIFICATIONS, DIRECT
ORY_PODCASTS) nos devolver una subcarpeta dentro de la anterior con su nombre
correspondiente.
As,
por
ejemplo,
una
llamada
al
mtodo
getExternalFilesDir(Environment.DIRECTORY_MUSIC) nos devolvera la siguiente
carpeta:
<raz_mem_ext>/Android/data/nuestro.paquete.java/files/Music
Esto ltimo, adems, ayuda a Android a saber qu tipo de contenidos hay en cada carpeta,
de forma que puedan clasificarse correctamente por ejemplo en la galera multimedia.
Sea como sea, para tener acceso a la memoria externa tendremos que especificar en el
ficheroAndroidManifest.xml que nuestra aplicacin necesita permiso de escritura en dicha
memoria. Para aadir un nuevo permiso usaremos como siempre la clusula <usespermission> utilizando
el
valor
android.permission.WRITE_EXTERNAL_STORAGE.
Con
nuestro AndroidManifest.xml quedara de forma similar a ste:
1
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
package="net.sgoliver.android.ficheros"
3
android:versionCode="1"
4
android:versionName="1.0" >
5
6
<uses-sdk
7
android:minSdkVersion="8"
8
android:targetSdkVersion="17" />
9
10
<uses-permission
11 android:name="android.permission.WRITE_EXTERNAL_STORAGE">
12
</uses-permission>
13
14
<application
15
android:allowBackup="true"
16
android:icon="@drawable/ic_launcher"
17
android:label="@string/app_name"
18
android:theme="@style/AppTheme" >
19
<activity
20
android:name="net.sgoliver.android.ficheros.MainActivity"
21
android:label="@string/app_name" >

concreto
esto,

232

22
23
24
25
26
27
28
29

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Si ejecutamos ahora el cdigo y nos vamos al explorador de archivos del DDMS podremos
comprobar cmose ha creado correctamente el fichero en el directorio raiz de nuestra SD.
Esta ruta puede variar entre dispositivos, pero para Android 2.x suele localizarse en la
carpeta /sd-card/, mientras que para Android 4 suele estar en /mnt/sdcard/.

Por su parte, leer un fichero desde la memoria externa es igual de sencillo. Obtenemos el
directorio raiz de la memoria externa con getExternalStorageDirectory(), o la carpeta
especfica de nuestra aplicacin con getExternalFilesDir() como ya hemos visto, creamos
un objeto File que combine esa ruta con el nombre del fichero a leer y lo encapsulamos
dentro de algn objeto que facilite la lectura lectura, nosotros para leer texto utilizaremos
como siempre un BufferedReader.
1
try
2
{
3
File ruta_sd = Environment.getExternalStorageDirectory();
4
5
File f = new File(ruta_sd.getAbsolutePath(), "prueba_sd.txt");
6
7
BufferedReader fin =
8
new BufferedReader(
9
new InputStreamReader(
10
new FileInputStream(f)));
11
12
String texto = fin.readLine();
13
fin.close();
14 }
15 catch (Exception ex)

233

16
17
18

{
Log.e("Ficheros", "Error al leer fichero desde tarjeta SD");
}

Como vemos, el cdigo es anlogo al que hemos visto para la escritura de ficheros.
Como aplicacin de ejemplo de este artculo he partido de la desarrollada en el artculo
anterior dedicado a la memoria interna y he aadido dos nuevos botones para leer y escribir
a memoria externa tal como hemos descrito. Los resultados se muestran en el log de la
aplicacin.

Tratamiento de XML en Android


Tratamiento de XML en Android (I): SAX
by Sgoliver on 18/01/2011 in Android, Programacin
En los siguientes artculos de este Tutorial de Desarrollo para Android vamos a comentar
las distintas posibilidades que tenemos a la hora de trabajar con datos en formato XML
desde la plataforma Android.
A da de hoy, en casi todas las grandes plataformas de desarrollo existen varias formas de
leer y escribir datos en formato XML. Los dos modelos ms extendidos son SAX (Simple
API for XML) y DOM (Document Object Model). Posteriormente, han ido apareciendo
otros tantos, con ms o menos xito, entre los que destaca StAX (Streaming API for XML).
Pues bien, Android no se queda atrs en este sentido e incluye estos tres modelos
principales para el tratamiento de XML, o para ser ms exactos, los dos primeros como tal

234

y una versin anloga del tercero (XmlPull). Por supuesto con cualquiera de los tres
modelos podemos hacer las mismas tareas, pero ya veremos cmo dependiendo de la
naturaleza de la tarea que queramos realizar va a resultar ms eficiente utilizar un modelo u
otro.
Antes de empezar, unas anotaciones respecto a los ejemplos que voy a utilizar. Estas
tcnicas se pueden utilizar para tratar cualquier documento XML, tanto online como local,
pero por utilizar algo conocido por la mayora de vosotros todos los ejemplos van a trabajar
sobre los datos XML de un documento RSS online, y en mi caso utilizar como ejemplo
el canal RSS de portada de europapress.com.
Un documento RSS de este feed tiene la estructura siguiente:
1
<rss version="2.0">
2
<channel>
3
<title>Europa Press</title>
4
<link>http://www.europapress.es/</link>
5
<description>Noticias de Portada.</description>
6
<image>
7
<url>http://s01.europapress.net/eplogo.gif</url>
8
<title>Europa Press</title>
9
<link>http://www.europapress.es</link>
10
</image>
11
<language>es-ES</language>
12
<copyright>Copyright</copyright>
13
<pubDate>Sat, 25 Dec 2010 23:27:26 GMT</pubDate>
14
<lastBuildDate>Sat, 25 Dec 2010 22:47:14 GMT</lastBuildDate>
15
<item>
16
<title>Ttulo de la noticia 1</title>
17
<link>http://link_de_la_noticia_2.es</link>
18
<description>Descripcin de la noticia 2</description>
19
<guid>http://identificador_de_la_noticia_2.es</guid>
20
<pubDate>Fecha de publicacin 2</pubDate>
21
</item>
22
<item>
23
<title>Ttulo de la noticia 2</title>
24
<link>http://link_de_la_noticia_2.es</link>
25
<description>Descripcin de la noticia 2</description>
26
<guid>http://identificador_de_la_noticia_2.es</guid>
27
<pubDate>Fecha de publicacin 2</pubDate>
28
</item>
29
...
30
</channel>
31 </rss>

235

Como puede observarse, se compone de un elemento principal <channel> seguido de varios


datos relativos al canal y posteriormente una lista de elementos <item> para cada noticia
con sus datos asociados.
En estos artculos vamos a describir cmo leer este XML mediante cada una de las tres
alternativas citadas, y para ello lo primero que vamos a hacer es definir una clase java para
almacenar los datos de una noticia. Nuestro objetivo final ser devolver una lista de objetos
de este tipo, con la informacin de todas las noticias. Por comodidad, vamos a almacenar
todos los datos como cadenas de texto:
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

public class Noticia {


private String titulo;
private String link;
private String descripcion;
private String guid;
private String fecha;
public String getTitulo() {
return titulo;
}
public String getLink() {
return link;
}
public String getDescripcion() {
return descripcion;
}
public String getGuid() {
return guid;
}
public String getFecha() {
return fecha;
}
public void setTitulo(String t) {
titulo = t;
}
public void setLink(String l) {
link = l;
}

236

36
public void setDescripcion(String d) {
37
descripcion = d;
38
}
39
40
public void setGuid(String g) {
41
guid = g;
42
}
43
44
public void setFecha(String f) {
45
fecha = f;
46
}
47 }
Una vez conocemos la estructura del XML a leer y hemos definido las clases auxiliares que
nos hacen falta para almacenar los datos, pasamos ya a comentar el primero de los modelos
de tratamiento de XML.
SAX en Android
En el modelo SAX, el tratamiento de un XML se basa en un analizador (parser) que a
medida que lee secuencialmente el documento XML va generando diferentes eventos con la
informacin de cada elemento leido. Asi, por ejemplo, a medida que lee el XML, si
encuentra el comienzo de una etiqueta <title>generar un evento de comienzo de
etiqueta, startElement(), con su informacin asociada, si despus de esa etiqueta encuentra
un fragmento de texto generar un evento characters() con toda la informacin necesaria, y
as sucesivamente hasta el final del documento. Nuestro trabajo consistir por tanto en

implementar las acciones necesarias a ejecutar para cada uno de los eventos posibles que se
pueden generar durante la lectura del documento XML.
Los principales eventos que se pueden producir son los siguientes (consultar aqu la lista
completa):
startDocument(): comienza el documento XML.
endDocument(): termina el documento XML.
startElement(): comienza una etiqueta XML.
endElement(): termina una etiqueta XML.
characters(): fragmento de texto.
Todos estos mtodos estn definidos en la clase org.xml.sax.helpers.DefaultHandler, de la
cual deberemos derivar una clase propia donde se sobrescriban los eventos necesarios. En
nuestro caso vamos a llamarla RssHandler.
1
public class RssHandler extends DefaultHandler {
2
private List<Noticia> noticias;
3
private Noticia noticiaActual;
4
private StringBuilder sbTexto;
5

237

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
46
47
48
49
50
51
52

public List<Noticia> getNoticias(){


return noticias;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
if (this.notciaActual != null)
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.notciaActual != null) {
if (localName.equals("title")) {
noticiaActual.setTitulo(sbTexto.toString());
} else if (localName.equals("link")) {
noticiaActual.setLink(sbTexto.toString());
} else if (localName.equals("description")) {
noticiaActual.setDescripcion(sbTexto.toString());
} else if (localName.equals("guid")) {
noticiaActual.setGuid(sbTexto.toString());
} else if (localName.equals("pubDate")) {
noticiaActual.setFecha(sbTexto.toString());
} else if (localName.equals("item")) {
noticias.add(noticiaActual);
}
sbTexto.setLength(0);
}
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
noticias = new ArrayList<Noticia>();
sbTexto = new StringBuilder();

238

53
54
55
56
57
58
59
60
61
62
63
64
65

}
@Override
public void startElement(String uri, String localName,
String name, Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
if (localName.equals("item")) {
noticiaActual = new Noticia();
}
}
}

Como se puede observar en el cdigo de anterior, lo primero que haremos ser incluir como
miembro de la clase la lista de noticias que pretendemos construir, List<Noticia> noticias, y
un mtodogetNoticias() que permita obtenerla tras la lectura completa del documento. Tras
esto, implementamos directamente los eventos SAX necesarios.
Comencemos por startDocument(), este evento indica que se ha comenzado a leer el
documento XML, por lo que lo aprovecharemos para inicializar la lista de noticias y las
variables auxiliares.
Tras ste, el evento startElement() se lanza cada vez que se encuentra una nueva etiqueta de
apertura. En nuestro caso, la nica etiqueta que nos interesar ser <item>, momento en el
que inicializaremos un nuevo objeto auxiliar de tipo Noticia donde almacenaremos
posteriormente los datos de la noticia actual.
El siguiente evento relevante es characters(), que se lanza cada vez que se encuentra un
fragmento de texto en el interior de una etiqueta. La tcnica aqu ser ir acumulando en una
variable auxiliar, sbTexto, todos los fragmentos de texto que encontremos hasta detectarse
una etiqueta de cierre.
Por ltimo, en el evento de cierre de etiqueta, endElement(), lo que haremos ser almacenar
en el atributo apropiado del objeto noticiaActual (que conoceremos por el
parmetro localName devuelto por el evento) el texto que hemos ido acumulando en la
variable sbTexto y limpiaremos el contenido de dicha variable para comenzar a acumular el
siguiente dato. El nico caso especial ser cuando detectemos el cierre de la
etiqueta <item>, que significar que hemos terminado de leer todos los datos de la noticia y
por tanto aprovecharemos para aadir la noticia actual a la lista de noticias que estamos
construyendo.
Una vez implementado nuestro handler, vamos a crear una nueva clase que haga uso de l
para parsear mediante SAX un documento XML concreto. A esta clase la llamaremos
RssParserSax. Ms adelante crearemos otras clases anlogas a sta que hagan lo mismo

239

pero utilizando los otros dos mtodos de tratamiento de XML ya mencionados. Esta clase
tendr nicamente un constructor que reciba como parmetro la URL del documento a
parsear, y un mtodo pblico llamado parse() para ejecutar la lectura del documento, y que
devolver como resultado una lista de noticias. Veamos cmo queda esta clase:
1
import java.io.IOException;
2
import java.io.InputStream;
3
import java.util.List;
4
5
import java.net.URL;
6
import javax.xml.parsers.SAXParser;
7
import java.net.MalformedURLException;
8
import javax.xml.parsers.SAXParserFactory;
9
10 public class RssParserSax
11 {
12
private URL rssUrl;
13
14
public RssParserSax(String url)
15
{
16
try
17
{
18
this.rssUrl = new URL(url);
19
}
20
catch (MalformedURLException e)
21
{
22
throw new RuntimeException(e);
23
}
24
}
25
26
public List<Noticia> parse()
27
{
28
SAXParserFactory factory = SAXParserFactory.newInstance();
29
30
try
31
{
32
SAXParser parser = factory.newSAXParser();
33
RssHandler handler = new RssHandler();
34
parser.parse(this.getInputStream(), handler);
35
return handler.getNoticias();
36
}
37
catch (Exception e)
38
{
39
throw new RuntimeException(e);
40
}
41
}

240

42
43
44
45
46
47
48
49
50
51
52
53
54

private InputStream getInputStream()


{
try
{
return rssUrl.openConnection().getInputStream();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}

Como se puede observar en el cdigo anterior, el constructor de la clase se limitar a


aceptar como parmetro la URL del documento XML a parsear a controlar la validez de
dicha URL, generando una excepcin en caso contrario.
Por su parte, el mtodo parse() ser el encargado de crear un nuevo parser SAX mediante
s fbricacorrespondiente [lo que se consigue obteniendo una instancia de la fbrica
conSAXParserFactory.newInstance() y
creando
un
nuevo
parser
con factory.newSaxParser()] y de iniciar el proceso pasando al parser una instancia
del handler que hemos creado anteriormente y una referencia al documento a parsear en
forma de stream.
Para esto ltimo, nos apoyamos en un mtodo privado auxiliar getInputStream(), que se
encarga de abrir la conexin con la URL especificada [mediante openConnection()] y
obtener el stream de entrada [mediante getInputStream()].
Con esto ya tenemos nuestra aplicacin Android preparada para parsear un documento
XML online utilizando el modelo SAX. Veamos lo simple que sera ahora llamar a este
parser por ejemplo desde nuestra actividad principal. Como ejemplo de tratamiento de los
datos obtenidos mostraremos los titulares de las noticias en un cuadro de texto
(txtResultado):
1
public void onCreate(Bundle savedInstanceState)
2
{
3
super.onCreate(savedInstanceState);
4
setContentView(R.layout.main);
5
6
RssParserSax saxparser =
7
new RssParserSax("http://www.europapress.es/rss/rss.aspx");
8
9
List<Noticia> noticias = saxparser.parse();
10

241

11
12
13
14
15
16
17
18
19
20
21

//Tratamos la lista de noticias


//Por ejemplo: escribimos los ttulos en pantalla
txtResultado.setText("");
for(int i=0; i<noticias.size(); i++)
{
txtResultado.setText(
txtResultado.getText().toString() +
System.getProperty("line.separator") +
noticias.get(i).getTitulo());
}
}

Las lineas 6 y 9 del cdigo anterior son las que hacen toda la magia. Primero creamos el
parser SAX pasndole la URL del documento XML y posteriormente llamamos al
mtodo parse() para obtener una lista de objetos de tipo Noticia que posteriormente
podremos manipular de la forma que queramos. As de sencillo.
Adicionalmente, para que este ejemplo funcione debemos aadir previamente permisos de
acceso a internet para la aplicacin. Esto se hace en el fichero AndroidManifest.xml, que
quedara de la siguiente forma:
1
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
package="net.sgoliver"
3
android:versionCode="1"
4
android:versionName="1.0">
5
6
<uses-permission
7
android:name="android.permission.INTERNET" />
8
9
<application android:icon="@drawable/icon"
10
android:label="@string/app_name">
11
<activity android:name=".AndroidXml"
12
android:label="@string/app_name">
13
<intent-filter>
14
<action android:name="android.intent.action.MAIN" />
15
<category android:name="android.intent.category.LAUNCHER" />
16
</intent-filter>
17
</activity>
18
19
</application>
20
<uses-sdk android:minSdkVersion="7" />
21 </manifest>
En la linea 6 del cdigo podis ver cmo aadimos el permiso de acceso a la red mediante
el elemento<uses-permission> con el parmetro android.permission.INTERNET
Pero hay algo ms, si ejecutamos el cdigo anterior en una versin de Android anterior a la
3.0 no tendremos ningn problema. La aplicacin descargar el XML y lo parsear tal

242

como hemos definido. Sin embargo, si utilizamos una versin de Android 3.0 o superior
nos encontraremos con algo similar a esto:

Y si miramos el log de ejecucin veremos que se ha generado una excepcin del


tipoNetworkOnMainThreadException. Qu ha ocurrido? Pues bien, lo que ha ocurrido es
que desde la versin 3 de Android se ha establecido una poltica que no permite a las
aplicaciones ejecutar operaciones de larga duracin en el hilo principal que puedan
bloquear temporalmente la interfaz de usuario. En este caso, nosotros hemos intentado
hacer una conexin a la red para descargarnos el XML y Android se ha quejado de ello
generando un error.
Y cmo arreglamos esto? Pues la solucin pasa por mover toda la lgica de descarga y
lectura del XML a otro hilo de ejecucin secundario, es decir, hacer el trabajo duro en
segundo plano dejando libre el hilo principal de la aplicacin y por tanto sin afectar a la
interfaz. La forma ms sencilla de hacer esto en Android es mediante la utilizacin de las
llamadas AsyncTask o tareas asncronas. Ms adelante dedicaremos todo un captulo a este
tema, por lo que ahora no entraremos en mucho detalle y nos limitaremos a ver cmo
transformar nuestro cdigo anterior para que funcione en versiones actuales de Android.
Lo que haremos ser definir una nueva clase que extienda de AsyncTask y
sobrescribiremos sus mtodosdoInBackground() y onPostExecute(). Al primero de ellos
moveremos la descarga y parseo del XML, y al segundo las tareas que queremos realizar
cuando haya finalizado el primero, en nuestro caso de ejemplo la escritura de los titulares al
cuadro de texto de resultado. Quedara de la siguiente forma:
1
//Tarea Asncrona para cargar un XML en segundo plano
2
private class CargarXmlTask extends AsyncTask<String,Integer,Boolean> {
3
4
protected Boolean doInBackground(String... params) {
5
6
RssParserSax saxparser =
7
new RssParserSax(params[0]);
8
9
noticias = saxparser.parse();
10
11
return true;

243

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

}
protected void onPostExecute(Boolean result) {
//Tratamos la lista de noticias
//Por ejemplo: escribimos los ttulos en pantalla
txtResultado.setText("");
for(int i=0; i<noticias.size(); i++)
{
txtResultado.setText(
txtResultado.getText().toString() +
System.getProperty("line.separator") +
noticias.get(i).getTitulo());
}
}
}

Por ltimo, sustituiremos el cdigo de nuestra actividad principal por una simple llamada
para crear y ejecutar la tarea en segundo plano:
1
public void onCreate(Bundle savedInstanceState)
2
{
3
super.onCreate(savedInstanceState);
4
setContentView(R.layout.main);
5
6
//Carga del XML mediante la tarea asncrona
7
8
CargarXmlTask tarea = new CargarXmlTask();
9
tarea.execute("http://www.europapress.es/rss/rss.aspx");
10 }
Y ahora s. Con esta ligera modificacin del cdigo nuestra aplicacin se ejecutar
correctamente en cualquier versin de Android.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
En los siguientes artculos veremos un modelo SAX alternativo con algunas ventajas sobre
el que acabamos de comentar, y los otros dos mtodos de tratamiento XML en Android
indicados (DOM y StAX).
Tratamiento de XML en Android (II): SAX Simplificado
by Sgoliver on 19/01/2011 in Android, Programacin
En el artculo anterior del tutorial vimos cmo realizar la lectura y tratamiento de un
documento XML utilizando el modelo SAX clsico. Vimos cmo implementar

244

un handler SAX, donde se definan las acciones a realizar tras recibirse cada uno de los
posibles eventos generados por el parser XML.
Este modelo, a pesar de funcionar perfectamente y de forma bastante eficiente, tiene claras
desventajas. Por un lado se hace necesario definir una clase independiente para el handler.
Adicionalmente, la naturaleza del modelo SAX implica la necesidad de poner bastante
atencin a la hora de definir dichohandler, ya que los eventos SAX definidos no estan
ligados de ninguna forma a etiquetas concretas del documento XML sino que se lanzarn
para todas ellas, algo que obliga entre otras cosas a realizar la distincin entre etiquetas
dentro de cada evento y a realizar otros chequeos adicionales.
Estos problemas se pueden observar perfectamente en el evento endElement() que
definimos en el ejemplo del artculo anterior. En primer lugar tenamos que comprobar la
condicin de que el atributonoticiaActual no fuera null, para evitar confundir el
elemento <title> descendiente de <channel>con el del mismo nombre pero descendiente
de <item>. Posteriormente, tenamos que distinguir con un IF gigantesco entre todas las
etiquetas posibles para realizar una accin u otra. Y todo esto para un documento XML
bastante sencillo. No es dificil darse cuenta de que para un documento XML algo ms
elaborado la complejidad del handler podra dispararse rpidamente, dando lugar a posibles
errores.
Para evitar estos problemas, Android propone una variante del modelo SAX que evita
definir una clase separada para el handler y que permite asociar directamente las acciones a
etiquetas concretas dentro de la estructura del documento XML, lo que alivia en gran
medida los inconvenientes mencionados.
Veamos cmo queda nuestro parser XML utilizando esta variante simplificada de SAX
para Android y despus comentaremos los aspectos ms importantes del mismo.
1
2
3
4
5
6
7
8
9
10
11
12
13

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import android.sax.Element;
import android.sax.EndElementListener;
import android.sax.EndTextElementListener;
import android.sax.RootElement;

245

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

import android.sax.StartElementListener;
import android.util.Xml;
public class RssParserSax2
{
private URL rssUrl;
private Noticia noticiaActual;
public RssParserSax2(String url)
{
try
{
this.rssUrl = new URL(url);
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}
public List<Noticia> parse()
{
final List<Noticia> noticias = new ArrayList<Noticia>();
RootElement root = new RootElement("rss");
Element channel = root.getChild("channel");
Element item = channel.getChild("item");
item.setStartElementListener(new StartElementListener(){
public void start(Attributes attrs) {
noticiaActual = new Noticia();
}
});
item.setEndElementListener(new EndElementListener(){
public void end() {
noticias.add(noticiaActual);
}
});
item.getChild("title").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
noticiaActual.setTitulo(body);
}
});

246

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

item.getChild("link").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
noticiaActual.setLink(body);
}
});
item.getChild("description").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
noticiaActual.setDescripcion(body);
}
});
item.getChild("guid").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
noticiaActual.setGuid(body);
}
});
item.getChild("pubDate").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
noticiaActual.setFecha(body);
}
});
try
{
Xml.parse(this.getInputStream(),
Xml.Encoding.UTF_8,
root.getContentHandler());
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
return noticias;
}
private InputStream getInputStream()
{
try
{
return rssUrl.openConnection().getInputStream();

247

108
109
110
111
112
113
114

}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}

Debemos atender principalmente al mtodo parse(). En el modelo SAX clsico nos


limitamos a instanciar al handler definido en una clase independiente y llamar al
correspondiente mtodo parse() de SAX. Por el contrario, en este nuevo modelo SAX
simplificado de Android, las acciones a realizar para cada evento las vamos a definir en esta
misma clase y adems asociadas a etiquetas concretas del XML. Y para ello lo primero que
haremos ser navegar por la estructura del XML hasta llegar a las etiquetas que nos
interesa tratar y una vez all, asignarle algunos de los listeners disponibles [de apertura
(StartElementListener) o cierre (EndElementListener) de etiqueta] incluyendo las acciones
oportunas. De esta forma, para el elemento <item> navegaremos hasta l obteniendo en
primer lugar el elemento raz del XML (<rss>) declarando un nuevo objeto RootElement y
despus accederemos a su elemento hijo <channel> y a su vez a su elemento hijo <item>,
utilizando en cada paso el mtodo getChild(). Una vez heos llegado a la etiqueta deseada,
asignaremos los listeners necesarios, en nuestro caso uno de apertura de etiqueta y otro de
cierre, donde inicializaremos la noticia actual y la aadiremos a la lista final
respectivamente, de forma anloga a lo que hacamos para el modelo SAX clsico. Para el
resto de etiquetas actuaremos de la misma forma, accediendo a ellas con getChild() y
asignado los listeners necesarios.
Finalmente, iniciaremos el proceso de parsing simplemente llamando al
mtodo parse() definido en la clase android.Util.Xml, al que pasaremos como parmetros el
stream de entrada, la codificacin del documento XML y un handler SAX obtenido
directamente del objeto RootElement definido anteriormente.
Importante indicar, tal como vimos en el apartado anterior, que si queremos ejecutar
nuestro parser sin errores en cualquier versin de Android (sobre todo a partir de la 3.0)
deberemos hacerlo mediante una tarea asncrona. Tienes ms informacin en el apartado
anterior.
Como vemos, este modelo SAX alternativo simplifica la elaboracin del handler necesario
y puede ayudar a evitar posibles errores en el handler y disminuir la complejidad del mismo
para casos en los que el documento XML no sea tan sencillo como el utilizado para estos
ejemplos. Por supuesto, el modelo clsico es tan vlido y eficiente como ste, por lo que la
eleccin entre ambos es cuestin de gustos.

248

Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
En el siguiente artculo pasaremos ya a describir el siguiente de los mtodos de lectura de
XML en Android, llamado Document Object Model (DOM).
Tratamiento de XML en Android (III): DOM
by Sgoliver on 24/01/2011 in Android, Programacin
En el artculo anterior del curso de programacin para Android hablamos sobre SAX, el
primero de los mtodos disponibles en Android para leer ficheros XML desde nuestras
aplicaciones. En este segundo artculo vamos a centrarnos en DOM, otro de los mtodos
clsicos para la lectura y tratamiento de XML.
Cuando comentbamos la filosofa de SAX ya vimos cmo con dicho modelo el
tratamiento del fichero XML se realizaba de forma secuencial, es decir, se iban realizando
las acciones necesarias durante la propia lectura del documento. Sin embargo, con DOM la
estrategia cambia radicalmente. Con DOM, el documento XML se lee completamente antes
de poder realizar ninguna accin en funcin de su contenido. Esto es posible gracias a que,
como resultado de la lectura del documento, el parser DOM devuelve todo su contenido en
forma de una estructura de tipo rbol, donde los distintos elementos del XML se representa
en forma de nodos y su jerarqua padre-hijo se establece mediante relaciones entre dichos
nodos.
Como ejemplo, vemos un ejemplo de XML sencillo y cmo quedara su representacin en
forma de rbol:
1
<noticias>
2
<noticia>
3
<titulo>T1</titulo>
4
<link>L1</link>
5
</noticia>
6
<noticia>
7
<titulo>T2</titulo>
8
<link>L2</link>
9
</noticia>
10 <noticias>
Este XML se traducira en un rbol parecido al siguiente:

249

Como vemos, este rbol conserva la misma informacin contenida en el fichero XML pero
en forma de nodos y transiciones entre nodos, de forma que se puede navegar fcilmente
por la estructura. Adems, este rbol se conserva persistente en memoria una vez leido el
documento completo, lo que permite procesarlo en cualquier orden y tantas veces como sea
necesario (a diferencia de SAX, donde el tratamiento era secuencial y siempre de principio
a fin del documento, no pudiendo volver atrs una vez finalizada la lectura del XML).
Para todo esto, el modelo DOM ofrece una serie de clases y mtodos que permiten
almacenar la informacin de la forma descrita y facilitan la navegacin y el tratamiento de
la estructura creada.
Veamos cmo quedara nuestro parser utilizando el modelo DOM y justo despus
comentaremos los detalles ms importantes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class RssParserDom


{
private URL rssUrl;
public RssParserDom(String url)
{
try
{
this.rssUrl = new URL(url);
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}

250

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

public List<Noticia> parse()


{
//Instanciamos la fbrica para DOM
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
List<Noticia> noticias = new ArrayList<Noticia>();
try
{
//Creamos un nuevo parser DOM
DocumentBuilder builder = factory.newDocumentBuilder();
//Realizamos lalectura completa del XML
Document dom = builder.parse(this.getInputStream());
//Nos posicionamos en el nodo principal del rbol (<rss>)
Element root = dom.getDocumentElement();
//Localizamos todos los elementos <item>
NodeList items = root.getElementsByTagName("item");
//Recorremos la lista de noticias
for (int i=0; i<items.getLength(); i++)
{
Noticia noticia = new Noticia();
//Obtenemos la noticia actual
Node item = items.item(i);
//Obtenemos la lista de datos de la noticia actual
NodeList datosNoticia = item.getChildNodes();
//Procesamos cada dato de la noticia
for (int j=0; j<datosNoticia.getLength(); j++)
{
Node dato = datosNoticia.item(j);
String etiqueta = dato.getNodeName();
if (etiqueta.equals("title"))
{
String texto = obtenerTexto(dato);
noticia.setTitulo(texto);
}
else if (etiqueta.equals("link"))
{
noticia.setLink(dato.getFirstChild().getNodeValue());
}
else if (etiqueta.equals("description"))

251

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

{
String texto = obtenerTexto(dato);
noticia.setDescripcion(texto);
}
else if (etiqueta.equals("guid"))
{
noticia.setGuid(dato.getFirstChild().getNodeValue());
}
else if (etiqueta.equals("pubDate"))
{
noticia.setFecha(dato.getFirstChild().getNodeValue());
}
}
noticias.add(noticia);
}
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
return noticias;
}
private String obtenerTexto(Node dato)
{
StringBuilder texto = new StringBuilder();
NodeList fragmentos = dato.getChildNodes();
for (int k=0;k<fragmentos.getLength();k++)
{
texto.append(fragmentos.item(k).getNodeValue());
}
return texto.toString();
}
private InputStream getInputStream()
{
try
{
return rssUrl.openConnection().getInputStream();
}
catch (IOException e)
{
throw new RuntimeException(e);

252

111
112
113

}
}
}

Nos centramos una vez ms en el mtodo parse(). Al igual que hacamos para SAX, el
primer paso ser instanciar una nueva fbrica, esta vez de tipo DocumentBuilderFactory, y
posteriormente crear un nuevo parser a partir de ella mediante el
mtodo newDocumentBuilder().
Tras esto, ya podemos realizar la lectura del documento XML llamando al mtod parse() de
nuestro parser DOM, pasndole como parmetro el stream de entrada del fichero. Al hacer
esto, el documento XML se leer completo y se generar la estructura de rbol equivalente,
que se devolver como un objeto de tipo Document. ste ser el objeto que podremos
navegar para realizar eltratamiento necesario del XML.
Para ello, lo primero que haremos ser acceder al nodo principal del rbol (en nuestro caso,
la etiqueta<rss>) utilizando el mtodo getDocumentElement(). Una vez posicionados en
dicho nodo, vamos a buscar todos los nodos cuya etiqueta sea <item>. Esto lo conseguimos
utilizando
el
mtodo
de
bsqueda
por
nombre
de
etiqueta, getElementsByTagName(nombre_de_etiqueta), que devolver una lista (de
tipo NodeList) con todos los nodos hijos del nodo actual cuya etiqueta coincida con la
pasada como parmetro.
Una vez tenemos localizados todos los elementos <item>, que representan a cada noticia,
los vamos a recorrer uno a uno para ir generando todos los objetos Noticia necesarios. Para
cada uno de ellos, se obtendrn los nodos hijos del elemento mediante getChildNodes() y se
recorrern stos obteniendo su texto y almacenndolo en el atributo correspondiente del
objeto Noticia. Para saber a qu etiqueta corresponde cada nodo hijo utilizamos el
mtodo getNodeName().
Merece la pena pararnos un poco en comentar la forma de obtener el texto contenido en un
nodo. Como vimos al principio del artculo en el ejemplo grfico de rbol DOM, el texto de
un nodo determinado se almacena a su vez como nodo hijo de dicho nodo. Este nodo de
texto suele ser nico, por lo que la forma habitual de obtener el texto de un nodo es obtener
su primer nodo hijo y de ste ltimo obtener su valor:
1

String texto = nodo.getFirstChild().getNodeValue();

Sin embargo, en ocasiones, el texto contenido en el nodo viene fragmentado en varios


nodos hijos, en vez de slo uno. Esto ocurre por ejemplo cuando se utilizan en el texto
entidades HTML, como por ejemplo&quot; . En estas ocasiones, para obtener el texto
completo hay que recorrer todos los nodos hijos e ir concatenando el texto de cada uno para
formar el texto completo. Esto es lo que hace nuestra funcin auxiliar obtenerTexto():

253

1
private String obtenerTexto(Node dato)
2
{
3
StringBuilder texto = new StringBuilder();
4
NodeList fragmentos = dato.getChildNodes();
5
6
for (int k=0;k<fragmentos.getLength();k++)
7
{
8
texto.append(fragmentos.item(k).getNodeValue());
9
}
10
11
return texto.toString();
12 }
Como vemos, el modelo DOM nos permite localizar y tratar determinados elementos
concretos del documento XML, sin la necesidad de recorrer todo su contenido de principio
a fin. Adems, a diferencia de SAX, como tenemos cargado en memoria el documento
completo de forma persistente (en forma de objetoDocument), podremos consultar, recorrer
y tratar el documento tantas veces como sea necesario sin necesidad de volverlo a parsear.
En un artculo posterior veremos como todas estas caractersticas pueden ser ventajas o
inconvenientes segn el contexto de la aplicacin y el tipo de XML tratado.
Tratamiento de XML en Android (IV): XmlPull
by Sgoliver on 25/01/2011 in Android, Programacin
En los artculos anteriores dedicados al tratamiento de XML en aplicaciones Android (parte
1, parte 2, parte 3) dentro de nuestro tutorial de programacin Android hemos comentado
ya los modelos SAX y DOM, los dos mtodos ms comunes de lectura de XML soportados
en la plataforma.
En este cuarto artculo nos vamos a centrar en el ltimo mtodo menos conocido, aunque
igual de vlido segn el contexto de la aplicacin, llamado XmlPull. Este mtodo es una
versin similar al modelo StAX(Streaming API for XML), que en esencia es muy parecido
al modelo SAX ya comentado. Y digo muy parecido porque tambin se basa en definir las
acciones a realizar para cada uno de los eventos generados durante la lectura secuencial del
documento XML. Cul es la diferencia entonces?
La diferencia radica principalmente en que, mientras que en SAX no tenamos control sobre
la lectura del XML una vez iniciada (el parser lee automticamente el XML de principio a
fin generando todos los eventos necesarios), en el modelo XmlPull vamos a poder guiar o
intervenir en la lectura del documento, siendo nosotros los que vayamos pidiendo de forma
explcita la lectura del siguiente elemento del XML y respondiendo al resultado ejecutando
las acciones oportunas.

254

Veamos cmo podemos hacer esto:


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

public class RssParserPull


{
private URL rssUrl;
public RssParserPull(String url)
{
try
{
this.rssUrl = new URL(url);
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}
public List<Noticia> parse()
{
List<Noticia> noticias = null;
XmlPullParser parser = Xml.newPullParser();
try
{
parser.setInput(this.getInputStream(), null);
int evento = parser.getEventType();
Noticia noticiaActual = null;
while (evento != XmlPullParser.END_DOCUMENT)
{
String etiqueta = null;
switch (evento)
{
case XmlPullParser.START_DOCUMENT:
noticias = new ArrayList<Noticia>();
break;
case XmlPullParser.START_TAG:
etiqueta = parser.getName();

255

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

if (etiqueta.equals("item"))
{
noticiaActual = new Noticia();
}
else if (noticiaActual != null)
{
if (etiqueta.equals("link"))
{
noticiaActual.setLink(parser.nextText());
}
else if (etiqueta.equals("description"))
{
noticiaActual.setDescripcion(parser.nextText());
}
else if (etiqueta.equals("pubDate"))
{
noticiaActual.setFecha(parser.nextText());
}
else if (etiqueta.equals("title"))
{
noticiaActual.setTitulo(parser.nextText());
}
else if (etiqueta.equals("guid"))
{
noticiaActual.setGuid(parser.nextText());
}
}
break;
case XmlPullParser.END_TAG:
etiqueta = parser.getName();
if (etiqueta.equals("item") && noticiaActual != null)
{
noticias.add(noticiaActual);
}
break;
}
evento = parser.next();
}
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}

256

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

return noticias;
}
private InputStream getInputStream()
{
try
{
return rssUrl.openConnection().getInputStream();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}

Centrndonos una vez ms en el mtodo parse(), vemos que tras crear el nuevo parser
XmlPull y establecer el fichero de entrada en forma de stream
[mediante XmlPull.newPullParser() yparser.setInput()] nos metemos en un buble en el
que iremos solicitando al parser en cada paso el siguiente evento encontrado en la lectura
del XML, utilizando para ello el mtodo parser.next(). Para cada evento devuelto como
resultado consultaremos su tipo mediante el mtodo parser.getEventType()y responderemos
con
las
acciones
oportunas
segn
dicho
tipo
(START_DOCUMENT, END_DOCUMENT,START_TAG, END_TAG).
Una
vez
identificado el tipo concreto de evento, podremos consultar el nombre de la etiqueta del
elemento
XML
mediante parser.getName() y
su
texto
correspondiente
medianteparser.nextText(). En cuanto a la obtencin del texto, con este modelo tenemos la
ventaja de no tener que preocuparnos por recolectar todos los fragmentos de texto
contenidos en el elemento XML, ya quenextText() devolver todo el texto que encuentre
hasta el prximo evento de fin de etiqueta (ENT_TAG).
Y sobre este modelo de tratamiento no queda mucho ms que decir, ya que las acciones
ejecutadas en cada caso son anlogas a las que ya vimos en los artculos anteriores.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
Espero haber sido capaz de mostrar con claridad en estos cuatro artculos todas las
posibilidades existentes a la hora de leer y procesar documentos XML en aplicaciones
Android.
Alternativas para leer y escribir XML (y otros ficheros) en Android

257

by Sgoliver on 23/01/2012 in Android, Programacin


Un artculo rpido antes de seguir con ms temas del Curso de Programacin Android.
Hace ya algn tiempo dedicamos varios artculos (I, II, III, IV) al tratamiento de ficheros
XML en Android y vimos varias alternativas a la hora de leer (parsear) ficheros de este
tipo. En todos los ejemplos decid utilizar como entrada un fichero online, que obtenamos
desde una determinada URL, porque pens que sera el caso ms tpico que bamos a
necesitar en la mayora de aplicaciones. Sin embargo, desde entonces han sido muchas las
consultas que me han llegado relativas a cmo utilizar estos mtodos para leer un fichero
XML que se encuentre en un recurso local. Adicionalmente tambin he recibido muchos
comentarios consultando las alternativas existentes para escribir ficheros XML. Pues bien,
aunque las alternativas son muchas, voy a dedicar este pequeo artculo a intentar resolver
ambas dudas.
Escribir ficheros XML en Android
Para escribir ficheros XML sin meternos en muchas complicaciones innecesarias se me
ocurren bsicamente dos formas. La primera de ellas, y la ms sencilla y directa, es
construir manualmente el XML utilizando por ejemplo un objeto StringBuilder y tras esto
escribir el resultado a un fichero en memoria interna o externa tal como ya vimos en los
artculos dedicados a tratamiento de ficheros (I y II). Esto quedara de una forma similar a
la siguiente:
1
//Creamos un fichero en la memoria interna
2
OutputStreamWriter fout =
3
new OutputStreamWriter(
4
openFileOutput("prueba.xml",
5
Context.MODE_PRIVATE));
6
7
StringBuilder sb = new StringBuilder();
8
9
//Construimos el XML
10 sb.append("");
11 sb.append("" + "Usuario1" + "");
12 sb.append("" + "ApellidosUsuario1" + "");
13 sb.append("");
14
15 //Escribimos el resultado a un fichero
16 fout.write(sb.toString());
17 fout.close();
Como mtodo alternativo tambin podemos utilizar el serializador XML incluido con la
API XmlPull. Aunque no es un mtodo tan directo como el anterior no deja de ser bastante
intuitivo. Los pasos para conseguir esto sern crear el objeto XmlSerializer, crear el fichero

258

de salida, asignar el fichero como salida del serializador y construir el XML mediante los
mtodos startTag(), text() yendTag() pasndoles los nombres de etiqueta y contenidos de
texto correspondientes (aunque existen ms mtodos, por ejemplo para escribir atributos de
una etiqueta, stos tres son los principales). Finalmente deberemos llamar
a endDocument() para finalizar y cerrar nuestro documento XML. Un ejemplo equivalente
al anterior utilizando este mtodo sera el siguiente:
1
//Creamos el serializer
2
XmlSerializer ser = Xml.newSerializer();
3
4
//Creamos un fichero en memoria interna
5
OutputStreamWriter fout =
6
new OutputStreamWriter(
7
openFileOutput("prueba_pull.xml",
8
Context.MODE_PRIVATE));
9
10 //Asignamos el resultado del serializer al fichero
11 ser.setOutput(fout);
12
13 //Construimos el XML
14 ser.startTag("", "usuario");
15
16 ser.startTag("", "nombre");
17 ser.text("Usuario1");
18 ser.endTag("", "nombre");
19
20 ser.startTag("", "apellidos");
21 ser.text("ApellidosUsuario1");
22 ser.endTag("", "apellidos");
23
24 ser.endTag("", "usuario");
25
26 ser.endDocument();
27
28 fout.close();
As de sencillo, no merece la pena complicarse la vida con otros mtodos ms complicados.
Leer ficheros XML en Android desde recursos locales
Para leer ficheros XML desde un recurso local se me ocurren varias posibilidades, por
ejemplo leerlo desde la memoria interna/externa del dispositivo, leer un XML desde un
recurso XML (carpeta /res/xml), desde un recurso Raw (carpeta /res/raw), o directamente
desde la carpeta /assets de nuestro proyecto. Salvo en el segundo caso (recurso XML), en
todos los dems la solucin va a pasar por conseguir una referencia de tipo InputStream (os

259

recuerdo que cualquiera de los mtodos que vimos para leer un XML partan de una
referencia de este tipo) a partir de nuestro fichero o recurso XML, sea cual sea su
localizacin.
As, por ejemplo, si el fichero XML est almacenado en la memoria interna de nuestro
dispositivo, podramos acceder a l mediante el mtodo openFileInput() tal como vimos en
los artculos dedicados a tratamiento de ficheros. Este mtodo devuelve un objeto de
tipo FileInputStream, que al derivar deInputStream podemos utilizarlo como entrada a
cualquiera de los mecanismos de lectura de XML (SAX,DOM, XmlPull).
1
//Obtenemos la referencia al fichero XML de entrada
2
FileInputStream fil = openFileInput("prueba.xml");
3
4
//DOM (Por ejemplo)
5
DocumentBuilderFactory factory =
6
DocumentBuilderFactory.newInstance();
7
8
DocumentBuilder builder = factory.newDocumentBuilder();
9
Document dom = builder.parse(fil);
10
11 //A partir de aqu se tratara el rbol DOM como siempre.
12 //Por ejemplo:
13 Element root = dom.getDocumentElement();
14
15 //...
En el caso de encontrarse el fichero como recurso Raw, es decir, en la carpeta /res/raw,
tendramos
que
obtener
la
referencia
al
fichero
mediante
el
mtodo getRawResource() pasndole como parmetro el ID de recurso del fichero. Esto
nos devuelve directamente el stream de entrada en forma de InputStream.
1
//Obtenemos la referencia al fichero XML de entrada
2
InputStream is = getResources().openRawResource(R.raw.prueba);
3
4
//DOM (Por ejemplo)
5
DocumentBuilderFactory factory =
6
DocumentBuilderFactory.newInstance();
7
8
DocumentBuilder builder = factory.newDocumentBuilder();
9
Document dom = builder.parse(is);
10
11 //A partir de aqu se tratara el rbol DOM como siempre.
12 //Por ejemplo:
13 Element root = dom.getDocumentElement();
14
15 //...

260

Por ltimo, si el XML se encontrara en la carpeta /assets de nuestra aplicacin,


accederamos a l a travs del AssetManager, el cual podemos obtenerlo con el
mtodo getAssets() de nuestra actividad. Sobre este AssetManager tan slo tendremos que
llamar al mtodo open() con el nombre del fichero para obtener una referencia de
tipo InputStream a nuestro XML.
1
//Obtenemos la referencia al fichero XML de entrada
2
AssetManager assetMan = getAssets();
3
InputStream is = assetMan.open("prueba_asset.xml");
4
5
//DOM (Por ejemplo)
6
DocumentBuilderFactory factory =
7
DocumentBuilderFactory.newInstance();
8
9
DocumentBuilder builder = factory.newDocumentBuilder();
10 Document dom = builder.parse(is);
11
12 //A partir de aqu se tratara el rbol DOM como siempre.
13 //Por ejemplo:
14 Element root = dom.getDocumentElement();
15
16 //...
El ltimo caso, algo distinto a los anteriores, pasara por leer el XML desde un recurso
XML localizado en la carpeta /res/xml de nuestro proyecto. Para acceder a un recurso de
este tipo debemos utilizar el mtodogetXml() al cual debemos pasarle como parmetro el
ID de recurso del fichero XML. Esto nos devolver un objeto XmlResourceParser, que no
es ms que un parser de tipo XmlPull como el que ya comentamosen su momento. Por
tanto, la forma de trabajar con este parser ser idntica a la que ya conocemos, por ejemplo:
1
//Obtenemos un parser XmlPull asociado a nuestro XML
2
XmlResourceParser xrp = getResources().getXml(R.xml.prueba);
3
4
//A partir de aqu utilizamos la variable 'xrp' como
5
//cualquier otro parser de tipo XmlPullParser. Por ejemplo:
6
7
int evento = xrp.getEventType();
8
9
if(evento == XmlPullParser.START_DOCUMENT)
10
Log.i("XmlTips", "Inicio del documento");
11
12 //...
Por ltimo, indicar que todas estas formas de acceder a un fichero en Android no se limitan
nicamente a los de tipo XML, sino que pueden utilizarse para leer cualquier otro tipo de
ficheros.

261

Localizacin Geogrfica en Android


Localizacin geogrfica en Android (I)
by Sgoliver on 27/04/2011 in Android, Programacin
La localizacin geogrfica en Android es uno de esos servicios que, a pesar de requerir
poco cdigo para ponerlos en marcha, no son para nada intuitivos ni fciles de llegar a
comprender por completo. Y esto no es debido al diseo de la plataforma Android en s,
sino a la propia naturaleza de este tipo de servicios. Por un lado, existen multitud de formas
de obtener la localizacin de un dispositivo mvil, aunque la ms conocida y popular es la
localizacin por GPS, tambin es posible obtener la posicin de un dispositivo por ejemplo
a travs de las antenas de telefona mvil o mediante puntos de acceso Wi-Fi cercanos, y
todos cada uno de estos mecanismos tiene una precisin, velocidad y consumo de recursos
distinto. Por otro lado, el modo de funcionamiento de cada uno de estos mecanismos hace
que su utilizacin desde nuestro cdigo no sea todo lo directa e intuitiva que se deseara.
Iremos comentando todo esto a lo largo del artculo, pero vayamos paso a paso.
Qu mecanismos de localizacin tenemos disponibles?
Lo primero que debe conocer una aplicacin que necesite obtener la localizacin geogrfica
es qu mecanismos de localizacin (proveedores de localizacin, o location providers)
tiene disponibles en el dispositivo. Como ya hemos comentado, los ms comunes sern el
GPS y la localizacin mediante la red de telefona, pero podran existir otros segn el tipo
de dispositivo.
La forma ms sencilla de saber los proveedores disponibles en el dispositivo es mediante
una llamada al mtodo getAllProviders() de la clase LocationManager, clase principal en la
que nos basaremos siempre a la hora de utilizar la API de localizacin de Android. Para
ello,
obtendremos
una
referencia
allocation
manager llamando
a getSystemService(LOCATION_SERVICE), y posteriormente obtendremos la lista de
proveedores mediante el mtodo citado para obtener la lista de nombres de los proveedores:
LocationManager
locManager
=
1
(LocationManager)getSystemService(LOCATION_SERVICE);
2
List<String> listaProviders = locManager.getAllProviders();
Una vez obtenida la lista completa de proveedores disponibles podramos acceder a las
propiedades de cualquiera de ellos (precisin, coste, consumo de recursos, o si es capaz de
obtener la altitud, la velocidad, ). As, podemos obtener una referencia al provider
mediante su nombre llamando al mtodogetProvider(nombre) y posteriormente utilizar los
mtodos disponibles para conocer sus propiedades, por ejemplo getAccuracy() para saber
su precisin (tenemos disponibles las constantesCriteria.ACCURACY_FINE para precisin

262

alta, y Criteria.ACCURACY_COARSE para precisin media),supportsAltitude() para


saber si obtiene la altitud, o getPowerRequirement() para obtener el nivel de consumo de
recursos del proveedor. La lista completa de mtodos para obtener las caractersticas de un
proveedor se puede consultar en la documentacin oficial de la clase LocationProvider.
LocationManager
locManager
=
1
(LocationManager)getSystemService(LOCATION_SERVICE);
2
List<String> listaProviders = locManager.getAllProviders();
3
4
LocationProvider provider = locManager.getProvider(listaProviders.get(0));
5
int precision = provider.getAccuracy();
6
boolean obtieneAltitud = provider.supportsAltitude();
7
int consumoRecursos = provider.getPowerRequirement();
Al margen de esto, hay que tener en cuenta que la lista de proveedores devuelta por el
mtodogetAllProviders() contendr todos los proveedores de localizacin conocidos por el
dispositivo, incluso si stos no estn permitidos (segn los permisos de la aplicacin) o no
estn activados, por lo que esta informacin puede que no nos sea de mucha ayuda.
Qu proveedor de localizacin es mejor para mi aplicacin?
Android proporciona un mecanismo alternativo para obtener los proveedores que cumplen
unos determinados requisitos entre todos los disponibles. Para ello nos permite definir un
criterio de bsqueda, mediante un objeto de tipo Criteria, en el que podremos indicar las
caractersticas mnimas del proveedor que necesitamos utilizar (podis consultar
la documentacin oficial de la clase Criteria para saber todas las caractersticas que
podemos definir). As, por ejemplo, para buscar uno con precisin alta y que nos
proporcione la altitud definiramos el siguiente criterio de bsqueda:
1 Criteria req = new Criteria();
2 req.setAccuracy(Criteria.ACCURACY_FINE);
3 req.setAltitudeRequired(true);
Tras esto, podremos utilizar los mtodos getProviders() o getBestProvider() para obtener la
lista de proveedores que se ajustan mejor al criterio definido o el proveedor que mejor se
ajusta a dicho criterio, respectivamente. Adems, ambos mtodos reciben un segundo
parmetro que indica si queremos que slo nos devuelvan proveedores que estn activados
actualmente. Veamos cmo se utilizaran estos mtodos:
1 //Mejor proveedor por criterio
2 String mejorProviderCrit = locManager.getBestProvider(req, false);
3
4 //Lista de proveedores por criterio
5 List<String> listaProvidersCrit = locManager.getProviders(req, false);
Con esto, ya tenemos una forma de seleccionar en cada dispostivo aquel proveedor que
mejor se ajusta a nuestras necesidades.

263

Est disponible y activado un proveedor determinado?


Aunque, como ya hemos visto, tenemos la posibilidad de buscar dinmicamente
proveedores de localizacin segn un determinado criterio de bsqueda, es bastante comn
que nuestra aplicacin est diseada para utilizar uno en concreto, por ejemplo el GPS, y
por tanto necesitaremos algn mecanismo para saber si ste est activado o no en el
dispositivo. Para esta tarea, la clase LocationManager nos proporciona otro mtodo
llamado isProviderEnabled() que nos permite hacer exactamente lo que necesitamos. Para

ello, debemos pasarle el nombre del provider que queremos consultar. Para los ms
comunes tenemos varias constantes ya definidas:
LocationManager.NETWORK_PROVIDER. Localizacin por la red de telefona.
LocationManager.GPS_PROVIDER. Localizacin por GPS.
De esta forma, si quisiramos saber si el GPS est habilitado o no en el dispositivo (y
actuar en consecuencia), haramos algo parecido a lo siguiente:
1
2
3
4

//Si el GPS no est habilitado


if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
mostrarAvisoGpsDeshabilitado();
}

En el cdigo anterior, verificamos si el GPS est activado y en caso negativo mostramos al


usuario un mensaje de advertencia. Este mensaje podramos mostralo sencillamente en
forma de notificacin de tipotoast, pero en el prximo artculo sobre localizacin veremos
cmo podemos, adems de informar de que el GPS est desactivado, invitar al usuario a
activarlo dirigindolo automticamente a la pantalla de configuracin del dispositivo.
El GPS ya est activado, y ahora qu?
Una vez que sabemos que nuestro proveedor de localizacin favorito est activado, ya
estamos en disposicin de intentar obtener nuestra localizacin actual. Y aqu es donde las
cosas empiezan a ser menos intuitivas. Para empezar, en Android no existe ningn mtodo
del tipo obtenerPosicinActual(). Obtener la posicin a travs de un dispositivo de
localizacin como por ejemplo el GPS no es una tarea inmediata, sino que puede requerir
de un cierto tiempo de procesamiento y de espera, por lo que no tendra sentido
proporcionar un mtodo de ese tipo.
Si buscamos entre los mtodos disponibles en la clase LocationManager, lo ms parecido
que encontramos es un mtodo llamado getLastKnownLocation(String provider), que como
se puede suponer por su nombre, nos devuelve la ltima posicin conocida del dispositivo
devuelta por un provider determinado. Es importante entender esto: este mtodo NO
devuelve la posicin actual, este mtodo NO solicita una nueva posicin al proveedor de
localizacin, este mtodo se limita a devolver la ltima posicin que se obtuvo a travs del

264

proveedor que se le indique como parmetro. Y esta posicin se pudo obtener hace pocos
segundos, hace das, hace meses, o incluso nunca (si el dispositivo ha estado apagado, si
nunca se ha activado el GPS, ). Por tanto, cuidado cuando se haga uso de la posicin
devuelta por el mtodo getLastKnownLocation().
Entonces, de qu forma podemos obtener la posicin real actualizada? Pues la forma
correcta de proceder va a consistir en algo as como activar el proveedor de localizacin y
suscribirnos a sus notificaciones de cambio de posicin. O dicho de otra forma, vamos a
suscribirnos al evento que se lanza cada vez que un proveedor recibe nuevos datos sobre la
localizacin actual. Y para ello, vamos a darle previamente unas indicaciones (que no
ordenes, ya veremos esto en el prximo artculo) sobre cada cuanto tiempo o cada cuanta
distacia recorrida necesitaramos tener una actualizacin de la posicin.
Todo esto lo vamos a realizar mediante una llamada al mtodo requestLocationUpdates(),

al que deberemos pasar 4 parmetros distintos:


Nombre del proveedor de localizacin al que nos queremos suscribir.
Tiempo mnimo entre actualizaciones, en milisegundos.
Distancia mnima entre actualizaciones, en metros.
Instancia de un objeto LocationListener, que tendremos que implementar previamente para
definir las acciones a realizar al recibir cada nueva actualizacin de la posicin.
Tanto el tiempo como la distancia entre actualizaciones pueden pasarse con valor 0, lo que
indicara que ese criterio no se tendr en cuenta a la hora de decidir la frecuencia de
actualizaciones. Si ambos valores van a cero, las actualizaciones de posicin se recibirn
tan pronto y tan frecuentemente como estn disponibles. Adems, como ya hemos indicado,
es importante comprender que tanto el tiempo como la distancia especificadas se
entendern como simples indicaciones o pistas para el proveedor (al menos para
versiones no recientes de Android), por lo que puede que no se cumplan de forma estricta.
En el prximo artculo intentaremos ver esto con ms detalle para entenderlo mejor. Por
ahora nos basta con esta informacin.

En cuanto al listener, ste ser del tipo LocationListener y contendr una serie de mtodos
asociados a los distintos eventos que podemos recibir del proveedor:
onLocationChanged(location). Lanzado cada vez que se recibe una actualizacin de la
posicin.
onProviderDisabled(provider). Lanzado cuando el proveedor se deshabilita.
onProviderEnabled(provider). Lanzado cuando el proveedor se habilita.
onStatusChanged(provider, status, extras). Lanzado cada vez que el proveedor cambia su
estado,
que
puede
variar
entre OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE, AVAILABLE.
Por nuestra parte, tendremos que implementar cada uno de estos mtodos para responder a
los eventos del proveedor, sobre todo al ms interesante, onLocationChanged(), que se

265

ejecutar cada vez que se recibe una nueva localizacin desde el proveedor. Veamos un
ejemplo de cmo implementar un listener de este tipo:
1
LocationListener locListener = new LocationListener() {
2
3
public void onLocationChanged(Location location) {
4
mostrarPosicion(location);
5
}
6
7
public void onProviderDisabled(String provider){
8
lblEstado.setText("Provider OFF");
9
}
10
11
public void onProviderEnabled(String provider){
12
lblEstado.setText("Provider ON");
13
}
14
15
public void onStatusChanged(String provider, int status, Bundle extras){
16
lblEstado.setText("Provider Status: " + status);
17
}
18 };
Como podis ver, en nuestro caso de ejemplo nos limitamos a mostrar al usuario la
informacin recibida en el evento, bien sea un simple cambio de estado, o una nueva
posicin, en cuyo caso llamamos al mtodo auxiliar mostrarPosicion() para refrescar todos
los datos de la posicin en la pantalla. Para este ejemplo hemos construido una interfaz muy
sencilla, donde se muestran 3 datos de la posicin (latitud, longitud y precisin) y un campo
para mostrar el estado del proveedor. Adems, se incluyen dos botones para comenzar y
detener la recepcin de nuevas actualizaciones de la posicin. No incluyo aqu el cdigo de
la interfaz para no alargar ms el artculo, pero puede consultarse en el cdigo
fuente suministrado al final del texto. El aspecto de nuestra ventana es el siguiente:

266

En el mtodo mostrarPosicion() nos vamos a limitar a mostrar los distintos datos de la


posicin pasada como parmetro en los controles correspondientes de la interfaz, utilizando
para ello los mtodos proporcionados por la clase Location. En nuestro caso
utilizaremos getLatitude(), getAltitude() ygetAccuracy() para obtener la latitud, longitud y
precisin respectivamente. Por supuesto, hay otros mtodos disponibles en esta clase para
obtener la altura, orientacin, velocidad, etc que se pueden consultar en
la documentacin oficial de la clase Location. Veamos el cdigo:
1
private void mostrarPosicion(Location loc) {
2
if(loc != null)
3
{
4
lblLatitud.setText("Latitud: " + String.valueOf(loc.getLatitude()));
5
lblLongitud.setText("Longitud: " + String.valueOf(loc.getLongitude()));
6
lblPrecision.setText("Precision: " + String.valueOf(loc.getAccuracy()));
7
}
8
else
9
{
10
lblLatitud.setText("Latitud: (sin_datos)");
11
lblLongitud.setText("Longitud: (sin_datos)");
12
lblPrecision.setText("Precision: (sin_datos)");
13
}
14 }
Por qu comprobamos si la localizacin recibida es null? Como ya hemos dicho
anteriomente, no tenemos mucho control sobre el momento ni la frecuencia con la que
vamos a recibir las actualizaciones de posicin desde un proveedor, por lo que tampoco
estamos seguros de tenerlas disponibles desde un primer momento. Por este motivo, una
tcnica
bastante
comn
es
utilizar
la
posicin
que
devuelve
el
mtodo getLastKnownLocation() como posicin provisional de partida y a partir de ah
esperar a recibir la primera actualizacin a travs del LocationListener. Y como tambin
dijimos, la ltima posicin conocida podra no existir en el dispositivo, de ah que
comprobemos si el valor recibido es null. Para entender mejor esto, a continuacin tenis la
estructura completa del mtodo que lanzamos al comenzar la recepcin de actualizaciones
de posicin (al pulsar el botn Activar de la interfaz):
1
private void comenzarLocalizacion()
2
{
3
//Obtenemos una referencia al LocationManager
4
locManager =
5
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
6
7
//Obtenemos la ltima posicin conocida
8
Location loc =
9
locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

267

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

//Mostramos la ltima posicin conocida


mostrarPosicion(loc);
//Nos registramos para recibir actualizaciones de la posicin
locListener = new LocationListener() {
public void onLocationChanged(Location location) {
mostrarPosicion(location);
}
//Resto de mtodos del listener
//...
};
locManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 30000, 0, locListener);
}

Como se puede observar, al comenzar la recepcin de posiciones, mostramos en primer


lugar la ltima posicin conocida, y posteriormente solicitamos al GPS actualizaciones de
poscin cada 30 segundos.
Por ltimo, nos quedara nicamente comentar cmo podemos detener la recepcin de
nuevas actualizaciones de posicin. Algo que es tan sencillo como llamar al
mtodo removeUpdates() del location manager. De esta forma, la implementacin del
botn Desactivar sera tan sencilla como esto:
1 btnDesactivar.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View v) {
4
locManager.removeUpdates(locListener);
5
}
6 });
Con esto habramos concluido nuestra aplicacin de ejemplo. Sin embargo, si descargis el
cdigo completo del artculo y ejecutis la aplicacin en el emulador veris que, a pesar
funcionar todo correctamente, slo recibiris una lectura de la posicin (incluso puede que
ninguna). Esto es debido a que la ejecucin y prueba de aplicaciones de este tipo en el
emulador de Android, al no tratarse de un dispositivo real y no estar disponible un receptor
GPS, requiere de una serie de pasos adicionales parasimular cambios en la posicin del
dispositivo.
Todo esto, adems de algunas aclaraciones que nos han quedado pendientes en esta primera
entrega sobre localizacin, lo veremos en el prximo artculo.
Localizacin geogrfica en Android (II)

268

by Sgoliver on 08/05/2011 in Android, Programacin


En el artculo anterior del curso de programacin en Android comentamos los pasos bsicos
necesarios para construir aplicaciones que accedan a la posicin geogrfica del dispositivo.
Ya comentamos algunas particularidades de este servicio de la API de Android, pero
dejamos en el tintero algunas aclaraciones ms detalladas y un tema importante y
fundamental, como es la depuracin de este tipo de aplicaciones que manejan datos de
localizacin. En este nuevo artculo intentar abarcar estos temas y dejar para un tercer

artculo algunas otras caractersticas ms avanzadas de los servicios de localizacin.


Como base para este artculo voy a utilizar la misma aplicacin de ejemplo que construimos
en la anterior entrega, haciendo tan slo unas pequeas modificaciones:
Reduciremos el tiempo entre actualizaciones de posicin a la mitad, 15 segundos, para
evitar tiempos de espera demasiado largos durante la ejecucin de la aplicacin.
Generaremos algunos mensajes de log en puntos clave del cdigo para poder estudiar con
ms detalle el comportamiento de la aplicacin en tiempo de ejecucin.
La generacin de mensajes de log resulta ser una herramienta perfecta a la hora de depurar
aplicaciones del tipo que estamos tratando, ya que en estos casos el cdigo no facilita
demasiado la depuracin tpica paso a paso que podemos realizar en otras aplicaciones.
En nuestro caso de ejemplo slo vamos a generar mensajes de log cuando ocurran dos
ciscunstancias:

Cuando el proveedor de localizacin cambie de estado, evento onStatusChanged(),


mostraremos el nuevo estado.
Cuando se reciba una nueva actualizacin de la posicin, evento onLocationChanged(),
mostraremos las nuevas coordenadas recibidas.
Nuestro cdigo quedara por tanto tal como sigue:
1 private void actualizarPosicion()
2 {
3
//Obtenemos una referencia al LocationManager
4
locationManager =
5
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
6
7
//Obtenemos la ltima posicin conocida
8
Location location =
9
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
10
11
//Mostramos la ltima posicin conocida
12
muestraPosicion(location);
13
14
//Nos registramos para recibir actualizaciones de la posicin

269

15
locationListener = new LocationListener() {
16
public void onLocationChanged(Location location) {
17
muestraPosicion(location);
18
}
19
public void onProviderDisabled(String provider){
20
lblEstado.setText("Provider OFF");
21
}
22
public void onProviderEnabled(String provider){
23
lblEstado.setText("Provider ON");
24
}
25
public void onStatusChanged(String provider, int status, Bundle extras){
26
Log.i("LocAndroid", "Provider Status: " + status);
27
lblEstado.setText("Provider Status: " + status);
28
}
29
};
30
31
locationManager.requestLocationUpdates(
32
LocationManager.GPS_PROVIDER, 15000, 0, locationListener);
33 }
34
35 private void muestraPosicion(Location loc) {
36
if(loc != null)
37
{
38
lblLatitud.setText("Latitud: " + String.valueOf(loc.getLatitude()));
39
lblLongitud.setText("Longitud: " + String.valueOf(loc.getLongitude()));
40
lblPrecision.setText("Precision: " + String.valueOf(loc.getAccuracy()));
41
Log.i("LocAndroid", String.valueOf(
42
loc.getLatitude() + " - " + String.valueOf(loc.getLongitude())));
43
}
44
else
45
{
46
lblLatitud.setText("Latitud: (sin_datos)");
47
lblLongitud.setText("Longitud: (sin_datos)");
48
lblPrecision.setText("Precision: (sin_datos)");
49
}
50 }
Si ejecutamos en este momento la aplicacin en el emulador y pulsamos el
botn Activar veremos cmo los cuadros de texto se rellenan con la informacin de la
ltima posicin conocida (si existe), pero sin embargo estos datos no cambiarn en ningn
momento ya que por el momento el emulador de Android tan slo cuenta con esa
informacin. Cmo podemos simular la actualizacin de la posicin del dispositivo para
ver si nuestra aplicacin responde exactamente como esperamos?
Pues bien, para hacer esto tenemos varias opciones. La primera de ellas, y la ms sencilla,
es el envo manual de una nueva posicin al emulador de Android, para simular que ste

270

hubiera cambiado su localizacin. Esto se puede realizar desde la perspectiva de DDMS, en


la pestaa Emulator Control, donde podemos encontrar una seccin llamada Location
Controls, mostrada en la imagen siguiente (click para ampliar):

Con estos controles podemos enviar de forma manual al emulador en ejecucin unas
nuevas coordenadas de posicin, para simular que stas se hubieran recibido a travs del
proveedor de localizacin utilizado. De esta forma, si introducimos unas coordenadas de
longitud y latitud y pulsamos el botn Send mientras nuestra aplicacin se ejecuta en el
emulador, esto provocar la ejecucin del eventoonLocationChanged() y por consiguiente
se mostrarn estos mismos datos en sus controles correspondientes de la interfaz, como
vemos en la siguiente captura de pantalla:

Por supuesto, si hacemos nuevos envos de coordenadas desde Eclipse veremos cmo sta
se va actualizando en nuestra aplicacin sin ningn tipo de problamas. Sin embargo este

271

mtodo de manual no resulta demasiado adecuado ni cmodo para probar toda la


funcionalidad de nuestra aplicacin, por ejemplo la actualizacin de posicin cada 15
segundos.
Por ello, Android proporciona otro mtodo algo menos manual de simular cambios
frecuentes de posicin para probar nuestras aplicaciones. Este mtodo consiste en
proporcionar, en vez de una sla coordenada cada vez, una lista de coordenadas que se iran
enviando automticamente al emulador una tras otra a una determinada velocidad, de forma
que podamos simular que el dispositivo se mueve constantemente y que nuestra aplicacin
responde de forma correcta y en el momento adecuado a esos cambios de posicin. Y esta
lista de coordenadas se puede proporcionar de dos formas distintas, en formato GPX o
como fichero KML. Ambos tipos de fichero son ampliamente utilizados por aplicaciones y
dispositivos de localizacin, como GPS, aplicaciones de cartografa y mapas, etc. Los
ficheros KML podemos generarlos por ejemplo a travs de la aplicacin Google Earth o
manualmente con cualquier editor de texto, pero recomiendo consultar los dos enlaces
anteriores para obtener ms informacin sobre cada formato. Para este ejemplo, yo he
generado un fichero KML de muestra con una lista de 1000 posiciones geogrficas al azar.
Para utilizar este fichero como fuente de datos para simular cambios en la posicin del
dispositivo, accedemos nuevamente a los Location Controls y pulsamos sobre la pestaa
GPX o KML, segn el formato que hayamos elegido, que en nuestro caso ser KML.
Pulsamos el botn Load KML para seleccionar nuestro fichero y veremos la lista de
coordenadas como en la siguiente imagen:

272

Una vez cargado el fichero, tendremos disponibles los cuatro botones inferiores para (de
izquierda a derecha):

Avanzar automticamente por la lista.


Ir a la posicin anterior de la lista de forma manual.
Ir a la posicin siguiente de la lista de forma manual.
Establecer la velocidad de avance automtico.
Entendido esto, vamos a utilizar la lista de posiciones para probar nuestra aplicacin. Para
ello, ejecutamos la aplicacin en el emulador, pulsamos nuestro botn Activar para
comenzar a detectar cambios de posicin, y pulsamos el botn de avance automtico (botn
verde) que acabamos de comentar.
Llegados a este punto pueden ocurrir varias cosas, dependiendo del dispositivo o el
emulador que estemos utilizando, y por supuesto de la versin de Android sobre la que
ejecutemos el ejemplo. O bien todo funciona segn lo esperado y empezamos a recibir una
lectura de posicin cada 15 segundos, o bien pueden aparecernos varias actualizaciones con
una frecuencia mucho menor que el periodo especificado. La primera situacin parece
lgica, es lo que habamos pedido. Pero cmo es posible la segunda? No habamos
configurado el LocationListener para obtener actualizaciones de posicin cada 15
segundos? Por qu llegan ms actualizaciones de las esperadas? Antes de contestar a esto,
dejemos por ejemplo que la aplicacin se ejecute durante un minuto sobre un emulador con
versin 2.2 de Android (API 8). Tras unos 60 segundos de ejecucin detenemos la captura
de posiciones pulsando nuestro botn Desactivar.
Ahora vayamos a la ventana de log del DDMS y veamos los mensajes de log ha generado
nuestra aplicacin para intentar saber qu ha ocurrido. En mi caso, los mensajes generados
son los siguientes (en tu caso deben ser muy parecidos):
1
2
3
4
5
6
7
8
9
10
11
12
13
14

05-08 10:50:37.921: INFO/LocAndroid(251): 7.0 - -11.999998333333334


05-08 10:50:38.041: INFO/LocAndroid(251): Provider Status: 2
05-08
10:50:38.901:
INFO/LocAndroid(251):
7.000001666666666
11.999996666666668
05-08
10:50:39.941:
INFO/LocAndroid(251):
7.000001666666666
11.999996666666668
05-08
10:50:41.011:
INFO/LocAndroid(251):
7.000003333333333
11.999995000000002
05-08
10:50:43.011:
INFO/LocAndroid(251):
7.000005000000001
11.999993333333334
05-08
10:50:45.001:
INFO/LocAndroid(251):
7.000006666666667
11.999991666666665
05-08
10:50:46.061:
INFO/LocAndroid(251):
7.000008333333333
11.999989999999999

273

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

05-08
10:50:47.131:
INFO/LocAndroid(251):
7.000008333333333
11.999989999999999
05-08 10:50:47.182: INFO/LocAndroid(251): Provider Status: 1
05-08 10:51:02.232: INFO/LocAndroid(251): 7.000023333333333 - -11.999975
05-08
10:51:02.812:
INFO/LocAndroid(251):
7.000023333333333
11.999973333333333
05-08 10:51:02.872: INFO/LocAndroid(251): Provider Status: 2
05-08
10:51:03.872:
INFO/LocAndroid(251):
7.000024999999999
11.999973333333333
05-08
10:51:04.912:
INFO/LocAndroid(251):
7.000026666666668
11.999971666666665
05-08
10:51:05.922:
INFO/LocAndroid(251):
7.000026666666668
11.999971666666665
05-08 10:51:06.982: INFO/LocAndroid(251): 7.000028333333334 - -11.99997
05-08
10:51:08.032:
INFO/LocAndroid(251):
7.000028333333334
11.999968333333333
05-08 10:51:09.062: INFO/LocAndroid(251): 7.00003 - -11.999968333333333
05-08
10:51:10.132:
INFO/LocAndroid(251):
7.000031666666667
11.999966666666667
05-08
10:51:12.242:
INFO/LocAndroid(251):
7.000033333333333
11.999965000000001
05-08
10:51:13.292:
INFO/LocAndroid(251):
7.000033333333333
11.999963333333335
05-08 10:51:13.342: INFO/LocAndroid(251): Provider Status: 1
05-08
10:51:28.372:
INFO/LocAndroid(251):
7.000048333333333
11.999950000000002
05-08
10:51:28.982:
INFO/LocAndroid(251):
7.000048333333333
11.999950000000002
05-08 10:51:29.032: INFO/LocAndroid(251): Provider Status: 2
05-08
10:51:30.002:
INFO/LocAndroid(251):
7.000050000000001
11.999948333333334
05-08
10:51:31.002:
INFO/LocAndroid(251):
7.000051666666667
11.999946666666665
05-08
10:51:33.111:
INFO/LocAndroid(251):
7.000053333333333
11.999944999999999
05-08
10:51:34.151:
INFO/LocAndroid(251):
7.000053333333333
11.999944999999999
05-08 10:51:35.201: INFO/LocAndroid(251): 7.000055 - -11.999943333333333
05-08 10:51:36.251: INFO/LocAndroid(251): 7.0000566666666675 11.999941666666667
05-08 10:51:37.311: INFO/LocAndroid(251): 7.0000566666666675 11.999941666666667
05-08 10:51:38.361: INFO/LocAndroid(251): 7.0000583333333335 - -11.99994
05-08 10:51:38.431: INFO/LocAndroid(251): Provider Status: 1

Estudiemos un poco este log. Si observamos las marcas de fecha hora vemos varias cosas:

274

Un primer grupo de actualizaciones entre las 10:50:37 y las 10:50:47, con 8 lecturas.
Un segundo grupo de actualizaciones entre las 10:51:02 y las 10:51:13, con 11 lecturas.
Un tercer grupo de actualizaciones entre las 10:51:28 y las 10:51:38, con 10 lecturas.
Entre cada grupo de lecturas transcurren aproximadamente 15 segundos.
Los grupos estn formados por un nmero variable de lecturas.
Por tanto ya podemos sacar algunas conclusiones. Indicar al location listener una
frecuencia de 15 segundos entre actualizaciones no quiere decir que vayamos a tener una
sola lectura cada 15 segundos, sino que al menos tendremos una nueva con dicha
frecuencia. Sin embargo, como podemos comprobar en los logs, las lecturas se recibirn
por grupos separados entre s por el intervalo de tiempo indicado.
Ms conclusiones, ahora sobre el estado del proveedor de localizacin. Si buscamos en el
log los momentos donde cambia el estado del proveedor vemos dos cosas importantes:

Despus de recibir cada grupo de lecturas el proveedor pasa a estado 1


(TEMPORARILY_UNAVAILABLE).
Tras empezar a recibir de nuevo lecturas el proveedor pasa a estado 2 (AVAILABLE).
Estos cambios en el estado de los proveedores de localizacin pueden ayudarnos a realizar
diversas tareas. Un ejemplo tpico es utilizar el cambio de estado a 1 (es decir, cuando se ha
terminado de recibir un grupo de lecturas) para seleccionar la lectura ms precisa del grupo
recibido, algo especialmente importante cuando se estn utilizando varios proveedores de
localizacin simultneamente, cada uno con una precisin distinta.
A modo de resumen, en este artculo hemos visto cmo podemos utilizar las distintas
herramientas que proporciona la plataforma Android y el entorno de desarrollo Eclipse para
simular cambios de posicin del dispositivo durante la ejecucin de nuestras aplicaciones
en el emulador. Tambi hemos visto cmo la generacin de mensajes de log pueden
ayudarnos a depurar este tipo de aplicaciones, y finalmente hemos utilizado esta
herramienta de depuracin para entender mejor el funcionamiento de los location listener y
el comportamiento de los proveedores de localizacin.

Content Providers en Android


Content Providers en Android (I): Construccin
by Sgoliver on 28/08/2011 in Android, Programacin
En este nuevo artculo del Curso de Programacin en Android que estamos publicando
vamos a tratar eltemido [o a veces incomprendido] tema de los Content Providers.

275

Un Content Provider no es ms que el mecanismo proporcionado por la plataforma


Android para permitir compartir informacin entre aplicaciones. Una aplicacin que desee
que todo o parte de la informacin que almacena est disponible de una forma controlada
para el resto de aplicaciones del sistema deber proporcionar un content provider a travs
del cul se pueda realizar el acceso a dicha informacin. Este mecanismo es utilizado por
muchas de las aplicaciones estandard de un dispositivo Android, como por ejemplo la lista
de contactos, la aplicacin de SMS, o el calendario/agenda. Esto quiere decir que
podramos acceder a los datos gestionados por estas aplicaciones desde nuestras propias
aplicaciones Android haciendo uso de los content providers correspondientes.
Son por tanto dos temas los que debemos tratar en este apartado, por un lado a construir
nuevos content providers personalizados para nuestras aplicaciones, y por otro utilizar un
content provider ya existente para acceder a los datos publicados por otras aplicaciones.
En gran parte de la bibliografa sobre programacin en Android se suele tratar primero el
tema del acceso a content providers ya existentes (como por ejemplo el acceso a la lista de
contactos de Android) para despus pasar a la construccin de nuevos content providers
personalizados. Yo sin embargo voy a tratar de hacerlo en orden inverso, ya que me parece
importante conocer un poco el funcionamiento interno de un content provider antes de
pasar a utilizarlo sin ms dentro de nuestras aplicaciones. As, en este primer artculo sobre
el tema veremos cmo crear nuestro propio content provider para compartir datos con otras
aplicaciones, y en el prximo artculo veremos como utilizar este mecanismo para acceder
directamente a datos de terceros.
Empecemos a entrar en materia. Para aadir un content provider a nuestra aplicacin
tendremos que:
1. Crear una nueva clase que extienda a la clase android ContentProvider.
2. Declarar el nuevo content provider en nuestro fichero AndroidManifest.xml
Por supuesto nuestra aplicacin tendr que contar previamente con algn mtodo de
almacenamiento interno para la informacin que queremos compartir. Lo ms comn ser
disponer de una base de datos SQLite, por lo que ser esto lo que utilizar para todos los
ejemplos de este artculo, pero internamente podramos tener los datos almacenados de
cualquier otra forma, por ejemplo en ficheros de texto, ficheros XML, etc. El content
provider sera el mecanismo que nos permita publicar esos datos a terceros de una forma
homogenea y a travs de una interfaz estandarizada.

276

Un primer detalle a tener en cuenta es que los registros de datos proporcionados por un
content provider deben contar siempre con un campo llamado _ID que los identifique de
forma unvoca del resto de registros. Como ejemplo, los registros devueltos por un content
provider de clientes podra tener este aspecto:
_ID

Cliente

Telefono

Email

Antonio

900123456

email1@correo.com

Jose

900123123

email2@correo.com

Luis

900123987

email3@correo.com

Sabiendo esto, es interesante que nuestros datos tambin cuenten internamente con este
campo _ID (no tiene por qu llamarse igual) de forma que nos sea ms sencillo despus
generar los resultados del content provider.
Con todo esto, y para tener algo desde lo que partir, vamos a construir en primer lugar una
aplicacin de ejemplo muy sencilla con una base de datos SQLite que almacene los datos
de una serie de clientes con una estructura similar a la tabla anterior. Para ello seguiremos
los mismos pasos que ya comentamos en los artculos dedicados al tratamiento de bases de
datos SQLite en Android (consultar ndice del curso).
Por volver a recordarlo muy brevemente, lo que haremos ser crear una nueva clase que
extienda aSQLiteOpenHelper, definiremos las sentencias SQL para crear nuestra tabla de
clientes, e implementaremos finalmente los mtodos onCreate() y onUpgrade(). El cdigo
de esta nueva clase, que yo he llamado ClientesSqliteHelper, quedara como sigue:
1
public class ClientesSqliteHelper extends SQLiteOpenHelper {
2
3
//Sentencia SQL para crear la tabla de Clientes
4
String sqlCreate = "CREATE TABLE Clientes " +
5
"(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
6
" nombre TEXT, " +
7
" telefono TEXT, " +
8
" email TEXT )";
9
10
public ClientesSqliteHelper(Context contexto, String nombre,
11
CursorFactory factory, int version) {
12
13
super(contexto, nombre, factory, version);
14
}

277

15
16
@Override
17
public void onCreate(SQLiteDatabase db) {
18
//Se ejecuta la sentencia SQL de creacin de la tabla
19
db.execSQL(sqlCreate);
20
21
//Insertamos 15 clientes de ejemplo
22
for(int i=1; i<=15; i++)
23
{
24
//Generamos los datos de muestra
25
String nombre = "Cliente" + i;
26
String telefono = "900-123-00" + i;
27
String email = "email" + i + "@mail.com";
28
29
//Insertamos los datos en la tabla Clientes
30
db.execSQL("INSERT INTO Clientes (nombre, telefono, email) " +
31
"VALUES ('" + nombre + "', '" + telefono +"', '" + email + "')");
32
}
33
}
34
35
@Override
36
public void onUpgrade(SQLiteDatabase db, int versionAnterior, int versionNueva) {
37
//NOTA: Por simplicidad del ejemplo aqu utilizamos directamente la opcin de
38
//
eliminar la tabla anterior y crearla de nuevo vaca con el nuevo formato.
39
//
Sin embargo lo normal ser que haya que migrar datos de la tabla antigua
40
//
a la nueva, por lo que este mtodo debera ser ms elaborado.
41
42
//Se elimina la versin anterior de la tabla
43
db.execSQL("DROP TABLE IF EXISTS Clientes");
44
45
//Se crea la nueva versin de la tabla
46
db.execSQL(sqlCreate);
47
}
48 }
Como notas relevantes del cdigo anterior:

Ntese el campo _id que hemos incluido en la base de datos de clientes por lo motivos
indicados un poco ms arriba. Este campo lo declaramos como INTEGER PRIMARY KEY
AUTOINCREMENT, de forma que se incremente automticamente cada vez que
insertamos un nuevo registro en la base de datos.
En el mtodo onCreate(), adems de ejecutar la sentencia SQL para crear la tabla Clientes,
tambin inserta varios registros de ejemplo.
Para simplificar el ejemplo, el mtodo onUpgrade() se limita a eliminar la tabla actual y
crear una nueva con la nueva estructura. En una aplicacin real habra que hacer
probblemente la migracin de los datos a la nueva base de datos.

278

Dado que la clase anterior ya se ocupa de todo, incluso de insertar algunos registro de
ejemplo con los que podamos hacer pruebas, la aplicacin principal de ejemplo no mostrar
en principio nada en pantalla ni har nada con la informacin. Esto lo he decidido as para
no complicar el cdigo de la aplicacin innecesariamente, ya que no nos va a interesar el
tratamiento directo de los datos por parte de la aplicacin principal, sino su utilizacin a
travs del content provider que vamos a construir.
Una vez que ya contamos con nuestra aplicacin de ejemplo y su base de datos, es hora de
empezar a construir el nuevo content provider que nos permitir compartir sus datos con
otras aplicaciones.
Lo primero que vamos a comentar es la forma con que se hace referencia en Android a los
content providers. El acceso a un content provider se realiza siempre mediante una URI.
Una URI no es ms que una cadena de texto similar a cualquiera de las direcciones web que
utilizamos en nuestro navegador. Al igual que para acceder a mi blog lo hacemos mediante
la direccin http://www.sgoliver.net, para acceder a un content provider utilizaremos una
direccin similar a content://net.sgoliver.android.contentproviders/clientes.
Las direcciones URI de los content providers estn formadas por 3 partes. En primer lugar
el prefijo content:// que indica que dicho recurso deber ser tratado por un content
provider. En segundo lugar se indica el identificador en s del content provider, tambin
llamado authority. Dado que este dato debe ser nico es una buena prctica utilizar un
authority de tipo nombre de clase java invertido, por ejemplo en mi caso
net.sgoliver.android.contentproviders. Por ltimo, se indica la entidad concreta a la que
queremos acceder dentro de los datos que proporciona el content provider. En nuestro caso
ser simplemente la tabla de clientes, ya que ser la nica existente, pero dado que un
content provider puede contener los datos de varias entidades distintas en este ltimo tramo
de la URI habr que especificarlo. Indicar por ltimo que en una URI se puede hacer
referencia directamente a un registro concreto de la entidad seleccionada. Esto se hara
indicando al final de la URI el ID de dicho registro. Por ejemplo la uri
content://net.sgoliver.android.contentproviders/clientes/23 hara referencia directa al
cliente con _ID = 23.
Todo esto es importante ya que ser nuestro content provider el encargado de
interpretar/parsear la URI completa para determinar los datos que se le estn solicitando.
Esto lo veremos un poco ms adelante.

279

Sigamos. El siguiente paso ser extender a la clase ContentProvider. Si echamos un vistazo


a los mtodos abstractos que tendremos que implementar veremos que tenemos los
siguientes:
onCreate()
query()
insert()
update()
delete()
getType()
El primero de ellos nos servir para inicializar todos los recursos necesarios para el
funcionamiento del nuevo content provider. Los cuatro siguientes sern los mtodos que
permitirn acceder a los datos (consulta, insercin, modificacin y eliminacin,
respectivamente) y por ltimo, el mtodo getType()permitir conocer el tipo de datos
devueltos por el content provider (ms tade intentaremos explicar algo mejor esto ltimo).
Adems de implementar estos mtodos, tambin definiremos una serie de constantes dentro
de nuestra nueva clase provider, que ayudarn posteriormente a su utilizacin. Veamos esto
paso a paso. Vamos a crear una nueva clase ClientesProvider que extienda
de ContentProvider.
Lo primero que vamos a definir es la URI con la que se acceder a nuestro content
provider.
En
mi
caso
he
elegido
la
siguiente:
content://net.sgoliver.android.contentproviders/clientes. Adems, para seguir la prctica
habitual de todos los content providers de Android, encapsularemos adems esta direccin
en un objeto esttico de tipo Uri llamado CONTENT_URI.
1 //Definicin del CONTENT_URI
2 private static final String uri =
3
"content://net.sgoliver.android.contentproviders/clientes";
4
5 public static final Uri CONTENT_URI = Uri.parse(uri);
A continuacin vamos a definir varias constantes con los nombres de las columnas de los
datos proporcionados por nuestro content provider. Como ya dijimos antes existen
columnas predefinidas que deben tener todos los content providers, por ejemplo la columna
_ID. Estas columnas estandar estn definidas en la clase BaseColumns, por lo que para
aadir la nuevas columnas de nuestro content provider definiremos una clase interna
pblica tomando como base la clase BaseColumns y aadiremos nuestras nuevas columnas.
1
//Clase interna para declarar las constantes de columna
2
public static final class Clientes implements BaseColumns
3
{
4
private Clientes() {}
5
6
//Nombres de columnas

280

7
8
9
10

public static final String COL_NOMBRE = "nombre";


public static final String COL_TELEFONO = "telefono";
public static final String COL_EMAIL = "email";
}

Por ltimo, vamos a definir varios atributos privados auxiliares para almacenar el nombre
de la base de datos, la versin, y la tabla a la que acceder nuestro content provider.
1
2
3
4
5

//Base de datos
private ClientesSqliteHelper clidbh;
private static final String BD_NOMBRE = "DBClientes";
private static final int BD_VERSION = 1;
private static final String TABLA_CLIENTES = "Clientes";

Como se indic anteriormente, la primera tarea que nuestro content provider deber hacer
cuando se acceda a l ser interpretar la URI utilizada. Para facilitar esta tarea Android
proporciona una clase llamada UriMatcher, capaz de interpretar determinados patrones en
una URI. Esto nos ser til para determinar por ejemplo si una URI hace referencia a una
tabla genrica o a un registro concreto a travs de su ID. Por ejemplo:

content://net.sgoliver.android.contentproviders/clientes > Acceso genrico a tabla de


clientes
content://net.sgoliver.android.contentproviders/clientes/17 > Acceso directo al cliente
con ID = 17
Para conseguir esto definiremos tambin como miembro de la clase un objeto UriMatcher y
dos nuevas constantes que representen los dos tipos de URI que hemos indicado: acceso
genrico a tabla (lo llamarCLIENTES) o acceso a cliente por ID (lo
llamar CLIENTES_ID). A continuacin inicializaremos el objetoUriMatcher indicndole
el formato de ambos tipos de URI, de forma que pueda diferenciarlos y devolvernos su tipo
(una de las dos constantes definidas, CLIENTES o CLIENTES_ID).
1
//UriMatcher
2
private static final int CLIENTES = 1;
3
private static final int CLIENTES_ID = 2;
4
private static final UriMatcher uriMatcher;
5
6
//Inicializamos el UriMatcher
7
static {
8
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
9
uriMatcher.addURI("net.sgoliver.android.contentproviders", "clientes", CLIENTES);
10
uriMatcher.addURI("net.sgoliver.android.contentproviders", "clientes/#", CLIENTES_ID);
11 }
En el cdigo anterior vemos como mediante el mtodo addUri() indicamos el authority de
nuestra URI, el formato de la entidad que estamos solicitando, y el tipo con el que

281

queremos identificar dicho formato. Ms tarde veremos cmo utilizar esto de forma
prctica.
Bien, pues ya tenemos definidos todos los miembros necesarios para nuestro nuevo content
provider. Ahora toca implementar los mtodos comentados anteriormente.
El primero de ellos es onCreate(). En este mtodo nos limitaremos simplemente a
inicializar nuestra base de datos, a travs de su nombre y versin, y utilizando para ello la
clase ClientesSqliteHelper que creamos al principio del artculo.
1 @Override
2 public boolean onCreate() {
3
4
clidbh = new ClientesSqliteHelper(
5
getContext(), BD_NOMBRE, null, BD_VERSION);
6
7
return true;
8 }
La parte interesante llega con el mtodo query(). Este mtodo recibe como parmetros una
URI, una lista de nombres de columna, un criterio de seleccin, una lista de valores para las
variables utilizadas en el criterio anterior, y un criterio de ordenacin. Todos estos datos
son anlogos a los que comentamos cuando tratamos la consulta de datos en SQLite para
Android, artculo que recomiendo releer si no tenis muy frescos estos conocimientos. El
mtodo query deber devolver los datos solicitados segn la URI indicada y los criterios de
seleccin y ordenacin pasados como parmetro. As, si la URI hace referencia a un cliente
concreto por su ID se deber ser el nico registro devuelto. Si por el contrario es un acceso
genrico a la tabla de clientes habr que realizar la consulta SQL correspondiente a la base
de datos respetanto los criterios pasados como parmetro.
Para disitinguir entre los dos tipos de URI posibles utilizaremos como ya hemos indicado el
objetouriMatcher, utilizando su mtodo match(). Si el tipo devuelto es CLIENTES_ID, es
decir, que se trata de un acceso a un cliente concreto, sustituiremos el criterio de seleccin
por uno que acceda a la tabla de clientes slo por el ID indicado en la URI. Para obtener
este ID utilizaremos el mtodogetLastPathSegment() del objeto uri que extrae el ltimo
elemento de la URI, en este caso el ID del cliente.
Hecho esto, ya tan slo queda realizar la consulta a la base de datos mediante el
mtodo query() deSQLiteDatabase. Esto es sencillo ya que los parmetros son anlogos a
los recibidos en el mtodoquery() del content provider.
1
@Override
2
public Cursor query(Uri uri, String[] projection,
3
String selection, String[] selectionArgs, String sortOrder) {
4

282

5
6
7
8
9
10
11
12
13
14
15
16
17

//Si es una consulta a un ID concreto construimos el WHERE


String where = selection;
if(uriMatcher.match(uri) == CLIENTES_ID){
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = clidbh.getWritableDatabase();
Cursor c = db.query(TABLA_CLIENTES, projection, where,
selectionArgs, null, null, sortOrder);
return c;
}

Como vemos, los resultados se devuelven en forma de Cursor, una vez ms exactamente
igual a como lo hace el mtodo query() de SQLiteDatabase.
Por su parte, los mtodos update() y delete() son completamente anlogos a ste, con la
nica diferencia de que devuelven el nmero de registros afectados en vez de un cursor a
los resultados. Vemos directamente el cdigo:
1
@Override
2
public int update(Uri uri, ContentValues values,
3
String selection, String[] selectionArgs) {
4
5
int cont;
6
7
//Si es una consulta a un ID concreto construimos el WHERE
8
String where = selection;
9
if(uriMatcher.match(uri) == CLIENTES_ID){
10
where = "_id=" + uri.getLastPathSegment();
11
}
12
13
SQLiteDatabase db = clidbh.getWritableDatabase();
14
15
cont = db.update(TABLA_CLIENTES, values, where, selectionArgs);
16
17
return cont;
18 }
19
20 @Override
21 public int delete(Uri uri, String selection, String[] selectionArgs) {
22
23
int cont;
24
25
//Si es una consulta a un ID concreto construimos el WHERE
26
String where = selection;
27
if(uriMatcher.match(uri) == CLIENTES_ID){

283

28
29
30
31
32
33
34
35
36

where = "_id=" + uri.getLastPathSegment();


}
SQLiteDatabase db = clidbh.getWritableDatabase();
cont = db.delete(TABLA_CLIENTES, where, selectionArgs);
return cont;
}

El mtodo insert() s es algo diferente, aunque igual de sencillo. La diferencia en este caso
radica en que debe devolver la URI que hace referencia al nuevo registro insertado. Para
ello, obtendremos el nuevo ID del elemento insertado como resultado del
mtodo insert() de SQLiteDatabase, y posteriormente construiremos la nueva URI mediante
el mtodo auxiliar ContentUris.withAppendedId() que recibe como parmetro la URI de
nuestro content provider y el ID del nuevo elemento.
1
@Override
2
public Uri insert(Uri uri, ContentValues values) {
3
4
long regId = 1;
5
6
SQLiteDatabase db = clidbh.getWritableDatabase();
7
8
regId = db.insert(TABLA_CLIENTES, null, values);
9
10
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, regId);
11
12
return newUri;
13 }
Por ltimo, tan slo nos queda implementar el mtodo getType(). Este mtodo se utiliza
para identificar el tipo de datos que devuelve el content provider. Este tipo de datos se
expresar como un MIME Type, al igual que hacen los navegadores web para determinar el
tipo de datos que estn recibiendo tras una peticin a un servidor. Identificar el tipo de
datos que devuelve un content provider ayudar por ejemplo a Android a determinar qu
aplicaciones son capaces de procesar dichos datos.
Una vez ms existirn dos tipos MIME distintos para cada entidad del content provider,
uno de ellos destinado a cuando se devuelve una lista de registros como resultado, y otro
para cuando se devuelve un registro nico concreto. De esta forma, seguiremos los
siguientes patrones para definir uno y otro tipo de datos:

vnd.android.cursor.item/vnd.xxxxxx > Registro nico


vnd.android.cursor.dir/vnd.xxxxxx > Lista de registros

284

En mi caso de ejemplo, he definido los siguientes tipos:

vnd.android.cursor.item/vnd.sgoliver.cliente
vnd.android.cursor.dir/vnd.sgoliver.cliente
Con esto en cuenta, la implementacin del mtodo getType() quedara como sigue:
1
@Override
2
public String getType(Uri uri) {
3
4
int match = uriMatcher.match(uri);
5
6
switch (match)
7
{
8
case CLIENTES:
9
return "vnd.android.cursor.dir/vnd.sgoliver.cliente";
10
case CLIENTES_ID:
11
return "vnd.android.cursor.item/vnd.sgoliver.cliente";
12
default:
13
return null;
14
}
15 }
Como se puede observar, utilizamos una vez ms el objeto UriMatcher para determinar el
tipo de URI que se est solicitando y en funcin de sta devolvemos un tipo MIME u otro.
Pues bien, con esto ya hemos completado la implementacin del nuevo content provider.
Pero an nos queda un paso ms, como indicamos al principio del artculo. Debemos
declarar el content provider en nuestro fichero AndroidManifest.xml de forma que una vez
instalada la aplicacin en el dispositivo Android conozca la existencia de dicho recurso.
Para ello, bastar insertar un nuevo elemento <provider> dentro de <application> indicando
el nombre del content provider y su authority.
1 <application android:icon="@drawable/icon"
2
android:label="@string/app_name">
3
4
...
5
6
<provider android:name="ClientesProvider"
7
android:authorities="net.sgoliver.android.contentproviders"/>
8
9 </application>
Ahora s hemos completado totalmente la construccin de nuestro nuevo content provider
mediante el cual otras aplicaciones del sistema podrn acceder a los datos almacenados por
nuestra aplicacin.

285

En el siguiente artculo veremos cmo utilizar este nuevo content provider para acceder a
los datos de nuestra aplicacin de ejemplo, y tambin veremos cmo podemos utilizar
alguno de los content provider predefinidos por Android para consultar datos del sistema,
como por ejemplo la lista de contactos o la lista de llamadas realizadas.
Content Providers en Android (II): Utilizacin
by Sgoliver on 31/08/2011 in Android, Programacin
En el artculo anterior del Curso de Programacin en Android vimos como construir
un content providerpersonalizado para permitir a nuestras aplicaciones Android compartir
datos con otras aplicaciones del sistema. En este nuevo artculo vamos a ver el tema desde
el punto de vista opuesto, es decir, vamos a aprender a hacer uso de un content provider ya
existente para acceder a datos de otras aplicaciones. Adems, veremos cmo tambin
podemos acceder a datos del propio sistema Android (logs de llamadas, lista de contactos,
agenda telefnica, bandeja de entrada de sms, etc) utilizando este mismo mecanismo.
Vamos a comenzar explicando cmo podemos utilizar el content provider que
implementamos en el artculo anterior para acceder a los datos de los clientes. Para no
complicar mucho el ejemplo ni hacer ms dificil las pruebas y la depuracin en el emulador
de Android vamos a hacer uso el content provider desde la propia aplicacin de ejemplo
que hemos creado. De cualquier forma, el cdigo necesario sera exactamente igual si lo
hiciramos desde otra aplicacin distinta.
Utilizar un content provider ya existente es muy sencillo, sobre todo comparado con el
laborioso proceso de construccin de uno nuevo. Para comenzar, debemos obtener una
referencia a un Content Resolver, objeto a travs del que realizaremos todas las acciones
necesarias sobre el content provider. Esto es tan fcil como utilizar el
mtodo getContentResolver() desde nuestra actividad para obtener la referencia indicada.
Una vez obtenida la referencia al content resolver, podremos utilizar sus
mtodos query(),update(), insert() y delete() para realizar las acciones equivalentes sobre
el content provider. Por ver varios ejemplos de la utilizacin de estos mtodos aadiremos
a nuestra aplicacin de ejemplo tres botones en la pantalla principal, uno para hacer una
consulta de todos los clientes, otro para insertar registros nuevos, y el ltimo para eliminar
todos los registros nuevos insertados con el segundo botn.
Empecemos por la consulta de clientes. El procedimiento ser prcticamente igual al que
vimosen los artculos de acceso a bases de datos SQLite (consultar el ndice del curso).
Comenzaremos por definir un array con los nombres de las columnas de la tabla que

286

queremos recuperar en los resultados de la consulta, que en nuestro caso sern el ID, el
nombre, el telfono y el email. Tras esto, obtendremos como dijimos antes una referencia
al content resolver y utilizaremos su mtodo query() para obtener los resultados en forma
de cursor. El mtodo query() recibe, como ya vimos en el artculo anterior, la Uri del
content provider al que queremos acceder, el array de columnas que queremos recuperar, el
criterio de seleccin, los argumentos variables, y el criterio de ordenacin de los resultados.
En nuestro caso, para no complicarnos utilizaremos tan slo los dos primeros, pasndole
el CONTENT_URI de nuestro provider y el array de columnas que acabamos de definir.
1
//Columnas de la tabla a recuperar
2
String[] projection = new String[] {
3
Clientes._ID,
4
Clientes.COL_NOMBRE,
5
Clientes.COL_TELEFONO,
6
Clientes.COL_EMAIL };
7
8
Uri clientesUri = ClientesProvider.CONTENT_URI;
9
10 ContentResolver cr = getContentResolver();
11
12 //Hacemos la consulta
13 Cursor cur = cr.query(clientesUri,
14
projection, //Columnas a devolver
15
null,
//Condicin de la query
16
null,
//Argumentos variables de la query
17
null);
//Orden de los resultados
Hecho esto, tendremos que recorrer el cursor para procesar los resultados. Para nuestro
ejemplo, simplemente los escribiremos en un cuadro de texto (txtResultados) colocado bajo
los tres botones de ejemplo. Una vez ms, si tienes dudas sobre cmo recorrer un cursor,
puedes consultar los artculos del curso dedicados al tratamiento de bases de datos SQLite,
por ejemplo ste. Veamos cmo quedara el cdigo:
1
if (cur.moveToFirst())
2
{
3
String nombre;
4
String telefono;
5
String email;
6
7
int colNombre = cur.getColumnIndex(Clientes.COL_NOMBRE);
8
int colTelefono = cur.getColumnIndex(Clientes.COL_TELEFONO);
9
int colEmail = cur.getColumnIndex(Clientes.COL_EMAIL);
10
11
txtResultados.setText("");
12
13
do

287

14
15
16
17
18
19
20
21
22

{
nombre = cur.getString(colNombre);
telefono = cur.getString(colTelefono);
email = cur.getString(colEmail);
txtResultados.append(nombre + " - " + telefono + " - " + email + "\n");
} while (cur.moveToNext());
}

Para insertar nuevos registros, el trabajo ser tambin exactamente igual al que se hace al
tratar directamente con bases de datos SQLite. Rellenaremos en primer lugar un objeto
ContentValues con los datos del nuevo cliente y posteriormente utilizamos el
mtodo insert() pasndole la URI del content provider en primer lugar, y los datos del
nuevo registro como segundo parmetro.
1 ContentValues values = new ContentValues();
2
3 values.put(Clientes.COL_NOMBRE, "ClienteN");
4 values.put(Clientes.COL_TELEFONO, "999111222");
5 values.put(Clientes.COL_EMAIL, "nuevo@email.com");
6
7 ContentResolver cr = getContentResolver();
8
9 cr.insert(ClientesProvider.CONTENT_URI, values);
Por ltimo, y ms sencillo todava, la eliminacin de registros la haremos directamente
utilizando el mtododelete() del content resolver, indicando como segundo parmetro el
criterio de localizacin de los registros que queremos eliminar, que en este caso sern los
que hayamos insertado nuevos con el segundo botn de ejemplo (aquellos con nombre =
ClienteN).
1 ContentResolver cr = getContentResolver();
2
3 cr.delete(ClientesProvider.CONTENT_URI,
4
Clientes.COL_NOMBRE + " = 'ClienteN'", null);
Como muestra grfica, veamos por ejemplo el resultado de la consulta de clientes (primer
botn) en la aplicacin de ejemplo.

288

Con esto, hemos visto lo sencillo que resulta acceder a los datos proporcionados por un
content provider. Pues bien, ste es el mismo mecanismo que podemos utilizar para acceder
a muchos datos de la propia plataforma Android. En la documentacin oficial
del paquete android.provider podemos consultar los datos que tenemos disponibles a travs
de este mecanismo, entre ellos encontramos por ejemplo: el historial de llamadas, la agenda
de contactos y telfonos, las bibliotecas multimedia (audio y video), o el historial y la lista
de favoritos del navegador.
Por ver un ejemplo de acceso a este tipo de datos, vamos a realizar una consulta al historial
de
llamadas
del
dispositivo,
para
lo
que
accederemos
al
content
provider android.provider.CallLog.
En primer lugar vamos a registrar varias llamadas en el emulador de Android, de forma que
los resultados de la consulta al historial de llamadas contenga algunos registros. Haremos
por ejemplo varias llamadas salientes desde el emulador y simularemos varias llamadas
entrantes desde el DDMS. Las primeras son sencillas, simplemente ve al emulador, accede
al telfono,marca y descuelga igual que lo haras en un dispositivo fsico. Y para emular
llamadas entrantes podremos hacerlo una vez ms desde Eclipse, accediendo a la vista del
DDMS. En esta vista, si accedemos a la seccin Emulator Control veremos un apartado
llamado Telephony Actions. Desde ste, podemos introducir un nmero de telfono
origen cualquiera y pulsar el botn Call para conseguir que nuestro emulador reciba una
llamada entrante. Sin aceptar la llamada en elemulador pulsaremos Hang Up para teminar
la llamada simulando as una llamada perdida.

289

Hecho esto, procedemos a realizar la consulta al historial de llamadas utilizando el content


provider indicado, y para ello aadiremos un botn ms a la aplicacin de ejemplo.
Consultando la documentacin del content provider veremos que podemos extraer
diferentes datos relacionados con la lista de llamadas. Nosotros nos quedaremos slo con
dos significativos, el nmero origen o destino de la llamada, y el tipo de llamada (entrante,
saliente, perdida). Los nombres de estas columnas se almacenan en las
constantes Calls.NUMBER y Calls.TYPE respectivamente.
Decidido esto, actuaremos igual que antes. Definiremos el array con las columnas que
queremos recuperar, obtendremos la referencia al content resolver y ejecutaremos la
consulta llamando al mtodoquery(). Por ltimo, recorremos el cursor obtenido y
procesamos los resultados. Igual que antes, lo nico que haremos ser escribir los
resultados al cuadro de texto situado bajo los botones. Veamos el cdigo:
1
String[] projection = new String[] {
2
Calls.TYPE,
3
Calls.NUMBER };
4
5
Uri llamadasUri = Calls.CONTENT_URI;
6
7
ContentResolver cr = getContentResolver();
8
9
Cursor cur = cr.query(llamadasUri,
10
projection, //Columnas a devolver
11
null,
//Condicin de la query
12
null,
//Argumentos variables de la query
13
null);
//Orden de los resultados
14
15 if (cur.moveToFirst())
16 {
17
int tipo;

290

18
String tipoLlamada = "";
19
String telefono;
20
21
int colTipo = cur.getColumnIndex(Calls.TYPE);
22
int colTelefono = cur.getColumnIndex(Calls.NUMBER);
23
24
txtResultados.setText("");
25
26
do
27
{
28
tipo = cur.getInt(colTipo);
29
telefono = cur.getString(colTelefono);
30
31
if(tipo == Calls.INCOMING_TYPE)
32
tipoLlamada = "ENTRADA";
33
else if(tipo == Calls.OUTGOING_TYPE)
34
tipoLlamada = "SALIDA";
35
else if(tipo == Calls.MISSED_TYPE)
36
tipoLlamada = "PERDIDA";
37
38
txtResultados.append(tipoLlamada + " - " + telefono + "\n");
39
40
} while (cur.moveToNext());
41 }
Lo nico fuera de lo normal que hacemos en el cdigo anterior es la decodificacin del
valor del tipo de llamada recuperado, que la hacemos comparando el resultado con las
constantes Calls.INCOMING_TYPE(entrante), Calls.OUTGOING_TYPE (saliente), Calls.
MISSED_TYPE (perdida) proporcionadas por la propia clase provider.
Un ltimo detalle importante. Para que nuestra aplicacin pueda acceder al historial de
llamadas del dispositivo tendremos que incluir en el fichero AndroidManifest.xml el
permiso READ_CONTACTS yREAD_CALL_LOG utilizando
la
clusula <usespermission> correspondiente.
<uses-permission android:name="android.permission.READ_CONTACTS"></uses1 permission>
2 <uses-permission android:name="android.permission.READ_CALL_LOG"></usespermission>
Si ejecutamos la aplicacin y realizamos la consulta podremos ver un resultado similar al
siguiente:

291

Y con esto terminamos con el tema dedicado a los content providers. Espero que os haya
sido til para aprender a incluir esta funcionalidad a vuestras aplicaciones y a utilizar este
mecanismo para acceder a datos propios del sistema.
Notificaciones en Android
Notificaciones en Android (I): Toast
by Sgoliver on 09/06/2011 in Android, Programacin
Un tema rpido antes de seguir con el Curso de Programacin Android que estamos
realizando. En Android existen varias formas de notificar mensajes al usuario, como por
ejemplo los cuadros de dilogo modales o las notificaciones de la bandeja del sistema (o
barra de estado). Pero en este artculo nos vamos a centrar en primer lugar en la forma ms
sencilla de notificacin: los llamados Toast.
Un toast es un mensaje que se muestra en pantalla durante unos segundos al usuario para
luego volver a desaparecer automticamente sin requerir ningn tipo de actuacin por su
parte, y sin recibir el foco en ningn momento (o dicho de otra forma, sin interferir en las
acciones que est realizando el usuario en ese momento). Aunque son personalizables,
aparecen por defecto en la parte inferior de la pantalla, sobre un rectngulo gris ligeramente
translcido. Por sus propias caractersticas, este tipo de notificaciones son ideales para
mostrar mensajes rpidos y sencillos al usuario, pero por el contrario, al no requerir
confirmacin por su parte, no deberan utilizarse para hacer notificaciones demasiado
importantes.
Su utilizacin es muy sencilla, concentrndose toda la funcionalidad en la clase Toast. Esta
clase dispone de un mtodo esttico makeText() al que deberemos pasar como parmetro el
contexto de la actividad, el texto a mostrar, y la duracin del mensaje, que puede tomar los
valores LENGTH_LONG o LENGTH_SHORT, dependiendo del tiempo que queramos
que la notificacin aparezca en pantalla. Tras obtener una referencia al objeto Toast a travs
de este mtodo, ya slo nos quedara mostrarlo en pantalla mediante el mtodo show().

292

Vamos a construir una aplicacin de ejemplo para demostrar el funcionamiento de este tipo
de notificaciones. Y para empezar vamos a incluir un botn que muestre un toast bsico de
la forma que acabamos de describir:
1
btnDefecto.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View arg0) {
4
Toast toast1 =
5
Toast.makeText(getApplicationContext(),
6
"Toast por defecto", Toast.LENGTH_SHORT);
7
8
toast1.show();
9
}
10 });
Si ejecutamos esta sencilla aplicacin en el emulador y pulsamos el botn que acabamos de
aadir veremos como en la parte inferior de la pantalla aparece el mensaje Toast por
defecto, que tras varios segundos desaparecer automticamente.

Como hemos comentado, ste es el comportamiento por defecto de las notificaciones toast,
sin embargo tambin podemos personalizarlo un poco cambiando su posicin en la
pantalla. Para esto utilizaremos el mtodo setGravity(), al que podremos indicar en qu
zona deseamos que aparezca la notificacin. La zona deberemos indicarla con alguna de las
constantes definidas en la clase Gravity: CENTER, LEFT,BOTTOM, o con alguna
combinacin de stas.

293

Para nuestro ejemplo vamos a colocar la notificacin en la zona central izquierda de la


pantalla. Para ello, aadamos un segundo botn a la aplicacin de ejemplo que muestre un
toast con estas caractersticas:
1
btnGravity.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View arg0) {
4
Toast toast2 =
5
Toast.makeText(getApplicationContext(),
6
"Toast con gravity", Toast.LENGTH_SHORT);
7
8
toast2.setGravity(Gravity.CENTER|Gravity.LEFT,0,0);
9
10
toast2.show();
11
}
12 });
Si volvemos a ejecutar la aplicacin y pulsamos el nuevo botn veremos como el toast
aparece en la zona indicada de la pantalla:

Si esto no es suficiente y necesitamos personalizar por completo el aspecto de la


notificacin, Android nos ofrece la posibilidad de definir un layout XML propio para toast,
donde podremos incluir todos los elementos necesarios para adaptar la notificacin a
nuestras necesidades. para nuestro ejemplo vamos a definir un layout sencillo, con una
imagen y una etiqueta de texto sobre un rectngulo gris:
1
2

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout

294

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/lytLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
android:background="#555555"
android:padding="5dip" >
<ImageView android:id="@+id/imgIcono"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/marcador" />
<TextView android:id="@+id/txtMensaje"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="#FFFFFF"
android:paddingLeft="10dip" />
</LinearLayout>

Guardaremos este layout con el nombre toast_layout.xml, y como siempre lo


colocaremos en la carpeta res\layout de nuestro proyecto.
Para asignar este layout a nuestro toast tendremos que actuar de una forma algo diferente a
las anteriores. En primer lugar deberemos inflar el layout mediante un
objeto LayoutInflater, como ya vimos en varias ocasiones al tratar los artculos de interfaz
grfica. Una vez construido el layout modificaremos los valores de los distintos controles
para mostrar la informacin que queramos. En nuestro caso, tan slo modificaremos el
mensaje de la etiqueta de texto, ya que la imagen ya la asignamos de forma esttica en el
layout XML mediante el atributo android:src. Tras esto, slo nos quedar establecer la
duracin de la notificacin con setDuration() y asignar el layout personalizado al toast
mediante el mtodo setView(). Veamos cmo quedara todo el cdigo incluido en un tercer
botn de ejemplo:
1
btnLayout.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View arg0) {
4
Toast toast3 = new Toast(getApplicationContext());
5
6
LayoutInflater inflater = getLayoutInflater();
7
View layout = inflater.inflate(R.layout.toast_layout,
8
(ViewGroup) findViewById(R.id.lytLayout));
9
10
TextView txtMsg = (TextView)layout.findViewById(R.id.txtMensaje);

295

11
12
13
14
15
16
17

txtMsg.setText("Toast Personalizado");
toast3.setDuration(Toast.LENGTH_SHORT);
toast3.setView(layout);
toast3.show();
}
});

Si ejecutamos ahora la aplicacin de ejemplo y pulsamos el nuevo botn, veremos como


nuestro toast aparece con la estructura definida en nuestro layout personalizado.

Como podis comprobar, mostrar notificaciones de tipo Toast en nuestras aplicaciones


Android es algo de lo ms sencillo, y a veces resultan un elemento de lo ms interesante
para avisar al usuario de determinados eventos.
Notificaciones en Android (II): Barra de Estado
by Sgoliver on 20/10/2011 in Android, Programacin
Hace algn tiempo, ya tratamos dentro de este curso un primer mecanismo de
notificaciones disponibles en la plataforma Android: los llamados Toast. Como ya
comentamos, este tipo de notificaciones, aunque resultan tiles y prcticas en muchas
ocasiones, no deberamos utilizarlas en situaciones en las que necesitemos asegurarnos la
atencin del usuario ya que no requiere de ninguna intervencin por su parte, se muestran y
desaparecen automticamente de la pantalla.
En este nuevo artculo vamos a tratar otro tipo de notificaciones algo ms persistentes, las
notificaciones de la barra de estado de Android. Estas notificaciones son las que se
muestran en nuestro dispositivo por ejemplo cuando recibimos un mensaje SMS, cuando

296

tenemos actualizaciones disponibles, cuando tenemos el reproductor de msica abierto en


segundo plano, Estas notificaciones constan de un icono y un texto mostrado en la barra
de estado superior, y adicionalmente un mensaje algo ms descriptivo y una marca de
fecha/hora que podemos consultar desplegando la bandeja del sistema. A modo de ejemplo,
cuando tenemos una llamada perdida en nuestro terminal, se nos muestra por un lado un
icono en la barra de estado superior (ms un texto que aparece durante unos segundos).

y un mensaje con ms informacin al desplegar la bandeja del sistema, donde en este


caso concreto se nos informa del evento que se ha producido (Missed call), el nmero de
telfono asociado, y la fecha/hora del evento. Adems, al pulsar sobre la notificacin se nos
dirige automticamente al historial de llamadas.

Pues bien, aprendamos a utilizar este tipo de notificaciones en nuestras aplicaciones.


Vamos a construir para ello una aplicacin de ejemplo, como siempre lo ms sencilla
posible para centrar la atencin en lo realmente importante. En este caso, el ejemplo va a
consistir en un nico botn que genere una notificacin de ejemplo en la barra de estado,
con todos los elementos comentados y con la posibilidad de dirigirnos a la propia
aplicacin de ejemplo cuando se pulse sobre ella.
Para generar notificaciones en la barra de estado del sistema vamos a utilizar una clase
incluida en la librera de compatibilidad android-support-v4.jar que ya hemos utilizado en
otras ocasiones y que debe estar incluida por defecto en vuestro proyecto si lo habis
creado con alguna versin reciente del plugin de Eclipse. Esto es as para asegurar la
compatibilidad con versiones de Android antiguas, ya que las notificaciones son un
elemento que han sufrido bastantes cambios en las versiones ms recientes. La clase en
cuestin se llama NotificationCompat.Builder y lo que tendremos que hacer ser crear un
nuevo objeto de este tipo pasndole el contexto de la aplicacin y asignar todas las
propiedades que queramos mediante sus mtodos set().
En
primer
lugar
estableceremos
los
iconos a
mostrar
mediante
los
mtodos setSmallIcon() ysetLargeIcon() que se corresponden con los iconos mostrados a la
derecha y a la izquierda del contenido de la notificacin en versiones recientes de Android.
En versiones ms antiguas tan slo se mostrar el icono pequeo a la izquierda de la
notificacin. Adems, el icono pequeo tambin se mostrar en la barra de estado superior.

297

A continuacin estableceremos el ttulo y el texto de la notificacin, utilizando para ello los


mtodossetContentTitle() y setContentText().
Por ltimo, estableceremos el ticker (texto que aparece por unos segundos en la barra de
estado al generarse una nueva notificacin) mediante setTicker() y el texto auxiliar
(opcional) que aparecer a la izquierda del icono pequeo de la notificacin
mediante setContentInfo().
La fecha/hora asociada a nuestra notificacin se tomar automticamente de la fecha/hora
actual si no se establece nada, o bien puede utilizarse el mtodo setWhen() para indicar otra
marca de tiempo. Veamos cmo quedara nuestro cdigo por el momento:
1 NotificationCompat.Builder mBuilder =
2
new NotificationCompat.Builder(MainActivity.this)
3
.setSmallIcon(android.R.drawable.stat_sys_warning)
4
.setLargeIcon((((BitmapDrawable)getResources()
5
.getDrawable(R.drawable.ic_launcher)).getBitmap()))
6
.setContentTitle("Mensaje de Alerta")
7
.setContentText("Ejemplo de notificacin.")
8
.setContentInfo("4")
9
.setTicker("Alerta!");
El segundo paso ser establecer la actividad a la cual debemos dirigir al usuario
automticamente si ste pulsa sobre la notificacin. Para ello debemos construir un
objeto PendingIntent, que ser el que contenga la informacin de la actividad asociada a la
notificacin y que ser lanzado al pulsar sobre ella. Para ello definiremos en primer lugar
un objeto Intent, indicando la clase de la actividad concreta a lanzar, que en nuestro caso
ser la propia actividad principal de ejemplo (MainActivity.class). Esteintent lo
utilizaremos
para
construir
el PendingIntent final
mediante
el
mtodoPendingIntent.getActivity(). Por ltimo asociaremos este objeto a la notificacin
mediante el mtodosetContentIntent() del Builder. Veamos cmo quedara esta ltima parte
comentada:
1 Intent notIntent =
2
new Intent(MainActivity.this, MainActivity.class);
3
4 PendingIntent contIntent =
5
PendingIntent.getActivity(
6
MainActivity.this, 0, notIntent, 0);
7
8 mBuilder.setContentIntent(contIntent);
Por ltimo, una vez tenemos completamente configuradas las opciones de nuestra
notificacin podemos generarla llamando al mtodo notify() del Notification Manager,al
cual

podemos

acceder

mediante

una

constante Context.NOTIFICATION_SERVICE.

llamada
Por

su

a getSystemService() con
parte

al

la

mtodo notify() le

298

pasaremos como parmetro un identificador nico definido por nosotros que identifique
nuestra notificacin y el resultado del builder que hemos construido antes, que obtenemos
llamando a su mtodo build().
1 NotificationManager mNotificationManager =
2
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
3
4 mNotificationManager.notify(NOTIF_ALERTA_ID, mBuilder.build());
Ya estamos en disposicin de probar nuestra aplicacin de ejemplo. Para ello vamos a
ejecutarla en el emulador de Android y pulsamos el botn que hemos implementado con
todo el cdigo anterior. Si todo va bien, debera aparecer en ese mismo momento nuestra
notificacin en la barra de estado, con el icono y texto definidos.

Si ahora salimos de la aplicacin y desplegamos la bandeja del sistema podremos verificar


el resto de informacin de la notificacin tal como muestra la siguiente imagen:

Por ltimo, si pulsamos sobre la notificacin se debera abrir de nuevo automticamente la


aplicacin de ejemplo.
En definitiva, como podis comprobar es bastante sencillo generar notificaciones en la
barra de estado de Android desde nuestras aplicaciones. Os animo a utilizar este mecanismo
para notificar determinados eventos al usuario de forma bastante visual e intuitiva.
Notificaciones en Android (III): Dilogos
by Sgoliver on 05/11/2011 in Android, Programacin
Durante este curso ya hemos visto dos de las principales alternativas a la hora de mostrar
notificaciones a los usuarios de nuestra aplicaciones: los toast y las notificaciones de la
barra de estado. En este ltimo artculo sobre notificaciones vamos a comentar otro

299

mecanismo que podemos utilizar para mostrar o solicitar informacin puntual al usuario. El
mecanismo del que hablamos son los cuadros de dilogo.
En principio, los dilogos de Android los podremos utilizar con distintos fines, en general:

Mostrar un mensaje.
Pedir una confirmacin rpida.
Solicitar al usuario una eleccin (simple o mltiple) entre varias alternativas.
De cualquier forma, veremos tambin cmo personalizar completamente un dilogo para
adaptarlo a cualquier otra necesidad.
El uso actual de los dilogos en Android se basa en fragmets, pero por suerte tenemos toda
la funcionalidad implementada una vez ms en la librera de compatibilidad androidsupport-v4.jar (que debe estar incluida por defecto en tu proyecto si lo has creado con una
versin reciente del pluin de Eclipse) por lo que no tendremos problemas al ejecutar nuestra
aplicacin en versiones antiguas de Android. En este caso nos vamos a basar en la
clase DialogFragment. Para crear un dilogo lo primero que haremos ser crear una nueva
clase
que
herede
de DialogFragment y
sobrescribiremos
uno
de
sus
mtodosonCreateDialog(), que ser el encargado de construir el dilogo con las opciones
que necesitemos.
La forma de construir cada dilogo depender de la informacin y funcionalidad que
necesitemos. A continuacin mostrar algunas de las formas ms habituales.

Dilogo de Alerta
Este tipo de dilogo se limita a mostrar un mensaje sencillo al usuario, y un nico botn de
OK para confirmar su lectura. Lo construiremos mediante la clase AlertDialog, y ms
concretamente su subclaseAlertDialog.Builder, de forma similar a las notificaciones de
barra de estado que ya hemos comentado en el captulo anterior. Su utilizacin es muy
sencilla, bastar con crear un objeto de tipoAlertDialog.Builder y establecer las propiedades
del dilogo mediante sus mtodos correspondientes: ttulo [setTitle()], mensaje
[setMessage()] y el texto y comportamiento del botn [setPositiveButton()]. Veamos un
ejemplo:
1
public class DialogoAlerta extends DialogFragment {
2
@Override
3
public Dialog onCreateDialog(Bundle savedInstanceState) {
4
5
AlertDialog.Builder builder =
6
new AlertDialog.Builder(getActivity());
7
8
builder.setMessage("Esto es un mensaje de alerta.")

300

9
10
11
12
13
14
15
16
17
18

.setTitle("Informacin")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
return builder.create();
}
}

Como vemos, al mtodo setPositiveButton() le pasamos como argumentos el texto a


mostrar en el botn, y la implementacin del evento onClick en forma de
objeto OnClickListener. Dentro de este evento, nos limitamos a cerrar el dilogo mediante
su mtodo cancel(), aunque podramos realizar cualquier otra accin.
Para lanzar este dilogo por ejemplo desde nuestra actividad principal, obtendramos una
referencia alFragment Manager mediante una llamada a getSupportFragmentManager(),
creamos un nuevo objeto de tipo DialogoAlerta y por ltimo mostramos el dilogo
mediante el mtodo show() pasndole la referencia al fragment manager y una etiqueta
identificativa del dilogo.
1 btnAlerta.setOnClickListener(new View.OnClickListener() {
2
public void onClick(View v) {
3
FragmentManager fragmentManager = getSupportFragmentManager();
4
DialogoAlerta dialogo = new DialogoAlerta();
5
dialogo.show(fragmentManager, "tagAlerta");
6
}
7 });
El aspecto de nuestro dilogo de alerta sera el siguiente:

Dilogo de Confirmacin
Un dilogo de confirmacin es muy similar al anterior, con la diferencia de que lo
utilizaremos para solicitar al usuario que nos confirme una determinada accin, por lo que
las posibles respuestas sern del tipo S/No.

301

La implementacin de estos dilogos ser prcticamente igual a la ya comentada para las


alertas, salvo que en esta ocasin aadiremos dos botones, uno de ellos para la respuesta
afirmativa (setPositiveButton()), y el segundo para la respuesta negativa
(setNegativeButton()). Veamos un ejemplo:
1
public class DialogoConfirmacion extends DialogFragment {
2
@Override
3
public Dialog onCreateDialog(Bundle savedInstanceState) {
4
5
AlertDialog.Builder builder =
6
new AlertDialog.Builder(getActivity());
7
8
builder.setMessage("Confirma la accin seleccionada?")
9
.setTitle("Confirmacion")
10
.setPositiveButton("Aceptar", new DialogInterface.OnClickListener() {
11
public void onClick(DialogInterface dialog, int id) {
12
Log.i("Dialogos", "Confirmacion Aceptada.");
13
dialog.cancel();
14
}
15
})
16
.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {
17
public void onClick(DialogInterface dialog, int id) {
18
Log.i("Dialogos", "Confirmacion Cancelada.");
19
dialog.cancel();
20
}
21
});
22
23
return builder.create();
24
}
25 }
En este caso, generamos a modo de ejemplo dos mensajes de log para poder verificar qu
botn pulsamos en el dilogo. El aspecto visual de nuestro dilogo de confirmacin sera el
siguiente:

Dilogo de Seleccin

302

Cuando las opciones a seleccionar por el usuario no son slo dos, como en los dilogos de
confirmacin, sino que el conjunto es mayor podemos utilizar los dilogos de seleccin
para mostrar una lista de opciones entre las que el usuario pueda elegir.
Para ello tambin utilizaremos la clase AlertDialog, pero esta vez no asignaremos ningn
mensaje ni definiremos las acciones a realizar por cada botn individual, sino que
directamente indicaremos la lista de opciones a mostrar (mediante el mtodo setItems()) y
proporcionaremos la implementacin del eventoonClick() sobre dicha lista (mediante un
listener de tipo DialogInterface.OnClickListener), evento en el que realizaremos las
acciones oportunas segn la opcin elegida. La lista de opciones la definiremos como un
array tradicional. Veamos cmo:
1
public class DialogoSeleccion extends DialogFragment {
2
@Override
3
public Dialog onCreateDialog(Bundle savedInstanceState) {
4
5
final String[] items = {"Espaol", "Ingls", "Francs"};
6
7
AlertDialog.Builder builder =
8
new AlertDialog.Builder(getActivity());
9
10
builder.setTitle("Seleccin")
11
.setItems(items, new DialogInterface.OnClickListener() {
12
public void onClick(DialogInterface dialog, int item) {
13
Log.i("Dialogos", "Opcin elegida: " + items[item]);
14
}
15
});
16
17
return builder.create();
18
}
19 }
En este caso el dilogo tendr un aspecto similar a la interfaz mostrada para los
controles Spinner.

303

Este dilogo permite al usuario elegir entre las opciones disponibles cada vez que se
muestra en pantalla. Pero, y si quisiramos recordar cul es la opcin u opciones
seleccionadas por el usuario para que aparezcan marcadas al visualizar de nuevo el cuadro
de
dilogo?
Para
ello
podemos
utilizar
los
mtodossetSingleChoiceItems() o setMultiChiceItems(), en vez de el setItems() utilizado
anteriormente. La diferencia entre ambos mtodos, tal como se puede suponer por su
nombre, es que el primero de ellos permitir una seleccin simple y el segundo una
seleccin mltiple, es decir, de varias opciones al mismo tiempo, mediante
controles CheckBox.
La forma de utilizarlos es muy similar a la ya comentada. En el caso
de setSingleChoiceItems(), el mtodo tan slo se diferencia de setItems() en que recibe un
segundo parmetro adicional que indica el ndice de la opcin marcada por defecto. Si no
queremos tener ninguna de ellas marcadas inicialmente pasaremos el valor -1.
1 builder.setTitle("Seleccin")
2
.setSingleChoiceItems(items, -1,
3
new DialogInterface.OnClickListener() {
4
public void onClick(DialogInterface dialog, int item) {
5
Log.i("Dialogos", "Opcin elegida: " + items[item]);
6
}
7
});
De esta forma conseguiramos un dilogo como el de la siguiente imagen:

Si por el contrario optamos por la opcin de seleccin mltiple, la diferencia principal


estar
en
que
tendremos
que
implementar
un
listener
del
tipo DialogInterface.OnMultiChoiceClickListener.
En
este
caso,
en
el
evento onClick recibiremos tanto la opcin seleccionada (item) como el estado en el que ha
quedado (isChecked). Adems, en esta ocasin, el segundo parmetro adicional que indica
el estado por defecto de las opciones ya no ser un simple nmero entero, sino que tendr

304

que ser un array de booleanos. En caso de no querer ninguna opcin seleccionada por
defecto pasaremos el valor null.
1 builder.setTitle("Seleccin")
2
.setMultiChoiceItems(items, null,
3
new DialogInterface.OnMultiChoiceClickListener() {
4
public void onClick(DialogInterface dialog, int item, boolean isChecked) {
5
Log.i("Dialogos", "Opcin elegida: " + items[item]);
6
}
7 });
Y el dilogo nos quedara de la siguiente forma:

Tanto si utilizamos la opcin de seleccin simple como la de seleccin mltiple, para salir
del dilogo tendremos que pulsar la tecla Atrs de nuestro dispositivo.
Dilogos Personalizados
Por ltimo, vamos a comentar cmo podemos establecer completamente el aspecto de un
cuadro de dilogo. Para esto vamos a actuar como si estuviramos definiendo la interfaz de
una actividad, es decir, definiremos un layout XML con los elementos a mostrar en el
dilogo. En mi caso voy a definir un layout de ejemplo llamado dialog_personal.xml que
colocar como siempre en la carpeta res/layout. Contendr por ejemplo una imagen a la
izquierda y dos lneas de texto a la derecha:
1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
android:layout_width="match_parent"
3
android:layout_height="match_parent"
4
android:orientation="horizontal"
5
android:padding="3dp" >
6
7
<ImageView
8
android:id="@+id/imageView1"
9
android:layout_width="wrap_content"
10
android:layout_height="wrap_content"
11
android:src="@drawable/ic_launcher" />
12

305

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Por

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="3dp">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialogo_linea_1" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialogo_linea_2" />
</LinearLayout>
</LinearLayout>
su

parte,

en

el

mtodo onCreateDialog() correspondiente

utilizaremos

el

mtodo setView() del builder para asociarle nuestro layout personalizado, que previamente
tendremos que inflar como ya hemos comentado otras veces utilizando el mtodo inflate().
Finalmente podremos incluir botones tal como vimos para los dilogos de alerta o
confirmacin. En este caso de ejemplo incluiremos un botn de Aceptar.
1
public class DialogoPersonalizado extends DialogFragment {
2
@Override
3
public Dialog onCreateDialog(Bundle savedInstanceState) {
4
5
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
6
LayoutInflater inflater = getActivity().getLayoutInflater();
7
8
builder.setView(inflater.inflate(R.layout.dialog_personal, null))
9
.setPositiveButton("Aceptar", new DialogInterface.OnClickListener() {
10
public void onClick(DialogInterface dialog, int id) {
11
dialog.cancel();
12
}
13
});
14
15
return builder.create();
16
}
17 }

306

De esta forma, si ejecutamos de nuevo nuestra aplicacin de ejemplo y lanzamos el dilogo


personalizado veremos algo como lo siguiente:

Y con esto terminamos con el apartado dedicado a notificaciones en Android. Hemos


comentado los tres principales mecanismos de Android a la hora de mostrar mensajes y
notificaciones al usuario (Toast, Barra de Estado, y Dilogos), todos ellos muy tiles para
cualquier tipo de aplicacin y que es importante conocer bien para poder decidir entre ellos
dependiendo de las necesidades que tengamos.
Acceso a Servicios Web en Android
Acceso a Servicios Web SOAP en Android (1/2)
by Sgoliver on 27/02/2012 in Android, Programacin
En este primer artculo que vamos a dedicar a los servicios web dentro del Curso de
Programacin Android nos vamos a centrar en los servicios web que utilizan el
estndar SOAP como mecanismo de comunicacin.
A diferencia de otros tutoriales, no slo vamos describir cmo acceder a este tipo de
servicios desde una aplicacin Android, sino que tambin veremos como crear un servicio
web SOAP mediante ASP.NET para acceder a una base de datos SQL Server. De esta
forma pretendo ilustrar la arquitectura completa de una aplicacin Android que acceda a
datos almacenados en un servidor de base de datos externo. Nota:Aunque intentar aportar
el mximo nmero de detalles, es imposible abarcarlo todo en un solo artculo, por lo que el
texto supondr unos conocimientos mnimos de Visual Studio y del lenguaje C#.
Como caso de ejemplo, vamos a crear una aplicacin sencilla capaz de gestionar un listado
de clientes que contendr el nombre y telfono de cada uno de ellos. Nuestra aplicacin
ser capaz de consultar el listado actual de clientes almacenados en el servidor externo y de
insertar nuevos clientes en la base de datos. Como siempre, se trata de un ejemplo muy
sencillo pero creo que lo suficientemente completo como para que sirva de base para crear
otras aplicaciones ms complejas.
Como software necesario, en este caso utilizar Visual Studio 2010 y SQL Server 2008
R2 para crear el servicio web y la base de datos respectivamente. Podis descargar de

307

forma gratuita las versionesExpress de ambos productos (ms que suficientes para crear una
aplicacin como la que describiremos en este artculo) desde la web oficial de Microsoft.
Tambin es recomendable instalar SQL Server 2008 Management Studio Express,
descargable tambin de forma gratuita desde esta web. Esta aplicacin no es ms que un
gestor grfico para acceder y manipular nuestras bases de datos SQL Server con total
comodidad.
Vamos comenzar por la base de todo el sistema, y esto es la base de datos a la que acceder
el servicio web y, a travs de ste, tambin la aplicacin Android que crearemos ms
adelante. Para ello abrimos SQL Server Management Studio, nos conectamos a nuestro
servidor SQL Server local, y pulsamos sobre la seccin Databases del rbol de objetos
que aparece a la izquierda. Sobre esta carpeta podemos acceder a la opcin New
Database del men contextual para crear una nueva base de datos.

En el cuadro de dilogo que aparece tan slo indicaremos el nombre de la nueva base de
datos, en mi caso la llamar DBCLIENTES, y dejaremos el resto de opciones con sus
valores por defecto.

308

Desplegamos el rbol de carpetas de nuestra recin creada base de datos DBCLIENTES y


sobre la carpeta Tables ejecutamos la opcin New table para crear una nueva tabla.

Vamos a aadir slo 3 campos a la tabla:

IdCliente, de tipo int, que ser un cdigo nico identificativo del cliente.
Nombre, de tipo nvarchar(50), que contendr el nombre del cliente.
Telefono, de tipo int, que contendr el telfono del cliente.
Marcaremos adems el campo IdCliente como clave principal de la tabla, y tambin
como campo de identidad autoincremental, de modo que se calcule automticamente cada
vez que insertemos un nuevo cliente.

309

Con esto ya tenemos nuestra tabla finalizada, por lo que slo nos queda guardarla con el
nombre que deseemos, que para este ejemplo ser Clientes.
Hecho, ya tenemos nuestra base de datos SQL Server creada y una tabla preparada para
almacenar los datos asociados a nuestros clientes. El siguiente paso ser crear el servicio
web que manipular los datos de esta tabla.
Para crear el servicio abriremos Visual Studio 2010 y crearemos un nuevo proyecto web en
C# utilizando la plantilla ASP.NET Empty Web Application. En un alarde de originalidad
lo llamaremos ServicioWebSoap.

Una vez creado el proyecto, aadiremos a ste un nuevo servicio web mediante el men
Project / Add new item. Lo llamaremos ServicioClientes.asmx.

310

Una vez aadido aparecer en pantalla el cdigo fuente por defecto del nuevo servicio web,
que contiene un nico mtodo de ejemplo llamado HelloWorld(). Este mtodo podemos
eliminarlo ya que no nos servir de nada, y adems modificaremos el
atributo WebService de la clase para indicar que el namespace ser http://sgoliver.net/
(en vuestro caso podis indicar otro valor). Con esto, nos quedara un cdigo base como
ste:
1
namespace ServicioWebSoap
2
{
3
/// <summary>
4
/// Summary description for ServicioClientes
5
/// </summary>
6
[WebService(Namespace = "http://sgoliver.net/")]
7
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
8
[System.ComponentModel.ToolboxItem(false)]
9
10
public class ServicioClientes : System.Web.Services.WebService
11
{
12
//Mtodos del servicio web
13
//...
14
}
15 }
Pues bien, dentro de esta clase ServicioClientes es donde aadiremos todos los mtodos
pblicos que queramos tener accesibles a travs de nuestro servicio web, siempre
precedidos por el atributo[WebMethod] como veremos en breve. Para nuestro ejemplo
vamos a crear tres mtodos, el primero para obtener el listado completo de clientes
almacenados en la base de datos, y los otros dos para insertar nuevos clientes (ms adelante
explicar por qu dos, aunque adelanto que es tan slo por motivos didcticos).
Antes de crear estos mtodos, vamos a crear una nueva clase sencilla que nos sirva para
encapsular los datos de un cliente. La aadiremos mediante la opcin Project / Add
class de Visual Studio y la llamaremos Cliente.cs. Esta clase contendr nicamente
los 3 campos que ya comentamos al crear la base de datos y dos constructores, uno de ellos
por defecto que tan solo inicializar los campos y otro con parmetros para crear clientes a

311

partir de sus datos identificativos. El cdigo de la clase es muy sencillo, y tan solo cabe
mencionar que definiremos sus tres atributos como propiedades automticas de C#
utilizando para ello la notacin abreviada {get; set;}
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Web;
5
6
namespace ServicioWebSoap
7
{
8
public class Cliente
9
{
10
public int Id {get; set;}
11
public string Nombre {get; set;}
12
public int Telefono {get; set;}
13
14
public Cliente()
15
{
16
this.Id = 0;
17
this.Nombre = "";
18
this.Telefono = 0;
19
}
20
21
public Cliente(int id, string nombre, int telefono)
22
{
23
this.Id = id;
24
this.Nombre = nombre;
25
this.Telefono = telefono;
26
}
27
}
28 }
Vamos ahora a escribir el primero de los mtodos que haremos accesible a travs de
nuestro servicio web. Lo llamaremos NuevoCliente(), recibir como parmetros de entrada
un nombre y un telfono, y se encargar de insertar un nuevo registro en nuestra tabla de
clientes con dichos datos. Recordemos que el ID del cliente no ser necesario insertarlo de
forma explcita ya que lo hemos definido en la base de datos como campo autoincremental.
Para el trabajo con la base de datos vamos a utilizar la API clsica deADO.NET, aunque
podramos utilizar cualquier otro mcanismo de acceso a datos, como por ejemploEntity
Framework, NHibernate, etc.
De esta forma, el primer paso ser crear una conexin a SQL Server mediante la
clase SQLConnection, pasando como parmetro la cadena de conexin correspondiente (en
vuestro caso tendris que modificarla para adaptarla a vuestro entorno). Tras esto abriremos

312

la conexin mediante una llamada al mtodo Open(), definiremos el comando SQL que
queremos ejecutar creando un objeto SQLCommand. Ejecutaremos el comando llamando al
mtodo ExecuteNonQuery() recogiendo el resultado en una variable, y finalmente
cerraremos la conexin llamando a Close(). Por ltimo devolveremos el resultado del
comando SQL como valor de retorno del mtodo web.
Como podis ver en el cdigo siguiente, los valores a insertar en la base de datos los hemos
especificado en la consulta SQL como parmetros variable (precedidos por el carcter @).
Los valores de estos parmetros los definimos y aadimos al comando SQL mediante el
mtodo Add() de su propiedadParameters. Esta opcin es ms recomendable que la opcin
clsica de concatenar directamente la cadena de texto de la sentencia SQL con los
parmetros variables, ya que entre otras cosas servir para evitar [en gran medida] posibles
ataques de inyeccin SQL. El resultado devuelto por este mtodo ser el nmero de

registros afectados por la sentencia SQL ejecutada, por lo que para verificar si se ha
ejecutado correctamente bastar con comprobar que el resultado es igual a 1.
[WebMethod]
1
public int NuevoClienteSimple(string nombre, int telefono)
2
{
3
SqlConnection con =
4
new SqlConnection(
5
@"Data
Source=SGOLIVERPC\SQLEXPRESS;Initial
Catalog=DBCLIENTES;Integrate
6
Security=True");
7
8
con.Open();
9
10
string sql = "INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre, @telefono)";
11
12
SqlCommand cmd = new SqlCommand(sql, con);
13
14
cmd.Parameters.Add("@nombre", System.Data.SqlDbType.NVarChar).Value = nombre;
15
cmd.Parameters.Add("@telefono", System.Data.SqlDbType.Int).Value = telefono;
16
17
int res = cmd.ExecuteNonQuery();
18
19
con.Close();
20
21
return res;
22
}
En el cdigo anterior, podis ver que hemos precedido el mtodo con el
atributo [WebMethod]. Con este atributo estamos indicando que el mtodo ser accesible a
travs de nuestro servicio web y podr ser llamado desde cualquier aplicacin que se
conecte con ste.

313

La siguiente operacin que vamos a aadir a nuestro servicio web ser la que nos permita
obtener el listado completo de clientes registrados en la base de datos. Llamaremos al
mtodo ListadoClientes()y devolver un array de objetos de tipo Cliente. El cdigo del
mtodo ser muy similar al ya comentado para la operacin de insercin, con la nica
diferencia de que en esta ocasin la sentencia SQL ser obviamente un SELECT y que
utilizaremos un objeto SqlDataReader para leer los resultados devueltos por la consulta.
Los registros ledos los iremos aadiendo a una lista de tipo List<Clientes> y una vez

completada la lectura convertiremos esta lista en un array de clientes llamando al


mtodo ToArray(). Este ltimo array ser el que devolveremos como resultado del mtodo.
Veamos el cdigo completo del mtodo:
[WebMethod]
1
public Cliente[] ListadoClientes()
2
{
3
SqlConnection con =
4
new SqlConnection(
5
@"Data
Source=SGOLIVERPC\SQLEXPRESS;Initial
Catalog=DBCLIENTES;Integrate
6
Security=True");
7
8
con.Open();
9
10
string sql = "SELECT IdCliente, Nombre, Telefono FROM Clientes";
11
12
SqlCommand cmd = new SqlCommand(sql, con);
13
14
SqlDataReader reader = cmd.ExecuteReader();
15
16
List<Cliente> lista = new List<Cliente>();
17
18
while (reader.Read())
19
{
20
lista.Add(
21
new Cliente(reader.GetInt32(0),
22
reader.GetString(1),
23
reader.GetInt32(2)));
24
}
25
26
con.Close();
27
28
return lista.ToArray();
29
}
Por ltimo, como dijimos al principio, vamos a aadir un tercer mtodo web con fines
puramente didcticos. Si os fijis en los dos mtodos anteriores, veris que en uno de los
casos devolvemos como resultado un valor simple, un nmero entero, y en el otro caso un

314

objeto complejo, en concreto un array de objetos de tipo Cliente. Sin embargo, ninguno de
ellos recibe como parmetro un tipo complejo, tan slo valores simples (enteros y strings).
Esto no tiene mucha relevancia en el cdigo de nuestro servicio web, pero s tiene ciertas
peculiaridades a la hora de realizar la llamada al servicio desde la aplicacin Android. Por
lo que para poder explicar esto ms adelante aadiremos un nuevo mtodo de insercin de
clientes que, en vez de recibir los parmetros de nombre y telfono por separado, recibir
como dato de entrada un objetoCliente.

El cdigo de este mtodo, que llamaremos NuevoClienteObjeto(), ser exactamente igual al


anterior mtodo de insercin, con la nica diferencia de los parmetros de entrada, por lo
que no nos detendremos en comentar nada ms.
[WebMethod]
1
public int NuevoClienteObjeto(Cliente cliente)
2
{
3
SqlConnection con = new SqlConnection(@"Data Source=SGOLIVERPC\SQLEXPRESS;Initial Cata
4
Security=True");
5
6
con.Open();
7
8
string sql = "INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre, @telefono)";
9
10
SqlCommand cmd = new SqlCommand(sql, con);
11
12
cmd.Parameters.Add("@nombre", System.Data.SqlDbType.NVarChar).Value = cliente.Nombre;
13
cmd.Parameters.Add("@telefono", System.Data.SqlDbType.Int).Value = cliente.Telefono;
14
15
int res = cmd.ExecuteNonQuery();
16
17
con.Close();
18
19
return res;
20
}
Y con esto hemos finalizado nuestro servicio web. Podemos probar su funcionamiento con
la pgina de prueba que proporciona ASP.NET al ejecutar el proyecto en Visual Studio. Si
ejecutamos el proyecto se abrir automticamente un explorador web que mostrar una
pgina con todas las operaciones que hemos definido en el servicio web.

315

Si pulsamos sobre cualquiera de ellas pasaremos a una nueva pgina que nos permitir dar
valores a sus parmetros y ejecutar el mtodo correspondiente para visualizar sus
resultados. Si pulsamos por ejemplo en la operacin NuevoCliente(string, int) llegaremos a
esta pgina:

Aqu podemos dar valores a los dos parmetros y ejecutar el mtodo (botn Invoke), lo
que nos devolver la respuesta codificada en un XML segn el estandar SOAP.

Como podis comprobar, en principio el XML devuelto no es fcil de interpretar, pero esto
es algo que no debe preocuparnos demasiado ya que en principio ser transparente para
nosotros, las libreras que utilizaremos ms adelante en Android para la llamada a servicios
SOAP se encargarn de parsearconvenientemente estas respuestas y de darnos tan slo
aquella parte que necesitamos.
En el siguiente artculo nos ocuparemos de la construccin de una aplicacin Android que
sea capaz de conectarse a este servicio web y de llamar a los mtodos que hemos definido
para insertar y recuperar clientes de nuestra base de datos. Veremos adems cmo podemos
ejecutar y probar en local todo el sistema de forma que podamos comprobar que todo
funciona como esperamos.
Acceso a Servicios Web SOAP en Android (2/2)
by Sgoliver on 27/02/2012 in Android, Programacin
En el artculo anterior del curso vimos cmo construir un servicio web SOAP haciendo uso
de ASP.NET y una base de datos externa SQL Server. En este segundo artculo veremos
cmo podemos acceder a este servicio web desde una aplicacin Android y probaremos
todo el sistema en local para verificar su correcto funcionamiento.

316

En primer lugar hay que empezar diciendo que Android no incluye de serie ningn tipo
de soporte para el acceso a servicios web de tipo SOAP. Es por esto por lo que vamos a
utilizar una librera externa para hacernos ms fcil esta tarea. Entre la oferta actual, la
opcin ms popular y ms utilizada es la libreraksoap2-android. Esta librera es un fork,
especialmente adaptado para Android, de la antigua librerakSOAP2. Este framework nos
permitir de forma relativamente fcil y cmoda utilizar servicios web que utilicen el
estndar SOAP. La ltima versin de esta librera en el momento de escribir este artculo es
la 2.6.0, que puede descargarse desde este enlace.
Agregar esta librera a nuestro proyecto Android es muy sencillo. Si tenemos una versin
reciente del plugin de Android para Eclipse, una vez tenemos creado el proyecto en
Android bastar con copiar el archivo .jar en la carpeta libs de nuestro proyecto. Si tu
versin del plugin es ms antigua es posible que tengas que adems aadir la librera
al path del proyecto. Para ello accederemos al men Project / Properties y en la ventana
de propiedades accederemos a la seccin Java Build Path. En esta seccin accederemos a
la solapa Libraries y pulsaremos el botn Add External JARs. Aqu seleccionamos el
fichero jar de la librera ksoap2-android (en este caso ksoap2-android-assembly-3.6.0-jarwith-dependencies.jar) y listo, ya tenemos nuestro proyecto preparado para hacer uso de la
funcionalidad aportada por la librera.
Como aplicacin de ejemplo, vamos a crear una aplicacin sencilla que permita aadir un
nuevo usuario a la base de datos. Para ello aadiremos a la vista principal dos cuadros de
texto para introducir el nombre y telfono del nuevo cliente (en mi caso se
llamarn txtNombre y txtTelefono respectivamente) y un botn (en mi caso btnEnviar) que
realice la llamada al mtodo NuevoCliente del servicio web pasndole como parmetros los
datos introducidos en los cuadros de texto anteriores.
No voy a mostrar todo el cdigo necesario para crear esta vista y obtener las referencias a
cada control porque no tiene ninguna particularidad sobre lo ya visto en multitud de
ocasiones en artculos anteriores del curso (en cualquier caso al final del artculo podis
descargar todo el cdigo fuente para su consulta). Lo que nos interesa en este caso es la
implementacin del evento onClick del botn btnEnviar, que ser el encargado de
comunicarse con el servicio web y procesar el resultado.
Lo primero que vamos a hacer en este evento es definir, por comodidad, cuatro constantes
que nos servirn en varias ocasiones durante el cdigo:

NAMESPACE. Espacio de nombres utilizado en nuestro servicio web.


URL. Direccin URL para realizar la conexin con el servicio web.
METHOD_NAME. Nombre del mtodo web concreto que vamos a ejecutar.
SOAP_ACTION. Equivalente al anterior, pero en la notacin definida por SOAP.

317

Aunque los valores se podran ms o menos intuir, para conocer exactamente los valores
que debemos asignar a estas constantes vamos a ejecutar una vez ms el proyecto de Visual
Studio que construimos en el artculo anterior y vamos a acceder a la pgina de prueba del
mtodo NuevoCliente. Veremos algo parecido a lo siguiente:

En la imagen anterior se muestran resaltados en rojo los valores de las cuatro constantes a
definir, que en nuestro caso concreto quedaran de la siguiente forma:
1
2
3
4

String NAMESPACE = "http://sgoliver.net/";


String URL="http://10.0.2.2:1473/ServicioClientes.asmx";
String METHOD_NAME = "NuevoClienteSimple";
String SOAP_ACTION = "http://sgoliver.net/NuevoClienteSimple";

Como podis comprobar, y esto es algo importante, en la URL he sustituido el nombre de


mquinalocalhost por su direccin IP equivalente, que en el caso de aplicaciones Android
ejecutadas en el emulador se corresponde con la direccin 10.0.2.2, en vez de la
clsica 127.0.0.1. Adems debes verificar que el puerto coincide con el que ests utilizando
en tu mquina. En mi caso el servicio se ejecuta sobre el puerto1473, pero es posible que en
tu caso el nmero sea distinto.
Los siguientes pasos del proceso sern crear la peticin SOAP al servicio web, enviarla al
servidor y recibir la respuesta. Aunque ya dijimos que todo este proceso sera casi
transparente para el programador, por ser sta la primera vez que hablamos del tema me
voy a detener un poco ms para intentar que entendamos lo que estamos haciendo y no solo
nos limitemos a copiar/pegar trozos de cdigo que no sabemos lo que hacen.

318

Volvamos a la pgina de prueba del mtodo web NuevoCliente. Justo debajo de la seccin
donde se solicitan los parmetros a pasar al mtodo se incluye tambin un XML de muestra
de cmo tendra que ser nuestra peticin al servidor si tuviramos que construirla a mano.
Echmosle un vistazo:

Una vez ms he marcado varias zonas sobre la imagen, correspondientes a las tres partes
principales de una peticin de tipo SOAP. Empezando por la parte interna del XML, en
primer lugar encontramos los datos de la peticin en s (Request) que contiene el nombre
del mtodo al que queremos llamar, y los nombres y valores de los parmetros en entrada.
Rodeando a esta informacin se aaden otra serie de etiquetas y datos a modo
de contenedor estndar que suele recibir el nombre de Envelope. La informacin indicada
en este contenedor no es especfica de nuestra llamada al servicio, pero s contiene
informacin sobre formatos y esquemas de validacin del estndar SOAP. Por ltimo,
durante el envo de esta peticin SOAP al servidor mediante el protocolo HTTP se aaden
determinados encabezados como los que veis en la imagen. Todo esto junto har que el
servidor sea capaz de interpretar correctamente nuestra peticin SOAP, se llame al mtodo
web correcto, y se devuelva el resultado en un formato similar al anterior que ya veremos
ms adelante. Aclarada un poco la estructura y funcionamiento general de una peticin
SOAP veamos lo sencillo que resulta realizarla desde nuestra aplicacin Android.
En primer lugar crearemos la peticin (request) a nuestro mtodo NuevoCliente. Para ello
crearemos un nuevo objeto SoapObject pasndole el namespace y el nombre del mtodo
web. A esta peticin tendremos que asociar los parmetros de entrada mediante el
mtodo addProperty() al que pasaremos los nombres y valores de los parmetros (que en
nuestro caso se obtendrn de los cuadros de texto de la vista principal).
1 SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
2
3 request.addProperty("nombre", txtNombre.getText().toString());
4 request.addProperty("telefono", txtTelefono.getText().toString());
El segundo paso ser crear el contenedor SOAP (envelope) y asociarle nuestra peticin.
Para ello crearemos un nuevo objeto SoapSerializationEnvelope indicando la versin de
SOAP que vamos a usar (versin 1.1 en nuestro caso, como puede verse en la imagen
anterior). Indicaremos adems que se trata de un servicio web .NET activando su

319

propiedad dotNet. Por ltimo, asociaremos la peticin antes creada a nuestro contenedor
llamando al mtodo setOutputSoapObject().
1 SoapSerializationEnvelope envelope =
2
new SoapSerializationEnvelope(SoapEnvelope.VER11);
3
4 envelope.dotNet = true;
5
6 envelope.setOutputSoapObject(request);
Como tercer paso crearemos el objeto que se encargar de realizar la comunicacin HTTP
con el servidor, de tipo HttpTransportSE, al que pasaremos la URL de conexin a nuestro
servicio web. Por ltimo, completaremos el proceso realizando la llamada al servicio web
mediante el mtodo call().
1
HttpTransportSE transporte = new HttpTransportSE(URL);
2
3
try
4
{
5
transporte.call(SOAP_ACTION, envelope);
6
7
//Se procesa el resultado devuelto
8
//...
9
}
10 catch (Exception e)
11 {
12
txtResultado.setText("Error!");
13 }
Tras la llamada al servicio ya estamos en disposicin de obtener el resultado devuelto por el
mtodo web llamado. Esto lo conseguimos mediante el mtodo getResponse().
Dependiendo del tipo de resultado que esperemos recibir deberemos convertir esta
respuesta a un tipo u otro. En este caso, como el resultado que esperamos es un valor
simple (un nmero entero) convertiremos la respuesta a un objetoSoapPrimitive, que
directamente podremos convertir a una cadena de caracteres llamado atoString(). Ms
adelante veremos cmo tratar valores de retorno ms complejos.
1 SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();
2 String res = resultado_xml.toString();
3
4 if(res.equals("1"))
5
txtResultado.setText("Insertado OK");
Y listo, con esto ya tenemos preparada la llamada a nuestro servicio web y el tratamiento de
la respuesta recibida.

320

Un detalle ms antes de poder probar todo el sistema. Debemos acordarnos de conceder


permiso de acceso a internet a nuestra aplicacin, aadiendo la linea correspondiente
al Android Manifest:
1 <uses-permission android:name="android.permission.INTERNET"/>
Pues bien, para probar lo que llevamos hasta ahora podemos ejecutar ambos
proyectos simultneamente, en primer lugar el de Visual Studio para iniciar la ejecucin del
servidor local que alberga nuestro servicio web (hay que dejar abierto el explorador una vez
que se abra), y posteriormente el de Eclipse para iniciar nuestra aplicacin Android en el
Emulador. Una vez estn los dos proyectos en ejecucin, podemos rellenar los datos de
nuestro cliente en la aplicacin Android y pulsar el botn Enviar para realizar la llamada
al servicio web e insertar el cliente en la base de datos (que por supuesto tambin deber
estar iniciada). Si todo va bien y no se produce ningn error, deberamos poder consultar la
tabla de Clientes a travs del SQL Server Management Studio para verificar que el cliente
se ha insertado correctamente.

En la imagen vemos cmo hemos insertado un nuevo cliente llamada cliente7 con nmero
de telfono 7777. Si consultamos ahora nuestra base de datos Sql Server podremos
comprobar si el registro efectivamente se ha insertado correctamente.

Algo importante que quiero remarcar llegados a este punto. El cdigo anterior debe
funcionar correctamente sobre un dispositivo o emulador con versin de Android anterior a
la 3.0. Sin embargo, si intentamos ejecutar la aplicacin sobre una versin posterior

321

obtendremos una excepcin de tipoNetworkOnMainThread. Esto es debido a que en


versiones recientes de Android no se permite realizar operaciones de larga duracin
directamente en el hilo principal de la aplicacin. Para solucionar esto y que nuestra
aplicacin funcione bajo cualquier versin de Android ser necesario trasladar el cdigo
que hemos escrito para llamar al servicio web a una AsyncTask que realice las operaciones
en segundo plano utilizando un hilo secundario. El curso contiene un capitulo dedicado a
describir con ms detalle las tareas asncronas o AsyncTask, por lo que en este caso me
limitar a poner cmo quedara nuestro cdigo dentro de la AsyncTask.
1
//Tarea Asncrona para llamar al WS de consulta en segundo plano
2
private class TareaWSConsulta extends AsyncTask<String,Integer,Boolean> {
3
4
private Cliente[] listaClientes;
5
6
protected Boolean doInBackground(String... params) {
7
8
boolean resul = true;
9
10
final String NAMESPACE = "http://sgoliver.net/";
11
final String URL="http://10.0.2.2:1473/ServicioClientes.asmx";
12
final String METHOD_NAME = "ListadoClientes";
13
final String SOAP_ACTION = "http://sgoliver.net/ListadoClientes";
14
15
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
16
17
SoapSerializationEnvelope envelope =
18
new SoapSerializationEnvelope(SoapEnvelope.VER11);
19
envelope.dotNet = true;
20
21
envelope.setOutputSoapObject(request);
22
23
HttpTransportSE transporte = new HttpTransportSE(URL);
24
25
try
26
{
27
transporte.call(SOAP_ACTION, envelope);
28
29
SoapObject resSoap =(SoapObject)envelope.getResponse();
30
31
listaClientes = new Cliente[resSoap.getPropertyCount()];
32
33
for (int i = 0; i < listaClientes.length; i++)
34
{
35
SoapObject ic = (SoapObject)resSoap.getProperty(i);
36

322

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

Cliente cli = new Cliente();


cli.id = Integer.parseInt(ic.getProperty(0).toString());
cli.nombre = ic.getProperty(1).toString();
cli.telefono =
Integer.parseInt(ic.getProperty(2).toString());
listaClientes[i] = cli;
}
}
catch (Exception e)
{
resul = false;
}
return resul;
}
protected void onPostExecute(Boolean result) {
if (result)
{
//Rellenamos la lista con los nombres de los clientes
final String[] datos = new String[listaClientes.length];
for(int i=0; i<listaClientes.length; i++)
datos[i] = listaClientes[i].nombre;
ArrayAdapter<String> adaptador =
new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, datos);
lstClientes.setAdapter(adaptador);
}
else
{
txtResultado.setText("Error!");
}
}
}

Como

podemos

ver,

prcticamente

todo

el

cdigo

se

ha

trasladado

al

mtodo doInBackground() de la tarea, salvo la parte en la que debemos actualizar la


interfaz de usuario tras la llamada que debe ir al mtodo onPostExecute().
Por su parte, una vez creada la tarea asncrona, en el evento click del botn nos limitaremos
a instanciar la tarea y ejecutarla llamando a su mtodo execute().
1 btnConsultar.setOnClickListener(new OnClickListener() {

323

2
3
4
5
6
7
8

@Override
public void onClick(View v) {
TareaWSConsulta tarea = new TareaWSConsulta();
tarea.execute();
}
});

Con esto, ya sabemos realizar una llamada a un servicio web SOAP que devuelve un valor
de retorno sencillo, en este caso un simple nmero entero. Lo siguiente que vamos a ver
ser como implementar la llamada a un mtodo del servicio web que nos devuelva un valor
algo ms complejo. Y esto lo vamos a ver con la llamada al mtodo
web ListadoClientes() que recordemos devolva un array de objetos de tipoCliente.
En este caso, la llamada al mtodo web se realizar de forma totalmente anloga a la ya
comentada. Donde llegarn las diferencias ser a la hora de tratar el resultado devuelto por
el servicio, comenzando por el resultado del mtodo getResponse() de ksoap. En esta
ocasin, dado que el resultado esperado no es ya un valor simple sino un objeto ms
complejo, convertiremos el resultado de getResponse() al tipoSoapObject, en vez
de SoapPrimitive como hicimos anteriormente. Nuestro objetivo ser generar un array de
objetos Cliente (lo llamaremos listaClientes) a partir del resultado devuelto por la llamada
al servicio.
Como sabemos que el resultado devuelto por el servicio es tambin un array, lo primero
que haremos ser crear un array local con la misma longitud que el devuelto, lo que
conseguiremos mediante el mtodogetPropertyCount(). Tras esto, iteraremos por los
distintos elementos del array devuelto mediante el mtodo getProperty(ind), donde ind ser
el ndice de cada ocurrencia. Cada uno de estos elementos ser a su vez otro objeto de
tipo SoapObject, que representar a un Cliente. Adicionalmente, para cada elemento
accederemos a sus propiedades (Id, Nombre, y Telefono) una vez ms mediante llamadas
agetProperty(), con el ndice de cada atributo, que seguir el mismo orden en que se
definieron. As,getProperty(0) recuperar el Id del cliente, getProperty(1) el nombre,
y getProperty(2) el telfono. De esta forma podremos crear nuestros objetos Cliente locales
a partir de estos datos. Al final de cada iteracin aadimos el nuevo cliente recuperado a
nuestro array. Veamos como quedara todo esto en el cdigo, donde seguro que se entiende
mejor:
1
SoapObject resSoap =(SoapObject)envelope.getResponse();
2
3
Cliente[] listaClientes = new Cliente[resSoap.getPropertyCount()];
4
5
for (int i = 0; i < listaClientes.length; i++)
6
{

324

7
8
9
10
11
12
13
14
15

SoapObject ic = (SoapObject)resSoap.getProperty(i);
Cliente cli = new Cliente();
cli.id = Integer.parseInt(ic.getProperty(0).toString());
cli.nombre = ic.getProperty(1).toString();
cli.telefono = Integer.parseInt(ic.getProperty(2).toString());
listaClientes[i] = cli;
}

En nuestra aplicacin de ejemplo aadimos un nuevo botn y un control tipo lista (lo
llamo lstClientes), de forma que al pulsar dicho botn rellenemos la lista con los nombres
de todos los clientes recuperados. La forma de rellenar una lista con un array de elementos
ya la vimos en los artculos dedicados a los controles de seleccin, por lo que no nos
pararemos a comentarlo. El cdigo sera el siguiente (Nota: s que todo esto se podra
realizar de forma ms eficiente sin necesidad de crear distintos arrays para los clientes y
para el adaptador de la lista, pero lo dejo as para no complicar el tutorial con temas ya
discutidos en otros artculos):
1
//Rellenamos la lista con los nombres de los clientes
2
final String[] datos = new String[listaClientes.length];
3
4
for(int i=0; i<listaClientes.length; i++)
5
datos[i] = listaClientes[i].nombre;
6
7
ArrayAdapter<String> adaptador =
8
new ArrayAdapter<String>(ServicioWebSoap.this,
9
android.R.layout.simple_list_item_1, datos);
10
11 lstClientes.setAdapter(adaptador);
Por ltimo, vamos a ver cmo llamar a un mtodo web que recibe como parmetro algn
objeto complejo. Para ilustrarlo haremos una llamada al segundo mtodo de insercin de
clientes que implementamos en el servicio, NuevoClienteObjeto(). Recordemos que este
mtodo reciba como parmetro de entrada un objeto de tipo Cliente.
Para poder hacer esto, lo primero que tendremos que hacer ser modificar un poco nuestra
claseCliente, de forma que ksoap sepa cmo serializar nuestros objetos Cliente a la hora de
generar las peticiones SOAP correspondientes. Y para esto, lo que haremos ser

implementar la interfazKvmSerializable en nuestra clase Cliente. Para ello, adems de


aadir la clusula implementscorrespondiente tendremos que implementar los siguientes
mtodos:
getProperty(int indice)
getPropertyCount()
getPropertyInfo(int indice, HashTable ht, PropertyInfo info)

325

setProperty(int indice, Object valor)


El primero de ellos deber devolver el valor de cada atributo de la clase a partir de su ndice
de orden. As, para el ndice 0 se devolver el valor del atributo Id, para el ndice 1 el del
atributo Nombre, y para el 2 el atributo Telfono.
1
@Override
2
public Object getProperty(int arg0) {
3
4
switch(arg0)
5
{
6
case 0:
7
return id;
8
case 1:
9
return nombre;
10
case 2:
11
return telefono;
12
}
13
14
return null;
15 }
El segundo de los mtodos, deber devolver simplemente el nmero de atributos de nuestra
clase, que en nuestro caso ser 3 (Id, Nombre y Telefono):
1 @Override
2 public int getPropertyCount() {
3
return 3;
4 }
El objetivo del tercero ser informar, segn el ndice recibido como parmetro, el tipo y
nombre del atributo correspondiente. El tipo de cada atributo se devolver como un valor
de la clase PropertyInfo.
1
@Override
2
public void getPropertyInfo(int ind, Hashtable ht, PropertyInfo info) {
3
switch(ind)
4
{
5
case 0:
6
info.type = PropertyInfo.INTEGER_CLASS;
7
info.name = "Id";
8
break;
9
case 1:
10
info.type = PropertyInfo.STRING_CLASS;
11
info.name = "Nombre";
12
break;
13
case 2:
14
info.type = PropertyInfo.INTEGER_CLASS;

326

15
16
17
18
19

info.name = "Telefono";
break;
default:break;
}
}

Por ltimo, el mtodo setProperty() ser el encargado de asignar el valor de cada atributo
segn su ndice y el valor recibido como parmetro.
1
@Override
2
public void setProperty(int ind, Object val) {
3
switch(ind)
4
{
5
case 0:
6
id = Integer.parseInt(val.toString());
7
break;
8
case 1:
9
nombre = val.toString();
10
break;
11
case 2:
12
telefono = Integer.parseInt(val.toString());
13
break;
14
default:
15
break;
16
}
17 }
Mediante estos mtodos, aunque de forma transparente para el programados, ksoap ser
capaz de transformar nuestros objetos Cliente al formato XML correcto de forma que pueda
pasarlos como parmetro en las peticiones SOAP a nuestro servicio.
Por su parte, la llamada al servicio tambin difiere un poco de lo ya comentado a la hora de
asociar los parmetros de entrada del mtodo web. En este caso, construiremos en primer
lugar el objeto Clienteque queremos insertar en la base de datos a partir de los datos
introducidos en la pantalla de nuestra aplicacin de ejemplo. Tras esto crearemos un nuevo
objeto PropertyInfo, al que asociaremos el nombre, valor y tipo de nuestro cliente mediante
sus mtodos setName(), setValue() y setClass()respectivamente. Por ltimo, asociaremos
este cliente como parmetro de entrada al servicio llamando al metodo addProperty() igual
que hemos hecho en las anteriores ocasiones, con la diferencia de que esta vez lo
llamaremos pasndole el objeto PropertyInfo que acabamos de crear. Adems de esto,
tendremos tambin que llamar finalmente al mtodo addMapping() para asociar de alguna
forma nuestro espacio de nombres y nombre de clase Cliente con la clase real java.
Veamos el cdigo para entenderlo mejor:
1
Cliente cli = new Cliente();
2
cli.nombre = txtNombre.getText().toString();

327

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

cli.telefono = Integer.parseInt(txtTelefono.getText().toString());
PropertyInfo pi = new PropertyInfo();
pi.setName("cliente");
pi.setValue(cli);
pi.setType(cli.getClass());
request.addProperty(pi);
SoapSerializationEnvelope envelope =
new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
envelope.addMapping(NAMESPACE, "Cliente", cli.getClass());

Todo esto lo haremos en un nuevo botn aadido a la aplicacin de ejemplo (Enviar2),


cuyo efecto tendr que ser idntico al que ya creamos para la llamada al mtodo
web NuevoClienteSimple(), aunque como acabamos de ver su implementacin es algo
diferente debido a los distintos parmetros de entrada utilizados.
Como imagen final veamos una captura de la pantalla final de nuestra aplicacin de
ejemplo, donde vemos los tres botones implementados, junto al resultado de la ejecucin de
cada uno, el mensaje Insertado OK de los mtodos de insercin, y la lista de clientes
recuperada por el mtodo de consulta.

328

Espero que estos dos ltimos artculos sobre servicios web SOAP y Android os sirvan para
tener un ejemplo completo, tanto de la parte servidor como de la parte cliente, que os sirva
de base para crear nuevos sistemas adaptados a vuestras necesidades.
Acceso a Servicios Web REST en Android (1/2)
by Sgoliver on 04/03/2012 in Android, Programacin
En los dos artculos anteriores (ste y ste) del Curso de Programacin Android nos hemos
ocupado de describir la forma de construir un sistema formado por un servicio web SOAP
que accede a una base de datos externa y una aplicacin Android que, a travs de este
servicio, es capaz de manipular dichos datos.
En este nuevo artculo vamos a crear un sistema similar, pero esta vez haciendo uso de la
otra alternativa por excelencia a la hora de crear servicios web, y no es otra de utilizar
servicios web tipo REST. Las famosas APIs que publican muchos de los sitios web
actualmente no son ms que servicios web de este tipo, aunque en la mayora de los casos
con medidas de seguridad adicionales tales como autenticacin OAuth o similares.
REST tambin se asienta sobre el protocolo HTTP como mecanismo de transporte entre
cliente y servidor, ya veremos despus en qu medida. Y en cuanto al formato de los datos
transmitidos, a diferencia de SOAP, no se impone ninguno en concreto, aunque lo ms
habitual actualmente es intercambiar la informacin en formato XML o JSON. Ya que en el
caso de SOAP utilizamos XML, en este nuevo artculo utilizaremos JSON para construir
nuestro ejemplo.
Tambin vamos a utilizar un framework distinto para construir el servicio, aunque
seguiremos hacindolo en Visual Studio y en lenguaje C#. En este caso, en vez de utilizar
ASP.NET a secas, vamos a utilizar el framework especfico ASP.NET MVC 3, cuyo
sistema de direccionamiento se ajusta mejor a los principios de REST, donde
cada recurso [en nuestro caso cada cliente] debera ser accesible mediante su propia URL
nica. Podis descargar MVC3 desde su pgina oficial de Microsoft.
En este primer artculo sobre servicios REST vamos a describir la construccin del servicio
web en s, y dedicaremos un segundo artculo a explicar cmo podemos acceder a este
servicio desde una aplicacin Android.
Empezamos. Lo primero que vamos a hacer ser crear un nuevo proyecto en Visual Studio
utilizando esta vez la plantilla llamada ASP.NET MVC 3 Web Application, lo llamaremos
ServicioWebRest.

329

En la ventana de opciones del proyecto dejaremos todos los datos que aparecen por defecto
y seleccionaremos como plantilla Empty para crear una aplicacin vaca.

Esto debera crearnos el nuevo proyecto con la estructura de carpetas necesaria, que como
veris es bastante elaborada. En nuestro caso vamos a crear el servicio web de forma
aislada del resto de la aplicacin web, y para ello lo primero que vamos a hacer es aadir
una nueva Area al proyecto, a la que llamaremos por ejemplo Api, lo que nos crear una
estructura de carpetas similar a la de la aplicacin principal pero dentro de una carpeta
independiente. Esto nos permite aislar todo el cdigo y recursos de nuestro servicio web del
resto de la aplicacin web (que en nuestro caso no existir porque no es el objetivo de este
artculo, pero que podramos crear sin problemas si lo necesitramos).

Con esto, la estructura de nuestro proyecto ser la siguiente:

330

Una vez que ya tenemos preparada toda la estructura de nuestro proyecto empecemos a
aadir los elementos necesarios. Lo primero que vamos a crear ser una nueva
clase Cliente, igual que hicimos en el ejemplo anterior con SOAP. La colocaremos en la
carpeta Api/Models y el cdigo es el mismo que ya vimos:
1 namespace ServicioWebRest.Areas.Api.Models
2 {
3
public class Cliente
4
{
5
public int Id { get; set; }
6
public string Nombre { get; set; }
7
public int Telefono { get; set; }
8
}
9 }
El siguiente elemento a aadir ser una nueva clase que contenga todas las operaciones que
queramos realizar sobre nuestra base de datos de clientes. Llamaremos a la
clase ClienteManager. En este caso s vamos a aadir las cuatro operaciones bsicas sobre

clientes, y una adicional para obtener el listado completo, de forma que ms tarde podamos
mostrar la implementacin en Android de todos los posibles tipos de llamada al servicio.
Los mtodos que aadiremos sern los siguientes:
Cliente ObtenerCliente(int id)
List<Clientes> ObtenerClientes()
bool InsertarCliente(Cliente c)
bool ActualizarCliente(Cliente c)
bool EliminarCliente(int id)
Los dos primeros mtodos nos servirn para recuperar clientes de la base de datos, tanto por
su ID para obtener un cliente concreto, como el listado completo que devolver una lista de
clientes. Los otros tres mtodos permitirn insertar, actualizar y eliminar clientes a partir de

331

su ID y los datos de entrada (si aplica). El cdigo de todos estos mtodos es anlogo a los
ya implementados en el caso de SOAP, por lo que no nos vamos a parar en volverlos a
comentar, tan slo decir que utilizan la api clsica de ADO.NET para el acceso a SQL
Server. En cualquier caso, al final del artculo tenis como siempre el cdigo fuente
completo para poder consultar lo que necesitis. A modo de ejemplo veamos la
implementacin de los mtodosObtenerClientes() e InsertarCliente().
1
public bool InsertarCliente(Cliente cli)
2
{
3
SqlConnection con = new SqlConnection(cadenaConexion);
4
5
con.Open();
6
7
string sql = "INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre,
8
@telefono)";
9
10
SqlCommand cmd = new SqlCommand(sql,con);
11
12
cmd.Parameters.Add("@nombre",
System.Data.SqlDbType.NVarChar).Value
=
13 cli.Nombre;
14
cmd.Parameters.Add("@telefono", System.Data.SqlDbType.Int).Value = cli.Telefono;
15
16
int res = cmd.ExecuteNonQuery();
17
18
con.Close();
19
20
return (res == 1);
21 }
22
23 public List<Cliente> ObtenerClientes()
24 {
25
List<Cliente> lista = new List<Cliente>();
26
27
SqlConnection con = new SqlConnection(cadenaConexion);
28
29
con.Open();
30
31
string sql = "SELECT IdCliente, Nombre, Telefono FROM Clientes";
32
33
SqlCommand cmd = new SqlCommand(sql,con);
34
35
SqlDataReader reader =
36
cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
37
38
while (reader.Read())
39
{

332

40
41
42
43
44
45
46
47
48
49
50
51

Cliente cli = new Cliente();


cli = new Cliente();
cli.Id = reader.GetInt32(0);
cli.Nombre = reader.GetString(1);
cli.Telefono = reader.GetInt32(2);
lista.Add(cli);
}
reader.Close();
return lista;

}
Hasta ahora, todo el cdigo que hemos escrito es bastante genrico y nada tiene que ver con
que nuestro proyecto sea de tipo MVC. Sin embargo, los dos siguientes elementos s que
estn directamente relacionados con el tipo de proyecto que tenemos entre manos.
Lo siguiente que vamos a aadir ser un controlador a nuestro servicio web. Este
controlador (claseClientesController) ser el encargado de contener las
diferentes acciones que se podrn llamar segn la URL y datos HTTP que recibamos como
peticin de entrada al servicio. Para nuestro ejemplo, aadiremos tan slo dos acciones, una
primera dirigida a gestionar todas las peticiones que afecten a un nico cliente (insertar,
actualizar, eliminar y obtener por ID), y otra que trate la peticin del listado completo de
clientes. Las llamaremos Clientes() y Cliente() respectivamente. Estas acciones harn uso
de una instancia de la clase ClienteManager creada anteriormente para realizar las acciones
necesarias contra la base de datos. Cada accin ser tambin responsable de formatear sus
resultados al formato de comunicacin que hayamos elegido, en nuestro caso JSON.
La accin Clientes es muy sencilla, se limitar a llamar al mtodo ObtenerClientes() y
formatear los resultados como JSON. Para hacer esto ltimo basta con crear directamente
un objeto JsonResultllamado al mtodo Json() pasndole como parmetro de entrada el
objeto a formatear. Todo esto se reduce a una sola linea de cdigo:
1 [HttpGet]
2 public JsonResult Clientes()
3 {
4
return Json(this.clientesManager.ObtenerClientes(),
5
JsonRequestBehavior.AllowGet);
6 }
Habris notado tambin que hemos precedido el mtodo con el atributo [HttpGet]. Para
intentar explicar esto me hace falta seguir hablando de los principios de diseo de REST.

333

Este tipo de servicios utiliza los propios tipos de peticin definidos por el protocolo HTTP
para diferenciar entre las operaciones a realizar por el servicio web. As, el propio tipo de
peticin HTTP realizada (GET, POST, PUT o DELETE), junto con la direccin URL
especificada en la llamada, nos determinar la operacin a ejecutar por el servicio web. En
el caso ya visto, el atributo [HttpGet] nos indica que dicho mtodo se podr ejecutar al
recibirse una peticin de tipo GET.
Entenderemos todo esto mejor ahora cuando veamos el cdigo de la accin Cliente(). En
esta accin, dependiente del tipo de peticin HTTP recibida, tendremos que llamar a un
mtodo u otro del servicio web. As, usaremos POST para las inserciones de
clientes, PUT para las actualizaciones, GET para la consulta por ID y DELETE para las
eliminaciones. En este caso no precedemos el mtodo por ningn atributo, ya que la misma
accin se encargar de tratar diferentes tipos de peticin.
1
public JsonResult Cliente(int? id, Cliente item)
2
{
3
switch (Request.HttpMethod)
4
{
5
case "POST":
6
return Json(clientesManager.InsertarCliente(item));
7
case "PUT":
8
return Json(clientesManager.ActualizarCliente(item));
9
case "GET":
10
return Json(clientesManager.ObtenerCliente(id.GetValueOrDefault()),
11
JsonRequestBehavior.AllowGet);
12
case "DELETE":
13
return Json(clientesManager.EliminarCliente(id.GetValueOrDefault()));
14
}
15
16
return Json(new { Error = true, Message = "Operacin HTTP desconocida" });
17 }
Algunos de vosotros seguro que os estis preguntando cmo distinguir el servicio cundo
llamar a la accin Clientes() para obtener el listado completo, o a la accin Cliente() para
obtener un nico cliente por su ID, ya que para ambas operaciones hemos indicado que se
recibir el tipo de peticin http GET.
Pues bien, aqu es donde nos va a ayudar el ltimo elemento a aadir al servicio web.
Realmente no lo aadiremos, sino que lo modificaremos, ya que es un fichero que ya ha
creado Visual Studio por nosotros. Se trata de la clase ApiAreaRegistration. La funcin de
esta clase ser la de dirigir las peticiones recibidas hacia una accin u otra del controlador
segn la URL utilizada al realizarse la llamada al servicio web.

334

En nuestro caso de ejemplo, vamos a reconocer dos tipos de URL. Una de ellas para
acceder a la lista completa de cliente, y otra para realizar cualquier accin sobre un cliente
en concreto:

Lista de clientes: http://servidor/Api/Clientes


Operacin sobre cliente: http://servidor/Api/Clientes/Cliente/id_del_cliente
Cada uno de estos patrones tendremos que registrarlos mediante el
mtodo MapRoute() dentro del mtodo RegisterArea() que ya tendremos creado dentro de
la clase ApiAreaRegistration. As, para registrar el primer tipo de URL haremos lo
siguiente:
1 context.MapRoute(
2
"AccesoClientes",
3
"Api/Clientes",
4
new
5
{
6
controller = "Clientes",
7
action = "Clientes"
8
}
9 );
Como primer parmetro de MapRoute() indicamos un nombre descriptivo para el patrn de
URL. El segundo parmetro es el patrn en s, que en este caso no tiene partes variables.
Por ltimo indicamos el controlador al que se dirigirn las peticiones que sigan este patrn
eliminando el sufijo Controller (en nuestro caso ser el controlador ClientesController) y
la accin concreta a ejecutar dentro de dicho controlador (en nuestro caso la
accin Clientes()).
Para el segundo tipo de URL ser muy similar, con la nica diferencia de que ahora habr
una parte final variable que se corresponder con el ID del cliente y que asignaremos al
parmetro id de la accin. En este caso adems, dirigiremos la peticin hacia la
accin Cliente(), en vez de Clientes().
1
context.MapRoute(
2
"AccesoCliente",
3
"Api/Clientes/Cliente/{id}",
4
new
5
{
6
controller = "Clientes",
7
action = "Cliente",
8
id = UrlParameter.Optional
9
}
10 );
Como todo esto en cuenta, y por recapitular un poco, las posibles llamadas a nuestro
servicio sern las siguientes:

335

GET /Api/Clientes
Recuperar el listado completo de clientes y lo devolver en formato JSON.
GET /Api/Clientes/Cliente/3
Recuperar el cliente con el ID indicado en la URL y lo devolver en formato JSON.
POST /Api/Clientes/Cliente { Nombre:nombre, Telefono:1234 }
Insertar un nuevo cliente con los datos aportados en la peticin en formato JSON.
PUT /Api/Clientes/Cliente/3 { Id:3, Nombre:nombre, Telefono:1234 }
Actualizar el cliente con el ID indicado en la URL con los datos aportados en la peticin
en formato JSON.
DELETE /Api/Clientes/Cliente/3
Eliminar el cliente con el ID indicado en la URL.
Llegados aqu, tan slo tenemos que ejecutar nuestro proyecto y esperar a que se abra el
navegador web. En principio no se mostrar un error por no encontrar la pgina principal de
la aplicacin, ya que no hemos creado ninguna, pero nos asegurar que el servidor de
prueba est funcionando, por lo que nuestro servicio debera responder a peticiones.
As, si escribimos en la barra de direcciones por ejemplo la siguiente direccin (el puerto
puede variar):
http://localhost:1234/Api/Clientes/Cliente/4
deberamos recibir un fichero en formato JSON que contuviera los datos del cliente con ID
= 4 de nuestra base de datos. Sera un fichero con contenido similar al siguiente:
{"Id":4,"Nombre":"cliente4","Telefono":4444}
En el siguiente artculo veremos cmo construir una aplicacin Android capaz de acceder a
este servicio y procesar los resultados recibidos.

Acceso a Servicios Web REST en Android (2/2)


by Sgoliver on 04/03/2012 in Android, Programacin
En el artculo anterior dedicado a los servicios web REST hemos visto cmo crear
fcilmente un servicio de este tipo utilizando el framework ASP.NET MVC 3. En esta

336

segunda parte vamos a describir cmo podemos construir una aplicacin Android que
acceda a este servicio web REST.
Y tal como hicimos en el caso de SOAP, vamos a crear una aplicacin de ejemplo que
llame a las distintas funciones de nuestro servicio web. En este caso la aplicacin se
compondr de 5 botones, uno por cada una de las acciones que hemos implementado en el
servicio web (insertar, actualizar, eliminar, recuperar un cliente, y listar todos los clientes).

A diferencia del caso de SOAP, en esta ocasin no vamos a utilizar ninguna librera externa
para acceder al servicio web, ya que Android incluye todo lo necesario para realizar la
conexin y llamada a los mtodos del servicio, y tratamiento de resultados en
formato JSON.
Como ya hemos comentado, al trabajar con servicios web de tipo REST, las llamadas al
servicio no se harn a travs de una nica URL, sino que se determinar la accin a realizar
segn la URL accedida y la accin HTTP utilizada para realizar la peticin
(GET, POST, PUT o DELETE). En los siguientes apartados veremos uno a uno la
implementacin de estos botones.
Insertar un nuevo cliente
Como ya comentamos en el artculo anterior, la insercin de un nuevo cliente la
realizaremos a travs de la siguiente URL:
http://10.0.2.2:2731/Api/Clientes/Cliente
Utilizaremos la accin http POST y tendremos que incluir en la peticin un objeto en
formato JSON que contenga los datos del nuevo cliente (tan slo Nombre y Telfono, ya
que el ID se calcular automticamente). El formato de este objeto de entrada ser anlogo
al siguiente:
{Nombre:cccc, Telefono:12345678}

337

Pues bien, para conseguir esto comenzaremos por crear un nuevo objeto HttpClient, que
ser el encargado de realizar la comunicacin HTTP con el servidor a partir de los datos
que nosotros le proporcionemos. Tras esto crearemos la peticin POST creando un nuevo
objeto HttpPost e indicando la URL de llamada al servicio. Modificaremos
mediante setHeader() el atributo http content-type para indicar que el formato de los datos
que utilizaremos en la comunicacin, que como ya indicamos ser JSON (cuyo MIMEType correspondiente es application/json).
1 HttpClient httpClient = new DefaultHttpClient();
2
3 HttpPost post =
4
new HttpPost("http://10.0.2.2:2731/Api/Clientes/Cliente");
5
6 post.setHeader("content-type", "application/json");
El siguiente paso ser crear el objeto JSON a incluir con la peticin, que deber contener
los datos del nuevo cliente a insertar. Para ello creamos un nuevo objeto JSONObject y le
aadimos mediante el mtodoput() los dos atributos necesarios (nombre y telfono) con sus
valores correspondientes, que los obtenemos de los cuadros de texto de la interfaz,
llamados txtNombre y txtTelefono.
Por ltimo asociaremos este objeto JSON a nuestra peticin HTTP convirtindolo primero
al tipoStringEntity e incluyndolo finalmente en la peticin mediante el mtodo setEntity().
1 //Construimos el objeto cliente en formato JSON
2 JSONObject dato = new JSONObject();
3
4 dato.put("Nombre", txtNombre.getText().toString());
5 dato.put("Telefono", Integer.parseInt(txtTelefono.getText().toString()));
6
7 StringEntity entity = new StringEntity(dato.toString());
8 post.setEntity(entity);
Una vez creada nuestra peticin HTTP y asociado el dato de entrada, tan slo nos queda
realizar la llamada al servicio mediante el mtodo execute() del objeto HttpClient y
recuperar el resultado mediante getEntity(). Este resultado lo recibimos en forma de objeto
HttpEntity, pero lo podemos convertir fcilmente en una cadena de texto mediante el
mtodo esttico EntityUtils.toString().
1 HttpResponse resp = httpClient.execute(post);
2 String respStr = EntityUtils.toString(resp.getEntity());
3
4 if(respStr.equals("true"))
5
lblResultado.setText("Insertado OK.");
En nuestro caso, el mtodo de insercin devuelve nicamente un valor booleano indicando
si el registro se ha insertado correctamente en la base de datos, por lo que tan slo

338

tendremos que verificar el valor de este booleano (true o false) para conocer el
resultado de la operacin, que mostraremos en la interfaz en una etiqueta de texto
llamada lblResultado.
Como ya dijimos en el apartado anterior sobre servicios SOAP, a partir de la versin 3 de
Android no se permite realizar operaciones de larga duracin dentro del hilo principal de la
aplicacin, entre ellas conexiones a internet como estamos haciendo en esta ocasin. Para
solucionar este problema y que la aplicacin funcione correctamente en todas las versiones
de Android debemos hacer la llamada al servicio mediante una tarea asncrona,
o AsynTask, que se ejecute en segundo plano. Una vez ms no entrar en detalles porque el
curso contiene un captulo dedicado exclusivamente a este tema. A modo de ejemplo, el
cdigo anterior trasladado a una AsyncTask quedara de la siguiente forma:
1
private class TareaWSInsertar extends AsyncTask<String,Integer,Boolean> {
2
3
protected Boolean doInBackground(String... params) {
4
5
boolean resul = true;
6
7
HttpClient httpClient = new DefaultHttpClient();
8
9
HttpPost post = new
10
HttpPost("http://10.0.2.2:2731/Api/Clientes/Cliente");
11
12
post.setHeader("content-type", "application/json");
13
14
try
15
{
16
//Construimos el objeto cliente en formato JSON
17
JSONObject dato = new JSONObject();
18
19
dato.put("Nombre", params[0]);
20
dato.put("Telefono", Integer.parseInt(params[1]));
21
22
StringEntity entity = new StringEntity(dato.toString());
23
post.setEntity(entity);
24
25
HttpResponse resp = httpClient.execute(post);
26
String respStr = EntityUtils.toString(resp.getEntity());
27
28
if(!respStr.equals("true"))
29
resul = false;
30
}
31
catch(Exception ex)
32
{

339

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

Log.e("ServicioRest","Error!", ex);
resul = false;
}
return resul;
}
protected void onPostExecute(Boolean result) {
if (result)
{
lblResultado.setText("Insertado OK.");
}
}
}

Y la llamada a la tarea asncrona desde el evento onClick del botn de Insertar sera tan
sencilla como sta:
1
btnInsertar.setOnClickListener(new OnClickListener() {
2
3
@Override
4
public void onClick(View v) {
5
TareaWSInsertar tarea = new TareaWSInsertar();
6
tarea.execute(
7
txtNombre.getText().toString(),
8
txtTelefono.getText().toString());
9
}
10 });
Actualizar un cliente existente
La URL utilizada para la actualizacin de clientes ser la misma que la anterior:
http://10.0.2.2:2731/Api/Clientes/Cliente
Pero en este caso, el objeto JSON a enviar como entrada deber contener no slo los
nuevos valores de nombre y telfono sino tambin el ID del cliente a actualizar, por lo que
tendra una estructura anloga a la siguiente:
{Id:123, Nombre:cccc, Telefono:12345678}
Para actualizar el cliente procederemos de una forma muy similar a la ya comentada para la
insercin, con las nicas diferencias de que en este caso la accin HTTP utilizada
ser PUT (objeto HttpPut) y que el objeto JSON de entrada tendr el campo ID adicional.
1
HttpClient httpClient = new DefaultHttpClient();
2
3
HttpPut put = new HttpPut("http://10.0.2.2:2731/Api/Clientes/Cliente");

340

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

put.setHeader("content-type", "application/json");
try
{
//Construimos el objeto cliente en formato JSON
JSONObject dato = new JSONObject();
dato.put("Id", Integer.parseInt(txtId.getText().toString()));
dato.put("Nombre", txtNombre.getText().toString());
dato.put("Telefono", Integer.parseInt(txtTelefono.getText().toString()));
StringEntity entity = new StringEntity(dato.toString());
put.setEntity(entity);
HttpResponse resp = httpClient.execute(put);
String respStr = EntityUtils.toString(resp.getEntity());
if(respStr.equals("true"))
lblResultado.setText("Actualizado OK.");
}
catch(Exception ex)
{
Log.e("ServicioRest","Error!", ex);
}

Eliminacin de un cliente
La eliminacin de un cliente la realizaremos a travs de la URL siguiente:
http://10.0.2.2:2731/Api/Clientes/Cliente/id_cliente
donde id_cliente ser el ID del cliente a eliminar. Adems, utilizaremos la accin
http DELETE (objetoHttpDelete) para identificar la operacin que queremos realizar. En
este caso no ser necesario pasar ningn objeto de entrada junto con la peticin, por lo que
el cdigo quedar an ms sencillo que los dos casos anteriores.
1
HttpClient httpClient = new DefaultHttpClient();
2
3
String id = txtId.getText().toString();
4
5
HttpDelete del =
6
new HttpDelete("http://10.0.2.2:2731/Api/Clientes/Cliente/" + id);
7
8
del.setHeader("content-type", "application/json");
9
10 try
11 {
12
HttpResponse resp = httpClient.execute(del);

341

13
14
15
16
17
18
19
20
21

String respStr = EntityUtils.toString(resp.getEntity());


if(respStr.equals("true"))
lblResultado.setText("Eliminado OK.");
}
catch(Exception ex)
{
Log.e("ServicioRest","Error!", ex);
}

Como podis ver, al principio del mtodo obtenemos el ID del cliente desde la interfaz de
la aplicacin y lo concatenamos con la URL base para formar la URL completa de llamada
al servicio.
Obtener un cliente
Esta operacin es un poco distinta a las anteriores, ya que en este caso el resultado devuelto
por el servicio ser un objeto JSON y no un valor simple como en los casos anteriores. Al
igual que en el caso de eliminacin de clientes, la URL a utilizar ser del tipo:
http://10.0.2.2:2731/Api/Clientes/Cliente/id_cliente
En este caso utilizaremos un tipo de peticin http GET (objeto HttpGet) y la forma de
realizar la llamada ser anloga a las anteriores. Donde aparecern las diferencias ser a la
hora de tratar el resultado devuelto por el servicio tras llamar al mtodo getEntity(). Lo que
haremos ser crear un nuevo objetoJSONObject a partir del resultado textual de getEntity().
Hecho esto, podremos acceder a los atributos del objeto utilizando para ello los
mtodos get() correspondientes, segn el tipo de cada atributo (getInt(), getString(), etc).
Tras esto mostraremos los datos del cliente recuperado en la etiqueta de resultados de la
interfaz (lblResultados).
1
HttpClient httpClient = new DefaultHttpClient();
2
3
String id = txtId.getText().toString();
4
5
HttpGet del =
6
new HttpGet("http://10.0.2.2:2731/Api/Clientes/Cliente/" + id);
7
8
del.setHeader("content-type", "application/json");
9
10 try
11 {
12
HttpResponse resp = httpClient.execute(del);
13
String respStr = EntityUtils.toString(resp.getEntity());
14
15
JSONObject respJSON = new JSONObject(respStr);

342

16
17
18
19
20
21
22
23
24
25
26

int idCli = respJSON.getInt("Id");


String nombCli = respJSON.getString("Nombre");
int telefCli = respJSON.getInt("Telefono");
lblResultado.setText("" + idCli + "-" + nombCli + "-" + telefCli);
}
catch(Exception ex)
{
Log.e("ServicioRest","Error!", ex);
}

Una vez ms como podis comprobar el cdigo es muy similar al ya visto para el resto de
operaciones.
Obtener listado completo de clientes
Por ltimo vamos a ver cmo podemos obtener el listado completo de clientes. El inters de
esta operacin est en que el resultado recuperado de la llamada al servicio ser un array de
objetos de tipo cliente, por supuesto en formato JSON. La accin http utilizada ser una vez
ms la accin GET, y la URL para recuperar el listado de clientes ser:
http://10.0.2.2:2731/Api/Clientes
De nuevo, la forma de llamar al servicio ser anloga a las anteriores hasta la llamada
a getEntity()para recuperar los resultados. En esta ocasin, dado que recibimos un array de
elementos, convertiremos este resultado a un objeto JSONArray, y hecho esto podremos
acceder a cada uno de los elementos del array mediante una llamada a getJSONObject(), al
que iremos pasando el ndice de cada elemento. Para saber cuntos elementos contiene el
array podremos utilizar el mtodo length() del objeto JSONArray. Por ltimo, el acceso a
los atributos de cada elemento del array lo realizamos exactamente igual como ya lo
hicimos en la operacin anterior de obtencin de cliente por ID.
1
HttpClient httpClient = new DefaultHttpClient();
2
3
HttpGet del =
4
new HttpGet("http://10.0.2.2:2731/Api/Clientes");
5
6
del.setHeader("content-type", "application/json");
7
8
try
9
{
10
HttpResponse resp = httpClient.execute(del);
11
String respStr = EntityUtils.toString(resp.getEntity());
12
13
JSONArray respJSON = new JSONArray(respStr);
14

343

15
String[] clientes = new String[respJSON.length()];
16
17
for(int i=0; i<respJSON.length(); i++)
18
{
19
JSONObject obj = respJSON.getJSONObject(i);
20
21
int idCli = obj.getInt("Id");
22
String nombCli = obj.getString("Nombre");
23
int telefCli = obj.getInt("Telefono");
24
25
clientes[i] = "" + idCli + "-" + nombCli + "-" + telefCli;
26
}
27
28
//Rellenamos la lista con los resultados
29
ArrayAdapter<String> adaptador =
30
new ArrayAdapter<String>(ServicioWebRest.this,
31
android.R.layout.simple_list_item_1, clientes);
32
33
lstClientes.setAdapter(adaptador);
34 }
35 catch(Exception ex)
36 {
37
Log.e("ServicioRest","Error!", ex);
38 }
Tras obtener nuestro array de clientes, para mostrar los resultados hemos aadido a la
interfas de nuestra aplicacin de ejemplo un control tipo ListView (llamado lstClientes) que
hemos rellenado a travs de su adaptador con los datos de los clientes recuperados.
A modo de ejemplo, en la siguiente imagen puede verse el resultado de ejecutar la
operacin de listado completo de clientes:

344

Y con esto hemos terminado. Espero haber ilustrado con claridad en los dos ltimos
artculos la forma de construir servicios web tipo REST mediante ASP.NET y aplicaciones
cliente Android capaces de acceder a dichos servicios.

Tareas en segundo plano en Android


Tareas en segundo plano en Android (I): Thread y AsyncTask
by Sgoliver on 29/07/2012 in Android, Programacin
Todos los componentes de una aplicacin Android, tanto las actividades, los servicios [s,
tambin los servicios], o los broadcast receivers se ejecutan en el mismo hilo de ejecucin,
el llamado hilo principal, main thread o GUI thread, que como ste ltimo nombre indica
tambin es el hilo donde se ejecutan todas las operaciones que gestionan la interfaz de
usuario de la aplicacin. Es por ello, que cualquier operacin larga o costosa que
realicemos en este hilo va a bloquear la ejecucin del resto de componentes de la aplicacin
y por supuesto tambin la interfaz, produciendo al usuario un efecto evidente de lentitud,
bloqueo, o mal funcionamiento en general, algo que deberamos evitar a toda costa. Incluso
puede ser peor, dado que Android monitoriza las operaciones realizadas en el hilo principal
y detecta aquellas que superen los 5 segundos, en cuyo caso se muestra el famoso mensaje
de Application Not Responding (ANR) y el usuario debe decidir entre forzar el cierre de
la aplicacin o esperar a que termine.

345

Obviamente, stos son el tipo de errores que nadie quiere ver al utilizar su aplicacin, y en
este artculo y los siguientes vamos a ver varias formas de evitarlo utilizando procesos en
segundo plano para ejecutar las operaciones de larga duracin. En este primer artculo de la
serie nos vamos a centrar en dos de las alternativas ms directas a la hora de ejecutar tareas
en segundo plano en Android:
1. Crear nosotros mismos de forma explcita un nuevo hilo para ejecutar nuestra tarea.
2. Utilizar la clase auxiliar AsyncTask proporcionada por Android.
Mi idea inicial para este artculo era obviar la primera opcin, ya que normalmente la
segunda solucin nos es ms que suficiente, y adems es mas sencilla y ms limpia de
implementar. Sin embargo, si no comentamos al menos de pasada la forma de crear a
mano nuevos hilos y los problemas que surgen, quiz no se viera demasiado claro las
ventajas que tiene utilizar las AsyncTask. Por tanto, finalmente voy a pasar muy
rpidamente por la primera opcin para despus centrarnos un poco ms en la segunda.
Adems, aprovechando el tema de la ejecucin de tareas en segundo plano, vamos a ver
tambin cmo utilizar un control (el ProgressBar) y un tipo de dilogo (el ProgressDialog)
que no vimos en los primeros temas del curso dedicados a la interfaz de usuario.
Y para ir paso a paso, vamso a empezar por crear una aplicacin de ejemplo en cuya
actividad principal colocaremos un control ProgressBar (en mi caso llamado pbarProgreso)
y un botn (btnSinHilos) que ejecute una tarea de larga duracin. Para simular una
operacin de larga duracin vamos a ayudarnos de un mtodo auxiliar que lo nico que
haga sea esperar 1 segundo, mediante una llamada aThread.sleep().
1 private void tareaLarga()
2 {
3
try {
4
Thread.sleep(1000);
5
} catch(InterruptedException e) {}
6 }
Haremos que nuestro botn ejecute este mtodo 10 veces, de forma que nos quedar una
ejecucin de unos 10 segundos en total:
1

btnSinHilos.setOnClickListener(new OnClickListener() {

346

2
@Override
3
public void onClick(View v) {
4
pbarProgreso.setMax(100);
5
pbarProgreso.setProgress(0);
6
7
for(int i=1; i<=10; i++) {
8
tareaLarga();
9
pbarProgreso.incrementProgressBy(10);
10
}
11
12
Toast.makeText(MainHilos.this, "Tarea finalizada!",
13
Toast.LENGTH_SHORT).show();
14
}
15 });
Como veis, aqu todava no estamos utilizando nada especial, por lo que todo el cdigo se
ejecutar en el hilo principal de la aplicacin. En cuanto a la utilizacin del
control ProgressBar vemos que es muy sencilla y no requiere apenas configuracin. En
nuestro caso tan slo hemos establecido el valor mximo que alcanzar (el valor en el que
la barra de progreso estar rellena al mximo) mediante el mtodosetMax(100),
posteriormente la hemos inicializado al valor mnimo mediante una llamada
asetProgress(0) de forma que inicialmente aparezca completamente vaca, y por ltimo en
cada iteracin del bucle incrementamos su valor en 10 unidades llamando
a incrementProgressBy(10), de tal forma que tras la dcima iteracin la barra llegue al valor
mximo de 100 que establecimos antes. Finalmente mostramos un mensaje Toast para
informar de la finalizacin de la tarea. Pues bien, ejecutemos la aplicacin, pulsemos el
botn, y veamos qu ocurre.
He colocado un pequeo vdeo al final del artculo donde puede verse el resultado final de
todas las pruebas que haremos durante este tutorial. En concreto esta primera prueba puede
verse entre los segundos 00:00 00:12
No era eso lo que esperbamos verdad? Lo que ha ocurrido es que desde el momento que
hemos pulsado el botn para ejecutar la tarea, hemos bloqueado completamente el resto de
la aplicacin, incluida la actualizacin de la interfaz de usuario, que ha debido esperar a que
sta termine mostrando directamente la barra de progreso completamente llena. En
definitiva, no hemos sido capaces de ver el progreso de la tarea. Pero como decamos, este
efecto puede empeorar. Probemos ahora a pulsar el botn de la tarea y mientras sta se
ejecuta realicemos cualquier accin sobre la pantalla, un simple click sobre el fondo nos
basta. Veamos qu ocurre ahora.
Puedes verlo en el vdeo entre los segundos 00:13 00:28

347

Vemos cmo al intentar hacer cualquier accin sobre la aplicacin Android nos ha
advertido con un mensaje de error que la aplicacin no responde debido a que se est
ejecutando una operacin de larga duracin en el hilo principal. El usuario debe elegir entre
esperar a que termine de ejecutarla o forzar el cierre de la aplicacin. Pues bien, estos son
los efectos que vamos a intentar evitar. La opcin ms inmediata que nos proporciona
Android, al igual que otras plataformas, es crear directamente hilos secundarios dentro de
los cuales ejecutar nuestras operaciones costosas. Esto lo conseguimos en Android
instanciando un objeto de la clase Thread. El constructor de la clase Thread recibe como
parmetro un nuevo objeto Runnable que debemos construir implementando su
mtodo run(), dentro del cual vamos a realizar nuestra tarea de larga duracin. Hecho esto,
debemos llamar al mtodo start() del objetoThreaddefinido para comenzar la ejecucin de
la tarea en segundo plano.
1 new Thread(new Runnable() {
2
public void run() {
3
//Aqu ejecutamos nuestras tareas costosas
4
}
5 }).start();
Hasta aqu todo sencillo y relativamente limpio. Los problemas aparecen cuando nos damos
cuenta que desde este hilo secundario que hemos creado no podemos hacer referencia
directa a componentes que se ejecuten en el hilo principal, entre ellos los controles que
forman nuestra interfaz de usuario, es decir, que desde el mtodo run() no podramos ir
actualizando directamente nuestra barra de progreso de la misma forma que lo hacamos
antes. Para solucionar esto, Android proporciona varias alternativas, entre ellas la
utilizacin del mtodo post() para actuar sobre cada control de la interfaz, o la llamada al
mtodorunOnUiThread() para enviar operaciones al hilo principal desde el hilo
secundario [Nota: S, vale, s que no he nombrado la opcin de los Handler, pero no quera
complicar ms el tema por el momento]. Ambas opciones requieren como parmetro un
nuevo objeto Runnable del que nuevamente habr que implementar su mtodo run() donde
se acte sobre los elementos de la interfaz. Por ver algn ejemplo, en nuestro caso hemos
utilizado el mtodo post() para actuar sobre el control ProgressBar, y el
mtodorunOnUiThread()para mostrar el mensaje toast.
1
new Thread(new Runnable() {
2
public void run() {
3
pbarProgreso.post(new Runnable() {
4
public void run() {
5
pbarProgreso.setProgress(0);
6
}
7
});
8

348

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

for(int i=1; i<=10; i++) {


tareaLarga();
pbarProgreso.post(new Runnable() {
public void run() {
pbarProgreso.incrementProgressBy(10);
}
});
}
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
});
}
}).start();

Utilicemos este cdigo dentro de un nuevo botn de nuestra aplicacin de ejemplo y vamos
a probarlo en el emulador.
Puedes verlo en el vdeo entre los segundos 00:29 00:43
Ahora s podemos ver el progreso de nuestra tarea reflejado en la barra de progreso. La
creacin de un hilo secundario nos ha permitido mantener el hilo principal libre de forma
que nuestra interfaz de usuario de actualiza sin problemas durante la ejecucin de la tarea
en segundo plano. Sin embargo miremos de nuevo el cdigo anterior. Complicado de leer,
verdad? Y eso considerando que tan slo estamos actualizando un control de nuestra
interfaz. Si el nmero de controles fuera mayor, o necesitramos una mayor interaccin con
la interfaz el cdigo empezara a ser inmanejable, difcil de leer y mantener, y por tanto
tambin ms propenso a errores. Pues bien, aqu es donde Android llega en nuestra ayuda y
nos ofrece la clase AsyncTask, que nos va a permitir realizar esto mismo pero con la

ventaja de no tener que utilizar artefactos del tipo runOnUiThread() y de una forma mucho
ms organizada y legible. La forma bsica de utilizar la clase AsyncTaskconsiste en crear
una nueva clase que extienda de ella y sobrescribir varios de sus mtodos entre los que
repartiremos la funcionalidad de nuestra tarea. Estos mtodos son los siguientes:
onPreExecute(). Se ejecutar antes del cdigo principal de nuestra tarea. Se suele utilizar
para preparar la ejecucin de la tarea, inicializar la interfaz, etc.
doInBackground(). Contendr el cdigo principal de nuestra tarea.
onProgressUpdate().
Se
ejecutar
cada
vez
que
llamemos
al
mtodo publishProgress() desde el mtodo doInBackground().
onPostExecute(). Se ejecutar cuando finalice nuestra tarea, o dicho de otra forma, tras la
finalizacin del mtodo doInBackground().

349

onCancelled(). Se ejecutar cuando se cancele la ejecucin de la tarea antes de su


finalizacin normal.
Estos mtodos tienen una particularidad esencial para nuestros intereses. El
mtodo doInBackground()se ejecuta en un hilo secundario (por tanto no podremos
interactuar con la interfaz), pero sin embargo todos los dems se ejecutan en el hilo
principal, lo que quiere decir que dentro de ellos podremos hacer referencia directa a
nuestros controles de usuario para actualizar la interfaz. Por su parte, dentro
dedoInBackground() tendremos
la
posibilidad
de
llamar peridicamente al

mtodo publishProgress()para
que
automticamente
desde
el
mtodo onProgressUpdate() se actualice la interfaz si es necesario. Al extender una nueva
clase de AsyncTaskindicaremos tres parmetros de tipo:
1. El tipo de datos que recibiremos como entrada de la tarea en el mtodo doInBackground().
2. El tipo de datos con el que actualizaremos el progreso de la tarea, y que recibiremos como
parmetro del mtodo onProgressUpdate() y que a su vez tendremos que incluir como
parmetro del mtodopublishProgress().
3. El tipo de datos que devolveremos como resultado de nuestra tarea, que ser el tipo de
retorno del mtodo doInBackground() y el tipo del parmetro recibido en el
mtodo onPostExecute().
En

nuestro

caso

de

ejemplo,

extenderemos

de AsyncTask indicando

los

tipos Void, Integer yBooleanrespectivamente, lo que se traducir en que:


doInBackground() no recibir ningn parmetro de entrada (Void).
publishProgress() y onProgressUpdate() recibirn como parmetros datos de tipo entero
(Integer).
doInBackground() devolver
como
retorno
un
dato
de
tipo
booleano
y onPostExecute() tambin recibir como parmetro un dato del dicho tipo (Boolean).
Dicho esto, cmo repartiremos la funcionalidad de nuestra tarea entre los distintos mtodos.
Pues sencillo, en onPreExecute() inicializaremos la barra de progreso estableciendo su
valor mximo y ponindola a cero para comenzar. En doInBackground() ejecutaremos
nuestro bucle habitual llamando apublishProgress() tras cada iteracin indicando el
progreso actual. En onProgressUpdate()actualizaremos el estado de la barra de progreso
con el valor recibido como parmetro. y por ltimo enonPostExecute() mostraremos el
mensaje Toast de finalizacin de la tarea. Veamos el cdigo completo:
1
private class MiTareaAsincrona extends AsyncTask<Void, Integer, Boolean> {
2
3
@Override
4
protected Boolean doInBackground(Void... params) {
5
6
for(int i=1; i<=10; i++) {
7
tareaLarga();
8
9
publishProgress(i*10);

350

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

if(isCancelled())
break;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progreso = values[0].intValue();
pbarProgreso.setProgress(progreso);
}
@Override
protected void onPreExecute() {
pbarProgreso.setMax(100);
pbarProgreso.setProgress(0);
}
@Override
protected void onPostExecute(Boolean result) {
if(result)
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
@Override
protected void onCancelled() {
Toast.makeText(MainHilos.this, "Tarea cancelada!",
Toast.LENGTH_SHORT).show();
}
}

Si observamos con detenimiento el cdigo, la nica novedad que hemos introducido es la


posibilidad de cancelar la tarea en medio de su ejecucin. Esto se realiza llamando al
mtodo cancel() de nuestraAsyncTask (para lo cual aadiremos un botn ms a nuestra
aplicacin de ejemplo, adems del nuevo que aadiremos para comenzar la tarea). Dentro
de la ejecucin de nuestra tarea en doInBackground()tendremos adems que consultar
periodicamente el resultado del mtodo isCancelled() que nos dir si el usuario ha
cancelado la tarea (es decir, si se ha llamado al mtodo cancel()), en cuyo caso deberemos
de terminar la ejecucin lo antes posible, en nuestro caso de ejemplo simplemente
saldremos del bucle con la instruccin break. Adems, tendremos en cuenta que en los
casos que se cancela la tarea, tras el mtodo doInBackground() no se llamar

351

a onPostExecute() sino al mtodo onCancelled(), dentro del cual podremos realizar


cualquier accin para confirma la cancelacin de la tarea. En nuestro caso mostraremos un
mensaje Toast informando de ello.
Puedes verlo en el vdeo entre los segundos 00:44 01:06
Mucho mejor que las alternativas anteriores, verdad? Pero vamos a mostrar una opcin
ms. Si queremos que el usuario pueda ver el progreso de nuestra tarea en segundo plano,
pero no queremos que interacte mientras tanto con la aplicacin tenemos la opcin de
mostrar la barra de progreso dentro de un dilogo. Android nos proporciona directamente
un componente de este tipo en forma de un tipo especial de dilogo
llamado ProgressDialog.
Configurar un cuadro de dilogo de este tipo es muy sencillo. Para ello vamos a aadir un
botn ms a nuestra aplicacin de ejemplo, donde inicializaremos el dilogo y lanzaremos
la tarea en segundo plano. Para inicializar el dilogo comenzaremos por crear un nuevo
objeto ProgressDialog pasndole como parmetro el contexto actual. Tras esto
estableceremos su estilo: STYLE_HORIZONTAL para una barra de progreso tradicional,
o STYLE_SPINNER para un indicador de progreso de tipo indeterminado.

ProgressDialog horizontal

ProgressDialog spinner
Lo siguiente ser especificar el texto a mostrar en el dilogo, en nuestro caso el mensaje
Procesando, y el valor mximo de nuestro progreso, que lo mantendremos en 100. Por
ltimo indicaremos si deseamos que el dilogo sea cancelable, es decir, que el usuario
pueda cerrarlo pulsando el botn Atrs del telfono. Para nuestro ejemplo activaremos esta
propiedad para ver cmo podemos cancelar tambin nuestra tarea en segundo plano cuando
el usuario cierra el dilogo. Tras la configuracin del dilogo lanzaremos laAsyncTask del
ejemplo anterior, que tendremos que modificar ligeramente para adaptarla al nuevo dilogo.
Veamos el cdigo por ahora:
1
btnAsyncDialog.setOnClickListener(new OnClickListener() {

352

2
3
@Override
4
public void onClick(View v) {
5
6
pDialog = new ProgressDialog(MainHilos.this);
7
pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
8
pDialog.setMessage("Procesando...");
9
pDialog.setCancelable(true);
10
pDialog.setMax(100);
11
12
tarea2 = new MiTareaAsincronaDialog();
13
tarea2.execute();
14
}
15 });
La AsyncTask ser muy similar a la que ya implementamos.

De

hecho

el

mtodo doInBackground() no sufrir cambios.


En onProgressUpdate() la nica diferencia ser que actualizaremos el progreso llamando al
mtodosetProgress() del ProgressDialog en vez de la ProgressBar.
El cdigo de onPreExecute() s tendr algn cambio ms. Aprovecharemos este mtodo
para implementar el evento onCancel del dilogo, dentro del cual cancelaremos tambin la
tarea en segundo plano llamando al mtodo cancel(). Adems, inicializaremos el progreso
del dilogo a 0 y lo mostraremos al usuario mediante el mtodo show().
Por ltimo, en el mtodo onPostExecute() adems de mostrar el Toast de finalizacin,
tendremos que cerrar previamente el dilogo llamando a su mtodo dismiss().
Veamos el cdigo completo de la AsyncTask modificada para usar el
nuevo ProgressDialog.
1
private class MiTareaAsincronaDialog extends AsyncTask<Void,
2
Boolean> {
3
4
@Override
5
protected Boolean doInBackground(Void... params) {
6
7
for(int i=1; i<=10; i++) {
8
tareaLarga();
9
10
publishProgress(i*10);
11
12
if(isCancelled())
13
break;
14
}
15
16
return true;
17
}

Integer,

353

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
46
47
48
49
50
51
52
53
54

@Override
protected void onProgressUpdate(Integer... values) {
int progreso = values[0].intValue();
pDialog.setProgress(progreso);
}
@Override
protected void onPreExecute() {
pDialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
MiTareaAsincronaDialog.this.cancel(true);
}
});
pDialog.setProgress(0);
pDialog.show();
}
@Override
protected void onPostExecute(Boolean result) {
if(result)
{
pDialog.dismiss();
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onCancelled() {
Toast.makeText(MainHilos.this, "Tarea cancelada!",
Toast.LENGTH_SHORT).show();
}

}
Si ahora ejecutamos nuestro proyecto y pulsamos sobre el ltimo botn incluido veremos
cmo el dilogo aparece por encima de nuestra actividad mostrando el progreso de la tarea
asncrona. Tras finalizar, el dilogo desaparece y se muestra el mensaje toast de
finalizacin. Si en cambio, se pulsa el botn Atrs del dispositivo antes de que la tarea
termine el dilogo se cerrar y se mostrar el mensaje de cancelacin.
Puedes verlo en el vdeo entre los segundos 01:07 01:35

354

Y con esto habramos concluido este primer artculo sobre hilos y tareas en segundo plano.
Os dejo a continuacin el vdeo de demostracin de la aplicacin de ejemplo construida
durante el tema.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
Demo Hilos y AsyncTask en Android

Tareas en segundo plano en Android (I): Thread y AsyncTask


by Sgoliver on 29/07/2012 in Android, Programacin
Todos los componentes de una aplicacin Android, tanto las actividades, los servicios [s,
tambin los servicios], o los broadcast receivers se ejecutan en el mismo hilo de ejecucin,
el llamado hilo principal, main thread o GUI thread, que como ste ltimo nombre indica
tambin es el hilo donde se ejecutan todas las operaciones que gestionan la interfaz de
usuario de la aplicacin. Es por ello, que cualquier operacin larga o costosa que
realicemos en este hilo va a bloquear la ejecucin del resto de componentes de la aplicacin
y por supuesto tambin la interfaz, produciendo al usuario un efecto evidente de lentitud,
bloqueo, o mal funcionamiento en general, algo que deberamos evitar a toda costa. Incluso
puede ser peor, dado que Android monitoriza las operaciones realizadas en el hilo principal
y detecta aquellas que superen los 5 segundos, en cuyo caso se muestra el famoso mensaje
de Application Not Responding (ANR) y el usuario debe decidir entre forzar el cierre de
la aplicacin o esperar a que termine.

Obviamente, stos son el tipo de errores que nadie quiere ver al utilizar su aplicacin, y en
este artculo y los siguientes vamos a ver varias formas de evitarlo utilizando procesos en
segundo plano para ejecutar las operaciones de larga duracin. En este primer artculo de la
serie nos vamos a centrar en dos de las alternativas ms directas a la hora de ejecutar tareas
en segundo plano en Android:
1. Crear nosotros mismos de forma explcita un nuevo hilo para ejecutar nuestra tarea.
2. Utilizar la clase auxiliar AsyncTask proporcionada por Android.

355

Mi idea inicial para este artculo era obviar la primera opcin, ya que normalmente la
segunda solucin nos es ms que suficiente, y adems es mas sencilla y ms limpia de
implementar. Sin embargo, si no comentamos al menos de pasada la forma de crear a
mano nuevos hilos y los problemas que surgen, quiz no se viera demasiado claro las
ventajas que tiene utilizar las AsyncTask. Por tanto, finalmente voy a pasar muy
rpidamente por la primera opcin para despus centrarnos un poco ms en la segunda.
Adems, aprovechando el tema de la ejecucin de tareas en segundo plano, vamos a ver
tambin cmo utilizar un control (el ProgressBar) y un tipo de dilogo (el ProgressDialog)
que no vimos en los primeros temas del curso dedicados a la interfaz de usuario.
Y para ir paso a paso, vamso a empezar por crear una aplicacin de ejemplo en cuya
actividad principal colocaremos un control ProgressBar (en mi caso llamado pbarProgreso)
y un botn (btnSinHilos) que ejecute una tarea de larga duracin. Para simular una
operacin de larga duracin vamos a ayudarnos de un mtodo auxiliar que lo nico que
haga sea esperar 1 segundo, mediante una llamada aThread.sleep().
1 private void tareaLarga()
2 {
3
try {
4
Thread.sleep(1000);
5
} catch(InterruptedException e) {}
6 }
Haremos que nuestro botn ejecute este mtodo 10 veces, de forma que nos quedar una
ejecucin de unos 10 segundos en total:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

btnSinHilos.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
pbarProgreso.setMax(100);
pbarProgreso.setProgress(0);
for(int i=1; i<=10; i++) {
tareaLarga();
pbarProgreso.incrementProgressBy(10);
}
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
});

Como veis, aqu todava no estamos utilizando nada especial, por lo que todo el cdigo se
ejecutar en el hilo principal de la aplicacin. En cuanto a la utilizacin del
control ProgressBar vemos que es muy sencilla y no requiere apenas configuracin. En

356

nuestro caso tan slo hemos establecido el valor mximo que alcanzar (el valor en el que
la barra de progreso estar rellena al mximo) mediante el mtodosetMax(100),
posteriormente la hemos inicializado al valor mnimo mediante una llamada
asetProgress(0) de forma que inicialmente aparezca completamente vaca, y por ltimo en
cada iteracin del bucle incrementamos su valor en 10 unidades llamando
a incrementProgressBy(10), de tal forma que tras la dcima iteracin la barra llegue al valor
mximo de 100 que establecimos antes. Finalmente mostramos un mensaje Toast para
informar de la finalizacin de la tarea. Pues bien, ejecutemos la aplicacin, pulsemos el
botn, y veamos qu ocurre.
He colocado un pequeo vdeo al final del artculo donde puede verse el resultado final de
todas las pruebas que haremos durante este tutorial. En concreto esta primera prueba puede
verse entre los segundos 00:00 00:12
No era eso lo que esperbamos verdad? Lo que ha ocurrido es que desde el momento que
hemos pulsado el botn para ejecutar la tarea, hemos bloqueado completamente el resto de
la aplicacin, incluida la actualizacin de la interfaz de usuario, que ha debido esperar a que
sta termine mostrando directamente la barra de progreso completamente llena. En
definitiva, no hemos sido capaces de ver el progreso de la tarea. Pero como decamos, este
efecto puede empeorar. Probemos ahora a pulsar el botn de la tarea y mientras sta se
ejecuta realicemos cualquier accin sobre la pantalla, un simple click sobre el fondo nos
basta. Veamos qu ocurre ahora.
Puedes verlo en el vdeo entre los segundos 00:13 00:28
Vemos cmo al intentar hacer cualquier accin sobre la aplicacin Android nos ha
advertido con un mensaje de error que la aplicacin no responde debido a que se est
ejecutando una operacin de larga duracin en el hilo principal. El usuario debe elegir entre
esperar a que termine de ejecutarla o forzar el cierre de la aplicacin. Pues bien, estos son
los efectos que vamos a intentar evitar. La opcin ms inmediata que nos proporciona
Android, al igual que otras plataformas, es crear directamente hilos secundarios dentro de
los cuales ejecutar nuestras operaciones costosas. Esto lo conseguimos en Android
instanciando un objeto de la clase Thread. El constructor de la clase Thread recibe como
parmetro un nuevo objeto Runnable que debemos construir implementando su
mtodo run(), dentro del cual vamos a realizar nuestra tarea de larga duracin. Hecho esto,
debemos llamar al mtodo start() del objetoThreaddefinido para comenzar la ejecucin de
la tarea en segundo plano.
1 new Thread(new Runnable() {
2
public void run() {

357

3
4
5

//Aqu ejecutamos nuestras tareas costosas


}
}).start();

Hasta aqu todo sencillo y relativamente limpio. Los problemas aparecen cuando nos damos
cuenta que desde este hilo secundario que hemos creado no podemos hacer referencia
directa a componentes que se ejecuten en el hilo principal, entre ellos los controles que
forman nuestra interfaz de usuario, es decir, que desde el mtodo run() no podramos ir
actualizando directamente nuestra barra de progreso de la misma forma que lo hacamos
antes. Para solucionar esto, Android proporciona varias alternativas, entre ellas la
utilizacin del mtodo post() para actuar sobre cada control de la interfaz, o la llamada al
mtodorunOnUiThread() para enviar operaciones al hilo principal desde el hilo
secundario [Nota: S, vale, s que no he nombrado la opcin de los Handler, pero no quera
complicar ms el tema por el momento]. Ambas opciones requieren como parmetro un
nuevo objeto Runnable del que nuevamente habr que implementar su mtodo run() donde
se acte sobre los elementos de la interfaz. Por ver algn ejemplo, en nuestro caso hemos
utilizado el mtodo post() para actuar sobre el control ProgressBar, y el
mtodorunOnUiThread()para mostrar el mensaje toast.
1
new Thread(new Runnable() {
2
public void run() {
3
pbarProgreso.post(new Runnable() {
4
public void run() {
5
pbarProgreso.setProgress(0);
6
}
7
});
8
9
for(int i=1; i<=10; i++) {
10
tareaLarga();
11
pbarProgreso.post(new Runnable() {
12
public void run() {
13
pbarProgreso.incrementProgressBy(10);
14
}
15
});
16
}
17
18
runOnUiThread(new Runnable() {
19
public void run() {
20
Toast.makeText(MainHilos.this, "Tarea finalizada!",
21
Toast.LENGTH_SHORT).show();
22
}
23
});
24
}
25 }).start();

358

Utilicemos este cdigo dentro de un nuevo botn de nuestra aplicacin de ejemplo y vamos
a probarlo en el emulador.
Puedes verlo en el vdeo entre los segundos 00:29 00:43
Ahora s podemos ver el progreso de nuestra tarea reflejado en la barra de progreso. La
creacin de un hilo secundario nos ha permitido mantener el hilo principal libre de forma
que nuestra interfaz de usuario de actualiza sin problemas durante la ejecucin de la tarea
en segundo plano. Sin embargo miremos de nuevo el cdigo anterior. Complicado de leer,
verdad? Y eso considerando que tan slo estamos actualizando un control de nuestra
interfaz. Si el nmero de controles fuera mayor, o necesitramos una mayor interaccin con
la interfaz el cdigo empezara a ser inmanejable, difcil de leer y mantener, y por tanto
tambin ms propenso a errores. Pues bien, aqu es donde Android llega en nuestra ayuda y

nos ofrece la clase AsyncTask, que nos va a permitir realizar esto mismo pero con la
ventaja de no tener que utilizar artefactos del tipo runOnUiThread() y de una forma mucho
ms organizada y legible. La forma bsica de utilizar la clase AsyncTaskconsiste en crear
una nueva clase que extienda de ella y sobrescribir varios de sus mtodos entre los que
repartiremos la funcionalidad de nuestra tarea. Estos mtodos son los siguientes:
onPreExecute(). Se ejecutar antes del cdigo principal de nuestra tarea. Se suele utilizar
para preparar la ejecucin de la tarea, inicializar la interfaz, etc.
doInBackground(). Contendr el cdigo principal de nuestra tarea.
onProgressUpdate().
Se
ejecutar
cada
vez
que
llamemos
al
mtodo publishProgress() desde el mtodo doInBackground().
onPostExecute(). Se ejecutar cuando finalice nuestra tarea, o dicho de otra forma, tras la
finalizacin del mtodo doInBackground().
onCancelled(). Se ejecutar cuando se cancele la ejecucin de la tarea antes de su
finalizacin normal.
Estos mtodos tienen una particularidad esencial para nuestros intereses. El
mtodo doInBackground()se ejecuta en un hilo secundario (por tanto no podremos
interactuar con la interfaz), pero sin embargo todos los dems se ejecutan en el hilo
principal, lo que quiere decir que dentro de ellos podremos hacer referencia directa a
nuestros controles de usuario para actualizar la interfaz. Por su parte, dentro
dedoInBackground() tendremos
la
posibilidad
de
llamar peridicamente al
mtodo publishProgress()para
que
automticamente
desde
el

mtodo onProgressUpdate() se actualice la interfaz si es necesario. Al extender una nueva


clase de AsyncTaskindicaremos tres parmetros de tipo:
1. El tipo de datos que recibiremos como entrada de la tarea en el mtodo doInBackground().
2. El tipo de datos con el que actualizaremos el progreso de la tarea, y que recibiremos como
parmetro del mtodo onProgressUpdate() y que a su vez tendremos que incluir como
parmetro del mtodopublishProgress().

359

3. El tipo de datos que devolveremos como resultado de nuestra tarea, que ser el tipo de
retorno del mtodo doInBackground() y el tipo del parmetro recibido en el
mtodo onPostExecute().

En nuestro caso de ejemplo, extenderemos de AsyncTask indicando los


tipos Void, Integer yBooleanrespectivamente, lo que se traducir en que:
doInBackground() no recibir ningn parmetro de entrada (Void).
publishProgress() y onProgressUpdate() recibirn como parmetros datos de tipo entero
(Integer).
doInBackground() devolver
como
retorno
un
dato
de
tipo
booleano
y onPostExecute() tambin recibir como parmetro un dato del dicho tipo (Boolean).
Dicho esto, cmo repartiremos la funcionalidad de nuestra tarea entre los distintos mtodos.
Pues sencillo, en onPreExecute() inicializaremos la barra de progreso estableciendo su
valor mximo y ponindola a cero para comenzar. En doInBackground() ejecutaremos
nuestro bucle habitual llamando apublishProgress() tras cada iteracin indicando el
progreso actual. En onProgressUpdate()actualizaremos el estado de la barra de progreso
con el valor recibido como parmetro. y por ltimo enonPostExecute() mostraremos el
mensaje Toast de finalizacin de la tarea. Veamos el cdigo completo:
1
private class MiTareaAsincrona extends AsyncTask<Void, Integer, Boolean> {
2
3
@Override
4
protected Boolean doInBackground(Void... params) {
5
6
for(int i=1; i<=10; i++) {
7
tareaLarga();
8
9
publishProgress(i*10);
10
11
if(isCancelled())
12
break;
13
}
14
15
return true;
16
}
17
18
@Override
19
protected void onProgressUpdate(Integer... values) {
20
int progreso = values[0].intValue();
21
22
pbarProgreso.setProgress(progreso);
23
}
24
25
@Override
26
protected void onPreExecute() {
27
pbarProgreso.setMax(100);

360

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

pbarProgreso.setProgress(0);
}
@Override
protected void onPostExecute(Boolean result) {
if(result)
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
@Override
protected void onCancelled() {
Toast.makeText(MainHilos.this, "Tarea cancelada!",
Toast.LENGTH_SHORT).show();
}
}

Si observamos con detenimiento el cdigo, la nica novedad que hemos introducido es la


posibilidad de cancelar la tarea en medio de su ejecucin. Esto se realiza llamando al
mtodo cancel() de nuestraAsyncTask (para lo cual aadiremos un botn ms a nuestra
aplicacin de ejemplo, adems del nuevo que aadiremos para comenzar la tarea). Dentro
de la ejecucin de nuestra tarea en doInBackground()tendremos adems que consultar
periodicamente el resultado del mtodo isCancelled() que nos dir si el usuario ha
cancelado la tarea (es decir, si se ha llamado al mtodo cancel()), en cuyo caso deberemos
de terminar la ejecucin lo antes posible, en nuestro caso de ejemplo simplemente
saldremos del bucle con la instruccin break. Adems, tendremos en cuenta que en los
casos que se cancela la tarea, tras el mtodo doInBackground() no se llamar
a onPostExecute() sino al mtodo onCancelled(), dentro del cual podremos realizar
cualquier accin para confirma la cancelacin de la tarea. En nuestro caso mostraremos un
mensaje Toast informando de ello.
Puedes verlo en el vdeo entre los segundos 00:44 01:06
Mucho mejor que las alternativas anteriores, verdad? Pero vamos a mostrar una opcin
ms. Si queremos que el usuario pueda ver el progreso de nuestra tarea en segundo plano,
pero no queremos que interacte mientras tanto con la aplicacin tenemos la opcin de
mostrar la barra de progreso dentro de un dilogo. Android nos proporciona directamente
un componente de este tipo en forma de un tipo especial de dilogo
llamado ProgressDialog.
Configurar un cuadro de dilogo de este tipo es muy sencillo. Para ello vamos a aadir un
botn ms a nuestra aplicacin de ejemplo, donde inicializaremos el dilogo y lanzaremos
la tarea en segundo plano. Para inicializar el dilogo comenzaremos por crear un nuevo

361

objeto ProgressDialog pasndole como parmetro el contexto actual. Tras esto


estableceremos su estilo: STYLE_HORIZONTAL para una barra de progreso tradicional,
o STYLE_SPINNER para un indicador de progreso de tipo indeterminado.

ProgressDialog horizontal

ProgressDialog spinner
Lo siguiente ser especificar el texto a mostrar en el dilogo, en nuestro caso el mensaje
Procesando, y el valor mximo de nuestro progreso, que lo mantendremos en 100. Por
ltimo indicaremos si deseamos que el dilogo sea cancelable, es decir, que el usuario
pueda cerrarlo pulsando el botn Atrs del telfono. Para nuestro ejemplo activaremos esta
propiedad para ver cmo podemos cancelar tambin nuestra tarea en segundo plano cuando
el usuario cierra el dilogo. Tras la configuracin del dilogo lanzaremos laAsyncTask del
ejemplo anterior, que tendremos que modificar ligeramente para adaptarla al nuevo dilogo.
Veamos el cdigo por ahora:
1
btnAsyncDialog.setOnClickListener(new OnClickListener() {
2
3
@Override
4
public void onClick(View v) {
5
6
pDialog = new ProgressDialog(MainHilos.this);
7
pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
8
pDialog.setMessage("Procesando...");
9
pDialog.setCancelable(true);
10
pDialog.setMax(100);
11
12
tarea2 = new MiTareaAsincronaDialog();
13
tarea2.execute();
14
}
15 });
La AsyncTask ser

muy similar

la

que

mtodo doInBackground() no sufrir cambios.

ya

implementamos.

De

hecho

el

362

En onProgressUpdate() la nica diferencia ser que actualizaremos el progreso llamando al


mtodosetProgress() del ProgressDialog en vez de la ProgressBar.
El cdigo de onPreExecute() s tendr algn cambio ms. Aprovecharemos este mtodo
para implementar el evento onCancel del dilogo, dentro del cual cancelaremos tambin la
tarea en segundo plano llamando al mtodo cancel(). Adems, inicializaremos el progreso
del dilogo a 0 y lo mostraremos al usuario mediante el mtodo show().
Por ltimo, en el mtodo onPostExecute() adems de mostrar el Toast de finalizacin,
tendremos que cerrar previamente el dilogo llamando a su mtodo dismiss().
Veamos el cdigo completo de la AsyncTask modificada para usar el
nuevo ProgressDialog.
1
private class MiTareaAsincronaDialog extends AsyncTask<Void, Integer,
2
Boolean> {
3
4
@Override
5
protected Boolean doInBackground(Void... params) {
6
7
for(int i=1; i<=10; i++) {
8
tareaLarga();
9
10
publishProgress(i*10);
11
12
if(isCancelled())
13
break;
14
}
15
16
return true;
17
}
18
19
@Override
20
protected void onProgressUpdate(Integer... values) {
21
int progreso = values[0].intValue();
22
23
pDialog.setProgress(progreso);
24
}
25
26
@Override
27
protected void onPreExecute() {
28
29
pDialog.setOnCancelListener(new OnCancelListener() {
30
@Override
31
public void onCancel(DialogInterface dialog) {
32
MiTareaAsincronaDialog.this.cancel(true);
33
}
34
});

363

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

pDialog.setProgress(0);
pDialog.show();
}
@Override
protected void onPostExecute(Boolean result) {
if(result)
{
pDialog.dismiss();
Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onCancelled() {
Toast.makeText(MainHilos.this, "Tarea cancelada!",
Toast.LENGTH_SHORT).show();
}
}

Si ahora ejecutamos nuestro proyecto y pulsamos sobre el ltimo botn incluido veremos
cmo el dilogo aparece por encima de nuestra actividad mostrando el progreso de la tarea
asncrona. Tras finalizar, el dilogo desaparece y se muestra el mensaje toast de
finalizacin. Si en cambio, se pulsa el botn Atrs del dispositivo antes de que la tarea
termine el dilogo se cerrar y se mostrar el mensaje de cancelacin.
Puedes verlo en el vdeo entre los segundos 01:07 01:35
Y con esto habramos concluido este primer artculo sobre hilos y tareas en segundo plano.
Os dejo a continuacin el vdeo de demostracin de la aplicacin de ejemplo construida
durante el tema.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
Demo Hilos y AsyncTask en Android
Tareas en segundo plano en Android (II): IntentService
by Sgoliver on 05/08/2012 in Android, Programacin
En el artculo anterior del Curso de Programacin Android vimos cmo ejecutar tareas en
segundo plano haciendo uso de hilos (Thread) y tareas asncronas (AsyncTask). En este
nuevo artculo nos vamos a centrar en una alternativa menos conocida, aunque tanto o ms

364

interesante, para conseguir el mismo objetivo: ejecutar una determinada tarea en un hilo
independiente al hilo principal de la aplicacin. Esta opcin se llama IntentService, y no es
ms que un tipo particular de servicio Android que se preocupar por nosotros de la
creacin y gestin del nuevo hilo de ejecucin y de detenerse a s mismo una vez concluida
su tarea asociada.
Como en el caso de las AsyncTask, la utilizacin de un IntentService va a ser tan sencilla
como
extender
una
nueva
clase
de IntentService e
implementar
su
mtodo onHandleIntent(). Este mtodo recibe como parmetro un Intent, que podremos
utilizar para pasar al servicio los datos de entrada necesarios.
A diferencia de las AsyncTask, un IntentService no proporciona mtodos que se ejecuten
en el hilo principal de la aplicacin y que podamos aprovechar para comunicarnos con
nuestra interfaz durante la ejecucin. ste es el motivo principal de que
los IntentService sean una opcin menos utilizada a la hora de ejecutar tareas que requieran
cierta vinculacin con la interfaz de la aplicacin. Sin embargo tampoco es imposible su
uso en este tipo de escenarios ya que podremos utilizar por ejemplo mensajes broadcast (y
por supuesto su BroadcastReceiver asociado capaz de procesar los mensajes) para
comunicar eventos al hilo principal, como por ejemplo la necesidad de actualizar controles
de la interfaz o simplemente para comunicar la finalizacin de la tarea ejecutada. En este
artculo veremos cmo implementar este mtodo para conseguir una aplicacin de ejemplo
similar a la que construimos en el artculo anterior utilizandoAsyncTask.
Empezaremos extendiendo una nueva clase derivada de IntentService, que para ser
originales llamaremos MiIntentService. Lo primero que tendremos que hacer ser
implementar un constructor por defecto. Este constructor lo nico que har ser llamar al
constructor de la clase padre pasndole el nombre de nuestra nueva clase.
A continuacin implementaremos el mtodo onHandleIntent(). Como ya hemos indicado,
este mtodo ser el que contenga el cdigo de la tarea a ejecutar en segundo plano. Para
simular una tarea de larga duracin utilizaremos el mismo bucle que ya vimos en el artculo
anterior con la novedad de que esta vez el nmero de iteraciones ser variable, de forma
que podamos experimentar con cmo pasar datos de entrada a travs del Intent recibido
como parmetro en onHandleIntent(). En nuestro caso de ejemplo pasaremos un slo dato
de entrada que indique el nmero de iteraciones. Por tanto, lo primero que haremos ser
obtener este dato a partir del Intent mediante el mtodo getIntExtra(). Una vez conocemos
el nmero de iteraciones, tan slo nos queda ejecutar el bucle y comunicar el progreso tras
cada iteracin.
Como ya hemos comentado, para comunicar este progreso vamos a hacer uso de
mensajes broadcast. Para enviar este tipo de mensajes necesitamos construir un Intent,

365

asociarle un nombre de accin determinada que lo identifique mediante setAction(), e


incluir los datos que necesitemos comunicar aadiendo tantos extras como sean necesarios
mediante el mtodo putExtra(). Los nombres de las acciones se suelen preceder con el
paquete java de nuestra aplicacin de forma que nos aseguremos que es un identificador
nico. En nuestro caso lo llamaremos net.sgoliver.intent.action.PROGRESO y lo
definiremos como atributo esttico de la clase para mayor comodidad,
llamado ACTION_PROGRESO. Por su parte, los datos a comunicar en nuestro ejemplo
ser solo el nivel de progreso, por lo que slo aadiremos un extra a nuestro intent con
dicho
dato.
Por
ltimo
enviaremos
el
mensaje
llamando
al
mtodosendBroadcast() pasndole como parmetro el intent recin creado. Veamos cmo
quedara el cdigo completo.
1
public class MiIntentService extends IntentService {
2
3
public static final String ACTION_PROGRESO =
4
"net.sgoliver.intent.action.PROGRESO";
5
public static final String ACTION_FIN =
6
"net.sgoliver.intent.action.FIN";
7
8
public MiIntentService() {
9
super("MiIntentService");
10
}
11
12
@Override
13
protected void onHandleIntent(Intent intent)
14
{
15
int iter = intent.getIntExtra("iteraciones", 0);
16
17
for(int i=1; i<=iter; i++) {
18
tareaLarga();
19
20
//Comunicamos el progreso
21
Intent bcIntent = new Intent();
22
bcIntent.setAction(ACTION_PROGRESO);
23
bcIntent.putExtra("progreso", i*10);
24
sendBroadcast(bcIntent);
25
}
26
27
Intent bcIntent = new Intent();
28
bcIntent.setAction(ACTION_FIN);
29
sendBroadcast(bcIntent);
30
}
31
32
private void tareaLarga()

366

33
{
34
try {
35
Thread.sleep(1000);
36
} catch(InterruptedException e) {}
37
}
38 }
Como podis comprobar tambin he aadido un nuevo tipo de mensaje broadcast
(ACTION_FIN), esta vez sin datos adicionales, para comunicar a la aplicacin principal la
finalizacin de la tarea en segundo plano.
Adems de la implementacin del servicio, recordemos que tambin tendremos que
declararlo en elAndroidManifest.xml, dentro de la seccin <application>:
1 <service android:name=".MiIntentService"></service>
Y con esto ya tendramos implementado nuestro servicio. El siguiente paso ser llamar al
servicio para comenzar su ejecucin. Esto lo haremos desde una actividad principal de
ejemplo en la que tan slo colocaremos una barra de progreso y un botn para lanzar el
servicio. El cdigo del botn para ejecutar el servicio ser muy sencillo, tan slo tendremos
que crear un nuevo intent asociado a la claseMiIntentService, aadir los datos de entrada
necesarios mediante putExtra() y ejecutar el servicio llamando a startService() pasando
como parmetro el intent de entrada. Como ya dijimos, el nico dato de entrada que
pasaremos ser el nmero de iteraciones a ejecutar.
1 btnEjecutar.setOnClickListener(new OnClickListener() {
2
3
@Override
4
public void onClick(View v) {
5
Intent msgIntent = new Intent(MainActivity.this, MiIntentService.class);
6
msgIntent.putExtra("iteraciones", 10);
7
startService(msgIntent);
8
}
9 });
Con esto ya podramos ejecutar nuestra aplicacin y lanzar la tarea, pero no podramos ver
el progreso de sta ni saber cundo ha terminado porque an no hemos creado
el BroadcastReceiver necesario para capturar los mensajes broadcast que enva el servicio
durante su ejecucin.
Para ello, como clase interna a nuestra actividad principal definiremos una nueva clase que
extienda aBroadcastReceiver y que implemente su mtodo onReceive() para gestionar los
mensajesACTION_PROGRESO y ACTION_FIN que definimos en nuestro IntentService.
En el caso de recibirseACTION_PROGRESO extraeremos el nivel de progreso del intent
recibido y actualizaremos consecuentemente la barra de progreso mediante setProgress().

367

En caso de recibirse ACTION_FINmostraremos un mensaje Toast informando de la


finalizacin de la tarea.
public class ProgressReceiver extends BroadcastReceiver {
1
2
@Override
3
public void onReceive(Context context, Intent intent) {
4
if(intent.getAction().equals(MiIntentService.ACTION_PROGRESO)) {
5
int prog = intent.getIntExtra("progreso", 0);
6
pbarProgreso.setProgress(prog);
7
}
8
else if(intent.getAction().equals(MiIntentService.ACTION_FIN)) {
9
Toast.makeText(MainActivity.this,
"Tarea
finalizada!",
10
Toast.LENGTH_SHORT).show();
11
}
12
}
13
}
Pero an no habramos terminado dado, ya que aunque hayamos implementado
nuestroBroadcastReceiver, ste no tendr ningn efecto a menos que lo registremos con la
aplicacin y lo asociemos a los tipos de mensaje que deber tratar (mediante
un IntentFilter). Para hacer esto, al final del mtodo onCreate() de nuestra actividad
principal crearemos un IntentFilter al que asociaremos mediante addAction() los dos tipos
de mensaje broadcast que queremos capturar, instanciaremos nuestro BroadcastReceiver y
lo registraremos mediante registerReceiver(), al que pasaremos la instancia creada y el
filtro de mensajes.
1 IntentFilter filter = new IntentFilter();
2 filter.addAction(MiIntentService.ACTION_PROGRESO);
3 filter.addAction(MiIntentService.ACTION_FIN);
4 ProgressReceiver rcv = new ProgressReceiver();
5 registerReceiver(rcv, filter);
Y con esto s habramos concluido nuestra aplicacin de ejemplo. Si ejecutamos la
aplicacin en el emulador y pulsamos el botn de comenzar la tarea veremos cmo la barra
de progreso comienza a avanzar hasta el final, momento en el que deber aparecer el
mensaje toast indicando la finalizacin de la tarea.

368

Android IntentService
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.

Depuracin de aplicaciones en Android


Depuracin en Android: Logging
by Sgoliver on 28/04/2011 in Android, Programacin
Hacemos un pequeo alto en el camino en el Curso de Programacin Android para hablar
de un tema que, si bien no es especfico de Android, s nos va a resultar bastante til a la
hora de explorar otras caractersticas de la plataforma.
Una de las tcnicas ms tiles a la hora de depurar y/o realizar el seguimiento de
aplicaciones sobre cualquier plataforma es la creacin de logs de ejecucin. Android por
supuesto no se queda atrs y nos proporciona tambin su propio servicio y API de logging a
travs de la clase android.util.Log.
En Android, todos los mensajes de log llevarn asociada la siguiente informacin:

Fecha/Hora del mensaje.


Criticidad. Nivel de gravedad del mensaje (se detalla ms adelante).
PID. Cdigo interno del proceso que ha introducido el mensaje.
Tag. Etiqueta identificativa del mensaje (se detalla ms adelante).
Mensaje. El texto completo del mensaje.
De forma similar a como ocurre con otros frameworks de logging, en Android los mensajes
de log se van a clasificar por su criticidad, existiendo as varias categorias (ordenadas de
mayor a menor criticidad):

1.
2.
3.
4.
5.

Error
Warning
Info
Debug
Verbose
Para cada uno de estos tipos de mensaje existe un mtodo esttico independiente que
permite aadirlo al log de la aplicacin. As, para cada una de las categoras anteriores
tenemos disponibles los mtodos e(),w(), i(), d() y v() respectivamente.
Cada uno de estos mtodos recibe como parmetros la etiqueta (tag) y el texto en s del
mensaje. Como etiqueta de los mensajes, aunque es un campo al que podemos pasar
cualquier valor, suele utilizarse el nombre de la aplicacin o de la actividad concreta que

369

genera el mensaje. Esto nos permitir ms tarde crear filtros personalizados para identificar
y poder visualizar nicamente los mensajes de log que nos interesan, entre todos los
generados por Android [que son muchos] durante la ejecucin de la aplicacin.
Hagamos un miniprograma de ejemplo para ver cmo fuenciona esto. El programa ser tan
simple como aadir varios mensajes de log dentro del mismo onCreate de la actividad
principal y ver qu ocurre. Os muestro el cdigo completo:
public class LogsAndroid extends Activity {
private static final String LOGTAG = "LogsAndroid";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.e(LOGTAG, "Mensaje de error");
Log.w(LOGTAG, "Mensaje de warning");
Log.i(LOGTAG, "Mensaje de informacin");
Log.d(LOGTAG, "Mensaje de depuracin");
Log.v(LOGTAG, "Mensaje de verbose");
}
}
Si ejecutamos la aplicacin anterior en el emulador veremos cmo se abre la pantalla
principal que crea Eclipse por defecto y aparentemente no ocurre nada ms. Dnde
podemos ver los mensajes que hemos aadido al log? Pues para ver los mensajes de log nos
tenemos que ir a la perspectiva de Eclipse llamadaDDMS. Una vez en esta perspectiva,
podemos acceder a los mensajes de log en la parte inferior de la pantalla, en una vista
llamada LogCat. En esta ventana se muestran todos los mensajes de log que genera
Android durante la ejecucin de la aplicacin, que son muchos, pero si buscamos un poco
en la lista encontraremos los generados por nuestra aplicacin, tal como se muestra en la
siguiente imagen (click para ampliar):

Como se puede observar, para cada mensaje se muestra toda la informacin que indicamos
al principio del artculo, adems de estar diferenciados por un color distinto segn su
criticidad.

370

En este caso de ejemplo, los mensajes mostrados son pocos y fciles de localizar en el log,
pero para una aplicacin real, el nmero de estos mensajes puede ser mucho mayor y
aparecer intercalados caticamente entre los dems mensajes de Android. Para estos casos,
la ventada de LogCat ofrece una serie de funcionalidades para facilitar la visualizacin y
bsqueda de determinados mensajes.
Por ejemplo, podemos restringir la lista para que slo muestre mensajes con una
determinada criticidad mnima. Esto se consigue pulsando alguno de los 5 primeros botones
que se observan en la parte superior derecha de la ventana de log. As, si por ejemplo
pulsamos sobre el botn de la categora Info (en verde), en la lista slo se mostrarn los
mensajes con criticidad Error, Warning e Info.
Otro mtodo de filtrado ms interesante es la definicin de filtros personalizados (botn
+ verde), donde podemos filtrar la lista para mostrar nicamente los mensajes con un PID
o Tag determinado. Si hemos utilizado como etiqueta de los mensajes el nombre de nuestra
aplicacin o de nuestras actividades esto nos proporcionar una forma sencilla de visualizar
slo los mensajes generados por nuestra aplicacin.
As, para nuestro ejemplo, podramos crear un filtro indicando como Tag la cadena
LogsAndroid, tal como se muestra en la siguiente imagen:

Esto crear una nueva ventana de log con el nombre que hayamos especificado en el filtro,
donde slo aparecern nuestros 5 mensajes de log de ejemplo (click para ampliar):

Por ltimo, cabe mencionar que existe una variante de cada uno de los mtodos de la clase
Log que recibe un parmetro ms en el que podemos pasar un objeto de tipo excepcin.

371

Con esto conseguimos que, adems del mensaje de log indicado, se muestre tambin la
traza de error generada con la excepcin.
Veamos esto con un ejemplo, y para ello vamos a forzar un error de divisin por cero,
vamos a capturar la excepcin y vamos a generar un mensaje de log con la variante
indicada:
try
{
int a = 1/0;
}
catch(Exception ex)
{
Log.e(LOGTAG, "Divisin por cero!", ex);
}
Si volvemos a ejecutar la aplicacin y vemos el log generado, podermos comprobar cmo
se muestra la traza de error corespondiente generada con la excepcin (click para ampliar).

En definitiva, podemos comprobar como la generacin de mensajes de log puede ser una
herramienta sencilla pero muy efectiva a la hora de depurar aplicaciones que no se ajustan
mucho a la depuracin paso a paso, o simplemente para generar trazas de ejecucin de
nuestras aplicaciones para comprobar de una forma sencilla cmo se comportan.

Google Play Services


Google Play Services: Introduccin y Preparativos
by Sgoliver on 18/08/2013 in Android, Programacin
Desde hace algn tiempo, y de forma bastante acertada, Google est tendiendo a incorporar
muchas de sus nuevas APIs para desarrolladores Android (y actualizaciones de algunas ya
existentes) dentro de los llamados Google Play Services. As por ejemplo, la API de mapas,
la de localizacin, o la de mensajera push, que antes existan de forma independiente, han
pasado a formar parte de estos servicios en los ltimos meses. Y por supuesto se han

372

incorporado otras nuevas, como la de integracin con Google+ o los famosos Game
Services.
Los Google Play Services viven como una aplicacin ms en todos los dispositivos Android
(versin 2.2 y superiores), lo que nos aporta la ventaja de no tener que preocuparnos de
ellos, ya que son actualizados automticamente por la plataforma cuando existen
novedades. Por decirlo de alguna forma, nosotros tan slo nos tendremos que preocupar de
conectarnos a ellos y utilizar las distintas funcionalidades como si fueran parte de nuestra
propia aplicacin.
En este artculo inicial vamos a ver cmo podemos crear en Eclipse un proyecto capaz de
hacer uso de los Google Play Services. Una vez preparado el proyecto como veremos aqu
cada servicio requerir de pasos adicionales para su utilizacin, por ejemplo el uso de
Google Maps requerir de la obtencin de una clave de acceso que nos permita el uso de su
API. Estos preparativos adicionales los veremos en cada captulo especfico de cada uno de
los servicios.
Empecemos. Cuando queremos hacer uso de cualquiera de las APIs incluidas en los Google
Play Services lo primero que tendremos que hacer ser importar en Eclipse el proyecto de
librera donde se implementan. Este proyecto se puede descargar mediante el SDK
Manager de Android, accediendo a la seccin Extras y marcando el paquete llamado
Google Play Services.

Si observis la captura anterior, veris que tambin existe un paquete llamado Google Play
Services for Froyo. ste ltimo slo deberamos utilizarlo (en sustitucin del primero) si
nuestra aplicacin necesita ejecutarse en dispositivos con Android 2.2, ya que esta versin
de los Play Services probablemente no recibir las futuras actualizaciones de los servicios.
Por tanto, si la versin mnima sobre la que debe ejecutarse nuestra aplicacin es al menos
la 2.3 usaremos siempre el paquete Google Play Services.
Una vez descargado podremos encontrarlo en la siguiente ruta:

373

<ruta-sdk>\extras\google\google_play_services\libproject\google-play-services_lib
Para importarlo en Eclipse utilizaremos la opcin de men File / Import, y
seleccionaremos la opcin Android / Existing Android Code Into Workspace.

Pulsamos el botn Next y accedemos a las opciones de importacin. En el campo Root


Directory indicamos la ruta indicada anteriormente, nos aseguramos que el proyecto
llamado google-play-services_lib queda marcado en la lista de Projects to Import, y
marcamos la opcin Copy projects into workspace.

Finalmente pulsamos Finish y el proyecto de librera de los Google Play Services


quedar importado en nuestro explorador de paquetes de Eclipse. Como ltimo paso

374

debemos entrar a las propiedades del proyecto importado (botn derecho / Properties),
accedemos a la seccin Java Build Path y nos aseguramos de que la opcin Android
Private Libraries est marcada en la ventana Order and Export.

Aceptamos, y con esto ya tenemos el proyecto de librera preparado.


El siguiente paso es crear nuestro proyecto, que despus har uso de esta librera. Para ello,
creamos un nuevo proyecto Android como siempre (File / New / Project / Android /
Android Application Project) y lo configuramos mediante las distintas pantallas del
asistente (si no tienes experiencia creando este tipo de proyectos te recomiendo leer
los captulos iniciales del curso). Una vez creado accedemos a sus propiedades (botn
derecho / Properties) y entramos en la seccin Android. Aqu es donde debemos aadir la
referencia al proyecto que hemos importado para los Google Play Services. Pulsamos el
botn Add, seleccionamos el proyecto importado y aceptamos.

A continuacin, editaremos el fichero AndroidManifest.xml de nuestra aplicacin y


aadiremos la siguiente clusula <meta-data> dentro del elemento <application>:
1 <meta-data android:name="com.google.android.gms.version"
2
android:value="@integer/google_play_services_version" />

375

Por ltimo, aadiremos al final del fichero proguard-project.txt las siguientes lineas para
evitar que esta herramienta elimine algunas clases necesarias:
1
-keep class * extends java.util.ListResourceBundle {
2
protected Object[][] getContents();
3
}
4
5
-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
6
public static final *** NULL;
7
}
8
9
-keepnames @com.google.android.gms.common.annotation.KeepName class *
10 -keepclassmembernames class * {
11
@ccom.google.android.gms.common.annotation.KeepName *;
12 }
13
14 -keepnames class * implements android.os.Parcelable {
15
public static final ** CREATOR;
16 }
Con esto ya tendramos nuestro proyecto preparado para hacer uso de cualquiera de las
APIs incluidas en los Google Play Services. En los artculos dedicados a cada una de estas
APIs haremos siempre referencia a ste para preparar el proyecto y describiremos
posteriormente los pasos adicionales especficos de cada servicio.

II. Mapas en Android


Mapas en Android (Google Maps Android API v2) I
by Sgoliver on 05/12/2012 in Android, Programacin
En diciembre de 2012, Google presentaba la segunda versin de su API de Google Maps

para Android. Esta nueva versin presenta muchas novedades interesantes, de las que cabe
destacar las siguientes:
Integracin con los Servicios de Google Play (Google Play Services) y la Consola de APIs.
Utilizacin a travs de un nuevo tipo especfico de fragment (MapFragment), una mejora
muy esperada por muchos.
Utilizacin de mapas vectoriales, lo que repercute en una mayor velocidad de carga y una
mayor eficiencia en cuanto a uso de ancho de banda.
Mejoras en el sistema de cach, lo que reducir en gran medida las famosas reas en blanco
que tardan en cargar.
Los mapas son ahora 3D, es decir, podremos mover nuestro punto de vista de forma que lo
veamos en perspectiva.

376

Al margen de las novedades generales, como desarrolladores qu diferencias nos vamos a


encontrar con respecto a la API anterior a la hora de desarrollar nuestras aplicaciones
Android?
Pues la principal ser el componente que utilizaremos para la inclusin de mapas en nuestra
aplicacin. Si recordamos la anterior versin de la API, para incluir un mapa en la
aplicacin debamos utilizar un control de tipo MapView, que adems requera que su
actividad contenedora fuera del tipo MapActivity. Con la nueva API nos olvidaremos de
estos dos componentes y pasaremos a tener slo uno, un nuevo tipo especfico
de fragment llamado MapFragment. Esto nos permitir entre otras cosas aadir uno [o
varios, esto tambin es una novedad] mapas a cualquier actividad, sea del tipo que sea, y
contando por supuesto con todas las ventajas del uso de fragments. Nota importante: dado
que el nuevo control de mapas se basa en fragments, si queremos mantener la
compatibilidad con versiones de Android anteriores a la 3.0 tendremos que utilizar la
librera de soporte android-support. Ms adelante veremos ms detalles sobre esto.
Adems de esta novedad, la integracin de la API con los Google Play Services y
la Consola de APIs de Google, harn que los preparativos del entorno, las libreras
utilizadas, y el proceso de obtencin de la API Key de Google Maps sean un poco distintos
a los que ya vimos en los artculos dedicados la primera versin.
Pues bien, en este nuevo artculo del Curso de Programacin Android vamos a describir los
pasos necesarios para hacer uso de la nueva versin de la API de mapas de Google (Google
Maps Android API v2).
Como en los artculos previos donde aprendimos a utilizar la API v1, en este caso tambin
ser necesario realizar algunos preparativos y tareas previas antes de poder empezar a
utilizarlos en nuestras aplicaciones.
En primer lugar, dado que la API v2 se proporciona como parte del SDK de Google Play
Services, ser necesario incorporar y configurar previamente en nuestro entorno de
desarrollo dicho paquete. Ya dedicamos un artculo especfico a describir esta tarea, por lo
que no lo volveremos a repetir aqu.
El siguiente paso ser obtener una API Key para poder utilizar el servicio de mapas de
Google en nuestra aplicacin. Este paso ya lo comentamos en los artculos sobre la API v1,
pero en este caso el procedimiento ser algo distinto. La nueva API de mapas de Android,
al igual que la mayora de APIs de Google, est integrada en la nueva Consola de APIs de
Google, por lo que el primer paso ser acceder a ella. Una vez hemos accedido, tendremos

377

que crear un nuevo proyecto mediante el botn CREATE PROJECT situado en la parte
superior izquierda de la pantalla.

Aparecer entonces una ventana que nos solicitar el nombre e ID del proyecto.
Introducimos algn nombre descriptivo y un ID nico y aceptamos pulsando Create.

Una vez creado el proyecto, accederemos a la opcin APIs & Auth del men izquierdo.
Desde esta ventana podemos activar o desactivar cada una de las APIs de Google que
queremos utilizar. En este caso slo activaremos la llamada Google Maps Android API
v2 pulsando sobre el botn ON/OFF situado justo a su derecha.

Una vez activado accederemos a la opcin Registered Apps del men izquierdo
(submen de APIs & Auth) y pulsaremos el botn REGISTER APP para registrar
nuestra aplicacin.

378

Accediendo a dicha opcin tendremos la posibilidad de obtener nuestra nueva API Key que
nos permita utilizar el servicio de mapas desde nuestra aplicacin particular. Tendremos
que indicar el nombre de la aplicacin, su tipo (Android), el modo de acceso a la API (en
este caso Accessing APIs directly from Android), el paquete java utilizado en la
aplicacin (que en mi caso ser net.sgoliver.android.mapasapi2) y la huella digital SHA1
del certificado con el que firmamos la aplicacin.

Este ltimo dato introducido requiere alguna explicacin. Toda aplicacin Android debe ir
firmada para poder ejecutarse en un dispositivo, tanto fsico como emulado. Este proceso
de firma es uno de los pasos que tenemos que hacer siempre antes de distribuir
pblicamente una aplicacin. Adicionalmentes, durante el desarrollo de la misma, para
realizar las pruebas y la depuracin del cdigo, aunque no seamos conscientes de ello
tambin estamos firmado la aplicacin con un certificado de pruebas.
En las ltimas versiones de Eclipse y el plugin ADT de Android, podemos consultar
directamente la huella SHA1 de nuestro certificado de pruebas accediendo al men
Window /Preferences y entrando a la seccin Android / Build.

379

En caso de disponer de una versin ms antigua que no muestre directamente este dato, lo
que al menos s debe aparecer es la ruta del certificado de pruebas (campo Default debug
keystore). Como se puede observar, en mi caso el certificado de pruebas est en la ruta
C:\Users\Salvador\.android\debug.keystore. Pues bien, si tuviramos que obtener
manualmente nuestra huella digital SHA1 deberemos acceder a dicha ruta desde la consola
de comando de Windows y ejecutar los siguientes comandos:
1
2
3

C:\>cd C:\Users\Salvador\.android\

C:\Users\Salvador\.android>"C:\Program Files\Java\jdk1.7.0_07\bin\keytool.exe" -list -v -keystore debug

Suponiendo que tu instalacin de Java est en la ruta C:\Program Files\Java\jdk1.7.0_07.


Si no es as slo debes sustituir sta por la correcta. Esto nos deber devolver varios datos
del certificado, entre ellos la huella SHA1.

Una vez rellenos todos los datos de la aplicacin, pulsamos el botn Register y ya
deberamos tener nuestra API Key generada, podremos verla en la pantalla siguiente dentro
del apartado Android Key. Apuntaremos este dato para utilizarlo ms tarde.

380

Con esto ya habramos concluido los preparativos iniciales necesarios para utilizar el
servicio de mapas de Android en nuestras propias aplicaciones, por lo que empecemos a
crear un proyecto de ejemplo en Eclipse.
Abriremos Eclipse y crearemos un nuevo proyecto estandar de Android, en mi caso lo he
llamado android-mapas-api2. Recordemos utilizar para el proyecto el mismo paquete java
que hemos indicado durante la obtencin de la API key.
Tras esto lo primero que haremos ser aadir al fichero AndroidManifest.xml la API Key
que acabamos de generar. Para ello aadiremos al fichero, dentro de la
etiqueta <application>, un nuevo elemento<meta-data> con los siguientes datos:
1 ...
2 <application>
3 ...
4
<meta-data android:name="com.google.android.maps.v2.API_KEY"
5
android:value="api_key"/>
6 ...
7 </application>
Como valor del parmetro android:value tendremos que poner nuestra API Key recien
generada.
Siguiendo con el AndroidManifest, tambin tendremos que incluir una serie de permisos
que nos permitan acceder a internet (INTERNET), conocer el estado de la red
(ACCESS_NETWORK_STATE), acceder al almacenamiento externo del dispositivo para
la cach de mapas (WRITE_EXTERNAL_STORAGE) y hacer uso de los servicios web de
Google (READ_GSERVICES).

381

1
2
3
4

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

Por ltimo, dado que la API v2 de Google Maps Android utiliza OpenGL ES versin 2,
deberemos especificar tambin dicho requisito en nuestro AndroidManifest aadiendo un
nuevo elemento <uses-feature>:
1 <uses-feature android:glEsVersion="0x00020000"
2
android:required="true"/>
El siguiente paso ser referenciar el proyecto de librera de los Google Play Services desde
nuestro proyecto de ejemplo, si no lo hemos hecho ya. Para ello iremos a sus propiedades
pulsando botn derecho / Properties sobre nuestro proyecto y accediendo a la seccin
Android de las preferencias. En dicha ventana podemos aadir una nueva librera en la
seccin inferior llamada Library. Cuando pulsamos el botn Add nos aparecer la
librera recien importada y podremos seleccionarla directamente, aadindose a nuestra
lista de libreras referenciadas por nuestro proyecto.

Como ltimo paso de configuracin de nuestro proyecto, si queremos que nuestra


aplicacin se pueda ejecutar desde versiones antiguas de Android (concretamente desde
la versin de Android 2.2) deberemos asegurarnos de que nuestro proyecto incluye la
librera android-support-v4.jar, que debera aparecer si desplegamos las seccin Android
Private Libraries o la carpeta libs de nuestro proyecto.

382

Las versiones ms recientes de ADT incluyen por defecto esta librera en nuestros
proyectos, pero si no est incluida podis hacerlo mediante la opcin del men contextual
Android Tools / Add Support Library sobre el proyecto, o bien de forma manual.
Y con esto hemos terminado de configurar todo lo necesario. Ya podemos escribir nuestro
cdigo. Y para este primer artculo sobre el tema nos vamos a limitar a mostrar un mapa en
la pantalla principal de la aplicacin. En artculos posteriores veremos como aadir otras
opciones o elementos al mapa.
Para esto tendremos simplemente que aadir el control correspondiente al layout de nuestra
actividad principal. En el caso de la nueva API v2 este control se aadir en forma
de fragment (de ah que hayamos tenido que incluir la librera android-support para poder
utilizarlos en versiones de Android anteriores a la 3.0) de un determinado tipo
(concretamente de la nueva clase com.google.android.gms.maps.SupportMapFragment),
quedando por ejemplo de la siguiente forma:
1 <?xml version="1.0" encoding="utf-8"?>
2 <fragment xmlns:android="http://schemas.android.com/apk/res/android"
3
android:id="@+id/map"
4
android:layout_width="match_parent"
5
android:layout_height="match_parent"
6
class="com.google.android.gms.maps.SupportMapFragment"/>
Por supuesto, dado que estamos utilizando fragments, la actividad principal tambin tendr
que extender aFragmentActivity (en vez de simplemente Activity como es lo normal).
Usaremos tambin la versin de FragmentActivity incluida en la librera androidsupport para ser compatibles con la mayora de las versiones Android actuales.

383

1
2
3
4
5
6
7
8
9
10

public class MainActivity extends android.support.v4.app.FragmentActivity {


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

Con esto, ya podramos ejecutar y probar nuestra aplicacin. En mi caso las pruebas las he
realizado sobre un dispositivo fsico con Android 2.2 ya que por el momento parece haber
algunos problemas para hacerlo sobre el emulador. Por tanto tendris que conectar vuestro
dispositivo al PC mediante el cable de datos e indicar a Eclipse que lo utilice para la
ejecucin de la aplicacin.
Si ejecutamos el ejemplo deberamos ver un mapa en la pantalla principal de la aplicacin,
sobre el que nos podremos mover y hacer zoom con los gestos habituales o utilizando los
controles de zoom incluidos por defecto sobre el mapa.

Con este artculo espero haber descrito todos los pasos necesarios para comenzar a utilizar
los servicios de mapas de Google utilizando su nueva API Google Maps Android v2. Si
tenis cualquier duda o propuesta de mejora no dudis en escribirlo en los comentarios.

384

Como habis podido comprobar hay muchos preparativos que hacer, aunque ninguno de
ellos de excesiva dificultad. En los prximos artculos aprenderemos a utilizar ms
caractersticas de la nueva API.

Mapas en Android (Google Maps Android API v2) II


by Sgoliver on 09/12/2012 in Noticias, Programacin
En el artculo anterior del curso vimos cmo realizar todos los preparativos necesarios para
comenzar a utilizar la nueva versin de Google Maps para Android (Google Maps Android
API v2): descargar las libreras necesarias, obtener la API Key y configurar un nuevo
proyecto en Eclipse.
En esta segunda entrega vamos a hacer un repaso de las opciones bsicas de los nuevos
mapas: elegir el tipo de mapa a mostrar, movernos por l de forma programtica, y obtener
los datos de la posicin actual. Como aplicacin de ejemplo (que podis descargar al final
de este artculo), tomaremos como base la ya creada en el artculo anterior, a la que
aadiremos varias opciones de men (men tradicional o de overflow en la ActionBar,
dependiendo de la versin de Android sobre la que lo ejecutemos) para demostrar el
funcionamiento de algunas funciones del mapa.
Si hacemos un poco de memoria, recordaremos cmo en la antigua versin de la API de
Google Maps era bastante poco homogneo el acceso y modificacin de determinados
datos del mapa. Por ejemplo, la consulta de la posicin actual o la configuracin del tipo de
mapa se hacan directamente sobre el controlMapView, mientras que la manipulacin de la
posicin y el zoom se hacan a travs del controladorasociado al mapa (MapController).
Adems, el tratamiento de las coordenadas y las unidades utilizadas eran algo peculiares,
teniendo estar continuamente convirtiendo de grados a microgrados y de estos a
objetos GeoPoint, etc.
Con la nueva API, todas las operaciones se realizarn directamente sobre un
objeto GoogleMap, el componente base de la API. Accederemos a este componente
llamando al mtodo getMap() del fragmento MapFragment que contenga nuestro mapa.
Podramos hacerlo de la siguiente forma:
1 import com.google.android.gms.maps.GoogleMap;
2 ...
3 GoogleMap mapa = ((SupportMapFragment) getSupportFragmentManager()
4
.findFragmentById(R.id.map)).getMap();
Una vez obtenida esta referencia a nuestro objeto GoogleMap podremos realizar sobre l la
mayora de las acciones bsicas del mapa.

385

As, por ejemplo, para modificar el tipo de mapa mostrado podremos utilizar una llamada a
su mtodosetMapType(), pasando como parmetro el tipo de mapa:
MAP_TYPE_NORMAL
MAP_TYPE_HYBRID
MAP_TYPE_SATELLITE
MAP_TYPE_TERRAIN
Para nuestro ejemplo voy a utilizar una variable que almacene el tipo de mapa actual (del 0
al 3) y habilitaremos una opcin de men para ir alternando entre las distintas opciones.
Quedara de la siguiente forma:
1
private void alternarVista()
2
{
3
vista = (vista + 1) % 4;
4
5
switch(vista)
6
{
7
case 0:
8
mapa.setMapType(GoogleMap.MAP_TYPE_NORMAL);
9
break;
10
case 1:
11
mapa.setMapType(GoogleMap.MAP_TYPE_HYBRID);
12
break;
13
case 2:
14
mapa.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
15
break;
16
case 3:
17
mapa.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
18
break;
19
}
20 }
En cuanto al movimiento sobre el mapa, con esta nueva versin de la API vamos a tener
mucha ms libertad que con la anterior versin, ya que podremos mover libremente nuestro
punto de vista (o cmara, como lo han llamado los chicos de Android) por un espacio 3D.
De esta forma, ya no slo podremos hablar de latitud-longitud (target) y zoom, sino
tambin de orientacin (bearing) y ngulo de visin (tilt). La manipulacin de los 2 ltimos
parmetros unida a posibilidad actual de ver edificios en 3D de muchas ciudades nos abren
un mundo de posibilidades.
El movimiento de la cmara se va a realizar siempre mediante la construccin de un
objeto CameraUpdatecon los parmetros necesarios. Para los movimientos ms bsicos
como la actualizacin de la latitud y longitud o el nivel de zoom podremos utilizar la
clase CameraUpdateFactory y sus mtodos estticos que nos facilitar un poco el trabajo.

386

As por ejemplo, para cambiar slo el nivel de zoom podremos utilizar los siguientes
mtodos para crear nuestro CameraUpdate:
CameraUpdateFactory.zoomIn(). Aumenta en 1 el nivel de zoom.
CameraUpdateFactory.zoomOut(). Disminuye en 1 el nivel de zoom.
CameraUpdateFactory.zoomTo(nivel_de_zoom). Establece el nivel de zoom.
Por su parte, para actualizar slo la latitud-longitud de la cmara podremos utilizar:
CameraUpdateFactory.newLatLng(lat, long). Establece la lat-lng expresadas en grados.
Si queremos modificar los dos parmetros anteriores de forma conjunta, tambin tendremos
disponible el mtodo siguiente:

CameraUpdateFactory.newLatLngZoom(lat, long, zoom). Establece la lat-lng y el zoom.


Para movernos lateralmente por el mapa (panning) podramos utilizar los mtodos de

scroll:
CameraUpdateFactory.scrollBy(scrollHorizontal, scrollVertical). Scroll expresado en
pxeles.
Tras construir el objeto CameraUpdate con los parmetros de posicin tendremos que
llamar a los mtodos moveCamera() o animateCamera() de nuestro objeto GoogleMap,
dependiendo de si queremos que la actualizacin de la vista se muestre directamente o de
forma animada.
Con esto en cuenta, si quisiramos por ejemplo centrar la vista en Espaa con un zoom de 5
podramos hacer lo siguiente:
1
2
3
4

CameraUpdate camUpd1 =
CameraUpdateFactory.newLatLng(new LatLng(40.41, -3.69));
mapa.moveCamera(camUpd1);

Adems de los movimientos bsicos que hemos comentado, si queremos modificar los
dems parmetros de la cmara o varios de ellos simultaneamente tendremos disponible el
mtodo ms generalCameraUpdateFactory.newCameraPosition() que recibe como
parmetro un objeto de tipoCameraPosition. Este objeto los construiremos indicando todos
los parmetros de la posicin de la cmara a travs de su mtodo Builder() de la siguiente
forma:
1
LatLng madrid = new LatLng(40.417325, -3.683081);
2
CameraPosition camPos = new CameraPosition.Builder()
3
.target(madrid) //Centramos el mapa en Madrid
4
.zoom(19)
//Establecemos el zoom en 19
5
.bearing(45)
//Establecemos la orientacin con el noreste arriba
6
.tilt(70)
//Bajamos el punto de vista de la cmara 70 grados
7
.build();
8

387

9
10
11
12

CameraUpdate camUpd3 =
CameraUpdateFactory.newCameraPosition(camPos);
mapa.animateCamera(camUpd3);

Como podemos comprobar, mediante este mecanismo podemos modificar todos los
parmetros de posicin de la cmara (o slo algunos de ellos) al mismo tiempo. En nuestro
caso de ejemplo hemos centrado la vista del mapa sobre el parque de El Retiro de Madrid,
con un nivel de zoom de 19, una orientacin de 45 grados para que el noreste est hacia
arriba y un ngulo de visin de 70 grados de forma que veamos en 3D el monumento a
Alfonso XII en la vista de mapa NORMAL. En la siguiente imagen vemos el resultado:

Como podis ver, en esta nueva versin de la API se facilita bastante el posicionamiento
dentro del mapa, y el uso de las clases CameraUpdate y CameraPosition resulta bastante
intuitivo.
Bien, pues ya hemos hablado de cmo modificar nuestro punto de vista sobre el mapa, pero
si el usuario se mueve de forma manual por l, cmo podemos conocer en un momento
dado la posicin de la cmara?
Pues igual de fcil, mediante el mtodo getCameraPosition(), que nos devuelve un
objetoCameraPosition como el que ya conocamos. Accediendo a los distintos mtodos y
propiedades de este objeto podemos conocer con exactitud la posicin de la cmara, la
orientacin y el nivel de zoom.
1 CameraPosition camPos = mapa.getCameraPosition();
2
3 LatLng coordenadas = camPos.target;

388

4 double latitud = coordenadas.latitude;


5 double longitud = coordenadas.longitude;
6
7 float zoom = camPos.zoom;
8 float orientacion = camPos.bearing;
9 float angulo = camPos.titl;
En nuestra aplicacin de ejemplo, que podis descargar al final del artculo, he aadido una
nueva opcin de men que muestra en un mensaje toast la latitud y longitud actual de la
vista de mapa.

Y con esto habramos terminado de describir las acciones bsicas de configuracin y


movimiento sobre el mapa. En los prximos artculos veremos ms opciones, como la
forma de aadir marcadores o dibujar sobre el mapa.

Mapas en Android (Google Maps Android API v2) III


by Sgoliver on 10/12/2012 in Android, Programacin
En los dos artculos anteriores (I y II) del curso hemos visto cmo crear aplicaciones
utilizando la nueva versin de la API v2 de Google Maps para Android y hemos descrito
las acciones principales sobre el mapa.
En este ltimo artculo de la serie nos vamos a centrar en los eventos del mapa que
podemos capturar y tratar, en la creacin y gestin de marcadores, y en el dibujo de lineas y
polgonos sobre el mapa.
Sin ms prembulos, comencemos con los eventos. Si hacemos un poco de memoria, con la
versin anterior de la API debamos crear una nueva capa (overlay) para capturar los

389

eventos principales de pulsacin. Sin embargo, el nuevo componente de mapas soporta


directamente los eventos de click, click largo y movimiento de cmara y podemos
implementarlos de la forma habitual, mediante su mtodo set correspondiente.
As por ejemplo, podramos implementar el evento de onclick llamando al
mtodosetOnMapClickListener() con
un
nuevo
listener
y
sobrescribir
el
mtodo onMapClick(). Este mtodo recibe como parmetro, en forma de objeto LatLng, las
coordenadas de latitud y longitud sobre las que ha pulsado el usuario. Si quisiramos
traducir estas coordenadas fsica en coordenadas en pantalla podramos utilizar un
objeto Projection (similar al que ya comentamos para la API v1), obtenindolo a partir del
mapa
a
travs
del
mtodo getProjection() y
posteriormente
llamando
a toScreenLocation()para obtener las coordenadas (x,y) de pantalla donde el usuario puls.
As, por ejemplo, si quisiramos mostrar un Toast con todos estos datos cada vez que se
pulse sobre el mapa podramos hacer lo siguiente:
1
mapa.setOnMapClickListener(new OnMapClickListener() {
2
public void onMapClick(LatLng point) {
3
Projection proj = mapa.getProjection();
4
Point coord = proj.toScreenLocation(point);
5
6
Toast.makeText(
7
MainActivity.this,
8
"Click\n" +
9
"Lat: " + point.latitude + "\n" +
10
"Lng: " + point.longitude + "\n" +
11
"X: " + coord.x + " - Y: " + coord.y,
12
Toast.LENGTH_SHORT).show();
13
}
14 });
De forma similar podramos implementar el evento de pulsacin larga, con la nica
diferencia de que lo asignaramos mediante setOnMapLongClickListener() y
sobrescribiramos el mtodoonMapLongClick().
1
mapa.setOnMapLongClickListener(new OnMapLongClickListener() {
2
public void onMapLongClick(LatLng point) {
3
Projection proj = mapa.getProjection();
4
Point coord = proj.toScreenLocation(point);
5
6
Toast.makeText(
7
MainActivity.this,
8
"Click Largo\n" +
9
"Lat: " + point.latitude + "\n" +
10
"Lng: " + point.longitude + "\n" +
11
"X: " + coord.x + " - Y: " + coord.y,

390

12
13
14

Toast.LENGTH_SHORT).show();
}
});

As, cuando el usuario hiciera una pulsacin larga sobre el mapa veramos los datos en
pantalla
de
la
siguiente
forma:

Tambin podremos capturar el evento de cambio de cmara, de forma que podamos realizar
determinadas acciones cada vez que el usuario se mueve manualmente por el mapa,
desplazndolo, haciendo zoom, o modificando la orientacin o el ngulo de visin. Este
evento lo asignaremos al mapa mediante su mtodosetOnCameraChangeListener() y
sobrescribiendo el mtodo onCameraChange(). Este mtodo recibe como parmetro un
objeto CameraPosition, que ya vimos en el artculo anterior, por lo que podremos recuperar
de l todos los datos de la cmara en cualquier momento.
De esta forma, si quisiramos mostrar un Toast con todos los datos podramos hacer lo
siguiente:
1
2
3
4
5
6
7
8
9
10
11
12

mapa.setOnCameraChangeListener(new OnCameraChangeListener() {
public void onCameraChange(CameraPosition position) {
Toast.makeText(
MainActivity.this,
"Cambio Cmara\n" +
"Lat: " + position.target.latitude + "\n" +
"Lng: " + position.target.longitude + "\n" +
"Zoom: " + position.zoom + "\n" +
"Orientacin: " + position.bearing + "\n" +
"ngulo: " + position.tilt,
Toast.LENGTH_SHORT).show();
}

391

13 });
Hecho esto, cada vez que el usuario se mueva por el mapa veramos lo siguiente:

El siguiente tema importante que quera tratar en este artculo es el de los marcadores. Rara
es la aplicacin Android que hace uso de mapas sin utilizar tambin este tipo de elementos
para resaltar determinados puntos en el mapa. Si recordamos el artculo sobre la API v1,
vimos cmo podamos aadir marcadores aadiendo una nueva capa (overlay) al mapa y
dibujando nuestro marcador como parte de su evento draw(). En la nueva versin de la API
tendemos toda esta funcionalidad integrada en la propia vista de mapa, y agregar un
marcador resulta tan sencillo como llamar al mtodo addMarker() pasndole la posicin en
forma de objeto LatLng y el texto a mostrar en la ventana de informacin del marcador. En
nuestra aplicacin de ejemplo aadiremos un men de forma que cuando lo pulsemos se
aada automticamente un marcador sobre Espaa con el texto Pais: Espaa. Veamos
cmo escribir un mtodo auxiliar que nos ayuda a hacer esto pasndole las coordenadas de
latitud y longitud:
1 private void mostrarMarcador(double lat, double lng)
2 {
3
mapa.addMarker(new MarkerOptions()
4
.position(new LatLng(lat, lng))
5
.title("Pais: Espaa"));
6 }
As de sencillo, basta con llamar al mtodo addMarker() pasando como parmetro un nuevo
objetoMarkerOptions sobre el que establecemos la posicin del marcador
(mtodo position()) y el texto a incluir en la ventana de informacin del marcador
(mtodos title() para el ttulo y snippet() para el resto del texto). Si ejecutamos la aplicacin

392

de ejemplo y pulsamos el men Marcadores aparecer el siguiente marcador sobre el


mapa (la ventana de informacin aparece si adems se pulsa sobre el marcador):

Adems de facilitarnos la vida con la inclusin de marcadores en el mapa tambin


tendremos ayuda a la hora de capturar el evento de pulsacin sobre un marcador, ya que
podremos asignar al mapa dicho evento como cualquiera de los comentados anteriormente.
En
este
caso
el
evento
se
asignar
al
mapa
mediante
el
mtodo setOnMarkerClickListener() y sobrescribiremos el mtodo onMarkerClick(). Dicho
mtodo recibe como parmetro el objeto Marker pulsado, de forma que podamos
identificarlo accediendo a su informacin (posicin, ttulo, texto, ). Veamos un ejemplo
donde mostramos un toast con el ttulo del marcador pulsado:
1
mapa.setOnMarkerClickListener(new OnMarkerClickListener() {
2
public boolean onMarkerClick(Marker marker) {
3
Toast.makeText(
4
MainActivity.this,
5
"Marcador pulsado:\n" +
6
marker.getTitle(),
7
Toast.LENGTH_SHORT).show();
8
9
return false;
10
}
11 });
Con el cdigo anterior, si pulsamos sobre el marcador de Espaa aparecer el siguiente
mensaje informativo:

393

Todo lo explicado sobre marcadores corresponde al comportamiento por defecto de la API,


sin embargo tambin es posible por supuesto personalizar determinadas cosas, como por
ejemplo el aspecto de los marcadores. Esto se sale un poco de este artculo, donde pretenda
describir los temas ms bsicos, pero para quien est interesado tan slo decir que mediante
los mtodos icon() y anchor() del objetoMakerOptions que hemos visto antes es posible
utilizar una imagen personalizada para mostrar como marcador en el mapa. En
la documentacin oficial (en ingls) podis encontrar un ejemplo de cmo hacer esto.
Como ltimo tema, vamos a ver cmo dibujar lneas y polgonos sobre el mapa, elementos
muy comunmente utilizados para trazar rutas o delimitar zonas del mapa. Para realizar esto
en la versin 2 de la API vamos a actuar una vez ms directamente sobre la vista de mapa,
sin
necesidad
de
aadir overlays o
similares,
y
ayudndonos
de
los
objetos PolylineOptions y PolygonOptions respectivamente.
Para dibujar una linea lo primero que tendremos que hacer ser crear un nuevo
objeto PolylineOptionssobre el que aadiremos utilizando su mtodo add() las coordenadas
(latitud-longitud) de todos los puntos que conformen la linea. Tras esto estableceremos el
grosor y color de la linea llamando a los mtodoswidth() y color() respectivamente, y por
ltimo aadiremos la linea al mapa mediante su mtodoaddPolyline() pasndole el
objeto PolylineOptions recin creado.
En nuestra aplicacin de ejemplo he aadido un nuevo men para dibujar un rectngulo
sobre Espaa. Veamos cmo queda:
1
2
3

private void mostrarLineas()


{
//Dibujo con Lineas

394

4
5
PolylineOptions lineas = new PolylineOptions()
6
.add(new LatLng(45.0, -12.0))
7
.add(new LatLng(45.0, 5.0))
8
.add(new LatLng(34.5, 5.0))
9
.add(new LatLng(34.5, -12.0))
10
.add(new LatLng(45.0, -12.0));
11
12
lineas.width(8);
13
lineas.color(Color.RED);
14
15
mapa.addPolyline(lineas);
16 }
Ejecutando esta accin en el emulador veramos lo siguiente:

Pues bien, esto mismo podramos haberlo logrado mediante el dibujo de polgonos, cuyo
funcionamiento es muy similar. Para ello crearamos un nuevo objeto PolygonOptions y
aadiremos las coordenadas de sus puntos en el sentido de las agujas del reloj. En este caso
no es necesario cerrar el circuito (es decir, que la primera coordenada y la ltima fueran
iguales) ya que se hace de forma automtica. Otra diferencia es que para polgonos el ancho
y color de la linea los estableceramos mediante los mtodosstrokeWidth() y strokeColor().
Adems, el dibujo final del polgono sobre el mapa lo haramos mediante addPolygon(). En
nuestro caso quedara como sigue:
1
//Dibujo con polgonos
2
3
PolygonOptions rectangulo = new PolygonOptions()
4
.add(new LatLng(45.0, -12.0),
5
new LatLng(45.0, 5.0),

395

6
new LatLng(34.5, 5.0),
7
new LatLng(34.5, -12.0),
8
new LatLng(45.0, -12.0));
9
10 rectangulo.strokeWidth(8);
11 rectangulo.strokeColor(Color.RED);
12
13 mapa.addPolygon(rectangulo);
El resultado al ejecutar la accin en el emulador debera ser exactamente igual que el
anterior.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pagina del curso en GitHub.
Y con esto habramos concluido la serie de tres artculos destinados a describir el
funcionamiento bsico de la nueva API v2 de Google Maps para Android. Espero haber
aclarado las dudas principales a la hora de comenzar a utilizar la nueva API. Tampoco
descarto algn artculo adicional para comentar temas algo ms avanzados sobre esta API,
pero eso ser ms adelante.

III. Notificaciones Push en Android Google Cloud Messaging (GCM /


C2DM)
Notificaciones Push Android: Google Cloud Messaging (GCM). Introduccin
by Sgoliver on 04/07/2012 in Android, Programacin
Los prximos artculos del Curso de Programacin Android los vamos a dedicar a describir
qu es y cmo utilizar el servicio Google Cloud Messaging. En este primer artculo
haremos una introduccin al servicio y comentaremos la forma de registrarnos para poder
utilizarlo (no preocuparos, es gratuito), y en los dos siguientes veremos cmo implementar
las aplicaciones servidor (una vez ms en ASP.NET) y cliente (en Android). Empecemos.
En algunas ocasiones, nuestras aplicaciones mviles necesitan contar con la capacidad de
notificar al usuario determinados eventos que ocurren fuera del dispositivo, normalmente
en una aplicacin web o servicio online alojado en un servidor externo. Como ejemplo de
esto podramos pensar en las notificaciones que nos muestra nuestro mvil cuando se recibe
un nuevo correo electrnico en nuestro servidor de correo habitual.
Para conseguir esto se nos podran ocurrir varias soluciones, por ejemplo mantener abierta
una conexin permanente con el servidor de forma que ste le pudiera comunicar

396

inmediatamente cualquier nuevo evento a nuestra aplicacin. Esta tcnica, aunque es viable
y efectiva, requiere de muchos recursos abiertos constantemente en nuestro dispositivo,
aumentando por tanto el consumo de CPU, memoria y datos de red necesarios para ejecutar
la aplicacin. Otra solucin utilizada habitualmente sera hacer que nuestra aplicacin
mvil revise de forma peridica en el servidor si existe alguna novedad que notificar al
usuario. Esto se denomina polling, y requiere muchos menos recursos que la opcin
anterior, pero tiene un inconveniente que puede ser importante segn el objetivo de nuestra
aplicacin: cualquier evento que se produzca en el servidor no se notificar al usuario hasta
la prxima consulta al servidor que haga la aplicacin cliente, que podra ser mucho tiempo
despus.
Para solventar este problema Google introdujo en Android, a partir de la versin 2.2
(Froyo), la posibilidad de implementar notificaciones de tipo push, lo que significa que es
el servidor el que inicia el proceso de notificacin, pudiendo realizarla en el mismo
momento que se produce el evento, y el cliente se limita a esperar los mensaje sin tener
que estar periodicamente consultando al servidor para ver si existen novedades, y sin tener
que mantener una conexin permanentemente abierta con ste. En la arquitectura de
Google, todo esto se consigue introduciendo un nuevo actor en el proceso, un servidor de
mensajerapush o cloud to device (que se traducira en algo as como mensajes de la nube
al dispositivo), que se situara entre la aplicacin web y la aplicacin mvil. Este servidor
intermedio se encargar de recibir las notificaciones enviadas desde las aplicaciones web y
hacerlas llegar a las aplicaciones mviles instaladas en los dispositivos correspondientes.
Para ello, deber conocer la existencia de ambas aplicaciones, lo que se consigue mediante
un protocolo bien definido de registros y autorizaciones entre los distintos actores que
participan en el proceso. Veremos ms adelante cmo implementar este proceso.
Este servicio de Google recibi en sus comienzos las siglas C2DM (Cloud to Device
Messaging), pero coincidiendo con su salida de fase beta modific su nombre
a GCM (Google Cloud Messaging).
Lo primero que debemos comentar es la forma de darnos de alta en el servicio, que a pesar
de ser gratuito requiere de un proceso previo de registro y la generacin de una ApiKey,
algo similar a lo que ya vimos al hablar de la utilizacin de mapas en Android. Para hacer
esto debemos acceder a la consola de APIs de Google, en siguiente direccin:
https://code.google.com/apis/console
Suponiendo que es la primera vez que accedemos a esta consola, nos aparecer la pantalla
de bienvenida y la opcin de crear un nuevo proyecto.

397

Google API Console Create Project


Una vez creado el proyecto el navegador te redirige a una direccin similar a sta:

Google API Console URL Proyecto


Al nmero que aparece tras la etiqueta #project: lo llamaremos Sender ID y debemos
anotarlo ya que nos har falta ms adelante como identificador nico de la aplicacin web
emisora de nuestros mensajes.
Una vez creado el nuevo proyecto vamos a activar el servicio GCM accediendo al men
Services y pulsando el botn ON/OFF asociado al servicio llamado Google Cloud
Messaging for Android.

Google API Console Services


Se nos puede presentar entonces una ventana donde tendremos que aceptar las condiciones
del servicio y tras sto el servicio quedar activado, aunque an nos faltar un ltimo paso
para poder utilizarlo. Como en el caso de la utilizacin de la api de Google Maps, para
hacer uso del servicio GCM tendremos que generar una ApiKey que nos sirva

398

posteriormente como identificacin de acceso. Para ello accedemos al men Api Access
y pulsaremos sobre el botn Create new Server Key.

Google API Console New Server Key


Nos aparecer un dilogo llamado Configure Server Key for, que aceptaremos sin ms
pulsando el botn Create, sin necesidad de rellenar nada.

Google API Console Configure Server Key


Y ya tendramos creada nuestra API Key, que aparecer en la seccin Simple API Access
con el ttulo Key for server apps (with IP locking).

Google API Console API Key


Con esto ya nos habramos registrado correctamente en el servicio GCM y habramos
generado nuestra API Key para identificarnos, con lo que estaramos en disposicin de
construir nuestras aplicaciones cliente y servidor, algo que veremos en los dos prximos
artculos. En ste nos pararemos un poco ms para hacer una descripcin a grandes rasgos
de la arquitectura que tendr nuestro sistema y de cmo fluir la informacin entre cada
uno de los servicios y aplicaciones implicados.

399

Como hemos indicado antes, para asegurar la correcta comunicacin entre los tres sistemas
se hace necesario un protocolo de registros y autorizaciones que proporcione seguridad y
calidad a todo el proceso. Este proceso se resume en el siguiente grfico (click para
ampliar), que intentar explicar a continuacin.

Proceso General GCM


Comentemos brevemente cada uno de los pasos indicados en el diagrama.
El primer paso, aunque no aparece en el grfico, sera la autenticacin de nuestra aplicacin
web en el servicio GCM. En la anterior versin del servicio GCM (llamada C2DM) esto
deba hacerse mediante la utilizacin de otra API de Google llamada ClientLogin, o a travs
del protocolo OAuth 2.0, ambas dirigidas a obtener un token de autorizacin que deba
enviarse posteriormente en el resto de llamadas al servicio. Sin embargo, con la llegada de
Google Cloud Messaging, esto se ha simplificado mediante la obtencin y uso de una API
Key, que es precisamente lo que hemos comentado unos prrafos ms arriba. Como pasaba
con el token de autorizacin, nuestra nueva API Key deber acompaar a cada una de las
llamadas que hagamos al servicio GCM desde nuestra aplicacin web.
Los siguientes pasado, ya s mostrados en el diagrama, seran los siguientes:
1. El siguiente paso es el equivalente al ya comentado para el servidor pero esta vez desde el
punto de vista de la aplicacin cliente. La aplicacin Android debe registrarse en los
servidores GCM como cliente capaz de recibir mensajes desde dicho servicio. Para esto es
necesario que el dispositivo/emulador cumplan una serie de requisitos:
1. Disponer de Android 2.2 o superior.
2. Tener configurada una cuenta de Google en el dispositivo o emulador. Configurable desde
Ajustes / Cuentas y sincronizacin.
3. Si se trata de un dispositivo real debe estar instalada la Google Play Store. Por el contrario
si estamos ejecutando la aplicacin desde el emulador bastar con usar un target que
incluya las APIs de Google (para la nueva versin de GCM incluida con los Google Play
Services se requiere Android 4.2.2 o superior para poder probar en el emulador).
Si el registro se finaliza correctamente se recibir un cdigo de registro
(Registration ID) que la aplicacin cliente deber conservar. Adems, la aplicacin
Android deber estar preparada para recibir peridicamente refrescos de este cdigo de

400

registro, ya que es posible que el servidor GCM invalide peridicamente este ID, genere
uno nuevo y lo vuelva a notificar a la aplicacin cliente.
Este nuevo paso consiste en enviar, desde la aplicacin cliente a la aplicacin
servidor, el cdigo de registro GCM recibido, el cual har las veces de identificador nico
del cliente en el servidor de forma que ste pueda indicar ms tarde el dispositivo mvil
concreto al que desea enviar un mensaje. La aplicacin servidora tendr que ser capaz por
tanto de almacenar y mantener todos los ID de registro de los distintos dispositivos mviles
que se registren como clientes capaces de recibir mensajes.
El ltimo paso ser obviamente el envo en s de un mensaje desde el servidor hasta
un cliente determinado, algo que se har a travs del servidor GCM (paso 4.1) y desde ste
se dirigir al cliente concreto que debe recibirlo (paso 4.2).
Como se puede comprobar, el procedimiento es relativamente complejo, aunque bastante
intuitivo. En los prximos artculos veremos cmo implementar cada uno de ellos. Una vez
ms, para nuestro ejemplo utilizaremos una aplicacin ASP.NET como aplicacin
servidora, con SQL Server a modo de almacn de datos, y aprovechando que ya hemos
visto como crear servicios web SOAP y llamarlos desde aplicaciones Android, vamos a
utilizar uno de stos para la comunicacin entre cliente y servidor (paso 3).

Notificaciones Push Android:


Implementacin Servidor

Google

Cloud

Messaging

(GCM).

by Sgoliver on 04/07/2012 in Android, Programacin


En el artculo anterior del curso hicimos una introduccin al servicio Google Cloud
Messaging (GCM), vimos cmo registrarnos y obtener la API Key necesaria para enviar
mensajes y describimos a alto nivel la arquitectura que tendr un sistema capaz de gestionar
mensajera de tipo push a travs de este servicio de Google. Este segundo artculo lo vamos
a dedicar a la implementacin de una aplicacin web capaz de enviar mensajes o
notificaciones push a dispositivos Android. En el prximo artculo veremos cmo
desarrollar la aplicacin Android cliente capaz de recibir estos mensajes.
Como ya viene siendo habitual en el curso, el sistema elegido para desarrollar la aplicacin
web ser ASP.NET, utilizando C# como lenguaje, y SQL Server como base de datos.
Como ya comentamos en el artculo anterior, la aplicacin web ser responsable de las
siguientes tareas:
1. Almacenar y mantener el listado de dispositivos cliente que podrn recibir mensajes.
2. Enviar los mensajes a los clientes a travs del servicio GCM de Google.
En cuanto al punto 1, la aplicacin deber ser capaz de recibir los datos de registro de cada
cliente que se d de alta para recibir mensajes y almacenarlos en la base de datos. Esto lo

401

haremos mediante la creacin de un servicio web que exponga un mtodo capaz de recoger
y almacenar los datos de registro de un cliente. La aplicacin Android se conectar
directamente a este servicio web y llamar al mtodo con sus datos identificativos para
registrarse como cliente capaz de recibir notificaciones. Por supuesto que para esto se
podra utilizar cualquier otro mecanismo distinto a servicios web, por ejemplo una simple
peticin HTTP al servidor pasando los datos como parmetros, pero no nos vendr mal para
seguir practicando con servicios web en android, que en este caso ser de tipo SOAP.
Por su lado, el punto 2 lo resolveremos a modo de ejemplo con una pgina web sencilla en
la que podamos indicar el nombre de usuario de cualquiera de los dispositivos registrados
en la base de datos y enviar un mensaje de prueba a dicho cliente.
Vamos a empezar creando la base de datos, aunque no nos detendremos mucho porque ya
vimos el procedimiento por ejemplo en el primer artculo dedicado a servicios web SOAP.
Tan slo decir que crearemos una nueva base de datos llamada DBUSUARIOS, que tendr
dos campos: NombreUsuario yCodigoC2DM, el primero de ellos destinado a almacenar un
nombre de usuario identificativo de cada cliente registrado, y el segundo para almacenar
el RegistrationID de GCM recibido desde dicho cliente a travs del servicio web
(recomiendo consultar el artculo anterior para entender bien todo este protocolo
requerido por GCM).
Una vez creada la base de datos vamos a crear en Visual Studio 2010 un nuevo proyecto C#
de tipo ASP.NET Web Application al que llamaremos GCMServer, y aadiremos a este
proyecto
un
nuevo
componente
de
tipo
Web
Service
llamado
ServicioRegistroGCM.asmx. Todo este procedimiento tambin se puede consultar en el
artculo sobre servicios web SOAP en Android.
Aadiremos un slo mtodo web al servicio, al que llamaremos RegistroCliente() y que

recibir como hemos comentado 2 parmetros: el nombre de usuario y el ID de registro del


cliente en GCM. El mtodo se limitar a realizar el INSERT o UPDATE correspondiente
con estos dos datos en la base de datos que hemos creado de usuarios.
1
[WebMethod]
2
public int RegistroCliente(string usuario, string regGCM)
3
{
4
SqlConnection con =
5
new SqlConnection(
6
@"Data
Source=SGOLIVERPC\SQLEXPRESS;Initial
Catalog=DBUSUARIOS;Integr
7
Security=True");
8
9
con.Open();

402

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

string cod = CodigoCliente(usuario);


int res = 0;
string sql = "";

if (cod == null)
sql = "INSERT INTO Usuarios (NombreUsuario, CodigoC2DM) VALUES (@usuario, @codigo)"
else
sql = "UPDATE Usuarios SET CodigoC2DM = @codigo WHERE NombreUsuario = @usuario";
SqlCommand cmd = new SqlCommand(sql, con);
cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value = usuario;
cmd.Parameters.Add("@codigo", System.Data.SqlDbType.NVarChar).Value = regGCM;
res = cmd.ExecuteNonQuery();
con.Close();
return res;
}

El cdigo es sencillo, pero por qu es necesario considerar el caso del UPDATE? Como ya
advertimos en el artculo anterior, el servidor GCM puede en ocasiones refrescar
(actualizar) el ID de registro de un cliente comunicndoselo de nuevo a ste, por lo que a su
vez la aplicacin cliente tendr que hacer tambin la misma actualizacin contra la
aplicacin web. Para ello, el cliente simplemente volver a llamar al
mtodo RegistroCliente() del servicio web pasando el mismo nombre de usuario pero con
el ID de registro actualizado. Para saber si el cliente est ya registrado o no el mtodo se
apoya en un mtodo auxiliar llamado CodigoCliente() que realiza una bsqueda de un
nombre de usuario en la base de datos para devolver su ID de registro en caso de

encontrarlo. El cdigo de este mtodo es igual de sencillo que el anterior:


1
public string CodigoCliente(string usuario)
2
{
3
SqlConnection con =
4
new SqlConnection(
5
@"Data
Source=SGOLIVERPC\SQLEXPRESS;Initial
Catalog=DBUSUARIOS;Integrate
6
Security=True");
7
8
con.Open();
9
10
string sql = "SELECT CodigoC2DM FROM Usuarios WHERE NombreUsuario = @usuario";
11
12
SqlCommand cmd = new SqlCommand(sql, con);

403

13
14
15
16
17
18
19
20

cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value = usuario;


string cod = (String)cmd.ExecuteScalar();
con.Close();
return cod;
}

Con esto ya tendramos implementado nuestro servicio web para el registro de clientes.
Para el envo de los mensajes utilizaremos directamente la pgina Default.aspx creada
por defecto al generar el proyecto de Visual Studio. Modificaremos esta pgina para aadir
tan slo un cuadro de texto donde podamos introducir el nombre de usuario asociado al
cliente al que queremos enviar un mensaje, un botn Enviar con el realizar el envo, y
una etiqueta donde mostrar el estado del envo. Quedara algo como lo siguiente:

Web Envo GCM


El botn de envo, realizar una bsqueda en la base de datos del nombre de usuario
introducido, recuperar su Registration ID y enviar un mensaje de prueba con la fechahora actual.
1
2
3
4
5
6
7
8
9
10
11
12
13

protected void Button3_Click(object sender, EventArgs e)


{
ServicioRegistroGCM svc = new ServicioRegistroGCM();
string codUsuario = svc.CodigoCliente(TxtUsuario.Text);
bool res = enviarMensajePrueba(codUsuario);
if (res == true)
LblResultadoMensaje.Text = "Envo OK";
else
LblResultadoMensaje.Text = "Envo NOK";
}

404

Como vemos en el cdigo, toda la lgica de envo de mensajes la he encapsulado en el


mtodo auxiliar enviarMensajePrueba() para poder centrarme ahora en ella. En este mtodo
es donde vamos a hacer realmente uso de la API del servicio de Google Cloud Messaging,
y por ello antes de ver la implementacin vamos a hablar primero de las distintas opciones
de esta API.
Todas las llamadas a la API de GCM para enviar mensajes se realizan mediante peticiones
HTTP POST a la siguiente direccin:
https://android.googleapis.com/gcm/send
La cabecera de esta peticin debe contener dos datos esenciales. Por un lado debemos
indicar la API Key que generamos en el primer artculo (atributo Authorization), y por otro
lado el formato del contenido (en este caso, los parmetros de la API) que vamos a incluir
con la peticin (atributo Content-Type). GCM permite formatear los datos como JSON
(para lo que habra que indicar el valor application/json) o como texto plano (para lo que
debemos utilizar el valor application/x-www-form-urlencoded). En nuestro caso de
ejemplo utilizaremos la segunda opcin.
Dado que hemos elegido la opcin de texto plano, los distintos datos del contenido se
formatearn como parmetros HTTP con el formato tradicional, es decir, tendremos que
construir una cadena de la forma param1=valor1&param2=valor2&.
Entre los distintos datos que podemos incluir hay tan solo uno obligatorio,
llamado registration_id, que debe contener el ID de registro del cliente al que se le va a

enviar el mensaje. A parte de ste tambin podemos incluir los siguientes parmetros
opcionales:
delay_while_idle. Hace que el servidor de GCM no enve el mensaje al dispositivo mientras
ste no se encuentre activo.
time_to_live. Indica el tiempo mximo que el mensaje puede permanecer en el servidor de
GCM sin entregar mientras el dispositivo est offline. Por defecto 4 semanas. Si se
especifica algn valor tambin habr que incluir el parmetro siguiente, collapse_key.
collapse_key. ste lo explicar con un ejemplo. Imaginad que activamos el
parmetrodelay_while_idle y que el dispositivo que debe recibir el mensaje permanece
inactivo varias horas. Si durante esas horas se generaran varias notificaciones hacia el
dispositivo, estos mensajes se iran acumulando en el servidor de GCM y cuando el
dispositivo se activara le llegaran todos de golpe. Esto puede tener sentido si cada mensaje
contiene informacin distinta y relevante, pero y si los mensajes simplemente fueran por
ejemplo para decirle al dispositivo Tienes correo nuevo? Sera absurdo entregar en el
varias notificaciones de este tipo en el mismo instante. Pues bien, para esto se utiliza el
parmetro collapse_key. A este parmetro podemos asignarle como valor cualquier cadena
de caracteres, de forma que si se acumulan en el servidor de GCM varios mensajes para el

405

mismo dispositivo y con la misma collapse_key, al dispositivo slo se le entregar el ltimo


de ellos cuando ste se active, descartando todos los dems.
data.<nombre_dato>. Se pueden incluir tantos parmetros de este tipo como queramos, para
incluir cualquier otra informacin que queramos en el mensaje. Por ejemplo podramos
pasar los datos de un nuevo correo recibido con dos parmetros como los siguientes:
data.emisor=aaa@gmail.com, y data.asunto=pruebagcm. Tan solo recordad preceder el
nombre de los datos con el prefijo data..
Una vez formateada convenientemente la cabecera y contenido de la peticin HTTP, y
realizada sta a la direccin indicada anteriormente, podemos obtener diferentes respuestas
dependiendo del resultado de la peticin. Diferenciaremos los distintos resultados por el
cdigo de estado HTTP recibido en la respuesta:

200. El mensaje se ha procesado correctamente, en cuyo caso se devuelve en los datos un


parmetro id= con el cdigo del mensaje generado.
401. Ha fallado la autenticacin de nuestra aplicacin web contra los servidores de GCM.
Normalmente significar algn problema con la API Key utilizada.
500. Se ha producido un error al procesarse el mensaje. En este caso la respuesta incluir en
su contenido un parmetro Error= que indicar el cdigo de error concreto devuelto por
GCM.
501. El servidor de GCM no est disponible temporalmente.
Y eso es todo, largo de contar pero sencillo en el fondo. Veamos cmo podemos
implementar esto en C#, y para ello vamos a ver el cdigo del mtodo que dejamos antes
pendiente, enviarMensajePrueba(), y justo despus lo comentamos.
1
private static bool enviarMensajePrueba(String registration_id)
2
{
3
String GCM_URL = @"https://android.googleapis.com/gcm/send";
4
5
string collapseKey = DateTime.Now.ToString();
6
7
Dictionary data = new Dictionary();
8
data.Add("data.msg",
9
HttpUtility.UrlEncode("Prueba. Timestamp: " + DateTime.Now.ToString()));
10
11
bool flag = false;
12
StringBuilder sb = new StringBuilder();
13
14
sb.AppendFormat("registration_id={0}&collapse_key={1}",
15
registration_id, collapseKey);
16
17
foreach (string item in data.Keys)
18
{
19
if (item.Contains("data."))
20
sb.AppendFormat("&{0}={1}", item, data[item]);
21
}

406

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

string msg = sb.ToString();


HttpWebRequest req = (HttpWebRequest)WebRequest.Create(GCM_URL);
req.Method = "POST";
req.ContentLength = msg.Length;
req.ContentType = "application/x-www-form-urlencoded";
string apiKey = "AIzaSyCJ7QSQAznAmhDzNTLSUE6uX9aUfr9-9RI";
req.Headers.Add("Authorization:key=" + apiKey);
using (StreamWriter oWriter = new StreamWriter(req.GetRequestStream()))
{
oWriter.Write(msg);
}
using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
{
using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
string respData = sr.ReadToEnd();
if (resp.StatusCode == HttpStatusCode.OK) // OK = 200
{
if (respData.StartsWith("id="))
flag = true;
}
else if (resp.StatusCode == HttpStatusCode.InternalServerError) // 500
Console.WriteLine("Error interno del servidor, prueba ms tarde.");
else if (resp.StatusCode == HttpStatusCode.ServiceUnavailable) // 503
Console.WriteLine("Servidor no disponible temporalmente, prueba ms tarde.");
else if (resp.StatusCode == HttpStatusCode.Unauthorized)
// 401
Console.WriteLine("La API Key utilizada no es vlida.");
else
Console.WriteLine("Error: " + resp.StatusCode);
}
}
return flag;
}

Como vemos el mtodo recibe directamente como parmetro el Registration ID del cliente
al que se va a enviar el mensaje. En primer lugar configuro todos los parmetros que pasar
en la llamada a la API, que en este caso de ejemplo tan slo sern, adems
del registration_id ya comentado, el colapse_key, y una dato adicional que llamar
data.msg (recordemos el prefijo data. obligatorio para este tipo de datos adicionales)
con un mensaje de prueba que contenga la fecha/hora actual. Toda la cadena con estos

407

parmetros la construyo utilizando un objeto StringBuilder. Lo nico reseable hasta ahora


sera la forma de aadir el parmetro adicional data.msg, que lo hago mediante la creacin
de un objetoDictionary y su mtodo add() para aadir el dato, para poco despus generar la
cadena final recorriendo este diccionario en un bucle foreach. En este caso no sera
necesaria toda esta parafernalia dado que slo vamos a aadir un dato adicional, pero lo he
dejado as para que tengis un ejemplo de patrn mediante el cual podeis aadir ms de
un dato adicional de una forma sencilla y organizada.
Una vez creada la cadena de parmetros y datos que incluiremos como contenido de la
peticin creamos dicha peticin como un objeto HttpWebRequest indicando la URL del
servicio. Indicamos que la peticin ser de tipo POST asignando la propiedad Method, y
configuramos la cabecera con los dos datos que ya hemos comentado antes en el artculo
(Authorization y Content-Type). El primero de ellos al ser personalizado debemos
aadirlo utilizando el mtodo Add() de la coleccin Headers de la peticin. En cambio para
el segundo existe una propiedad del objeto HttpWebRequest con la que podemos
establecerlo directamente, llamada ContentType. Hecho esto, tan slo nos queda aadir el
contenido a la peticin, lo que conseguimos obteniendo el stream de escritura de la peticin
medianteGetRequestStream() y escribiendo en l nuestra cadena de parmetros mediante el
mtodo Write().
Seguidamente vamos a ejecutar la peticin y a obtener la respuesta como
objeto HttpWebResponsemediante una llamada a GetResponse(). Por ltimo, obtenemos el
cdigo de estado HTTP de la respuesta mediante la consulta a su propiedad StatusCode, y
los
datos
asociados
obteniendo
el stream de
lectura
de
la
respuesta
mediante GetResponseStream() y el mtodo ReadToEnd() para leer todo el contenido.
Evaluando estos dos datos determinamos fcilmente el resultado del envo segn la
informacin ya comentada antes en el artculo.
Y con esto habramos terminado la implementacin del servidor. Haremos las pruebas
pertinentes y mostrar el resultado cuando implementemos la aplicacin Android cliente en
el prximo artculo.
Puedes consultar y/o descargar el cdigo completo de los ejemplos desarrollados en este
artculo accediendo a la pgina del curso en GitHub.
Actualizacin: Existe una librera .NET/Mono llamada PushSharp capaz de facilitarnos la
vida a la hora de enviar notificaciones push a dispositivos Android (y tambin iOS,
Windows Phone y Blackberry).
Notificaciones Push Android: Google Cloud Messaging (GCM). Implementacin Cliente

408

by Sgoliver on 04/07/2012 in Android, Programacin


Este artculo ha quedado obsoleto con la aparicin de una nueva versin de la API de
Google Cloud Messaging. Tienes disponible un nuevo artculo del curso donde aprender a
utilizar esta nueva versin.
En los dos anteriores (I y II) artculos del curso hemos hablado sobre el servicio Google
Cloud Messaging y hemos visto como implementar una aplicacin web que haga uso de
dicho servicio para enviar mensajes a dispositivos Android. Para cerrar el crculo, en este
nuevo artculo nos centraremos en la aplicacin Android cliente.
Esta aplicacin cliente, como ya hemos comentado en alguna ocasin ser responsable de:
1. Registrarse contra los servidores de GCM como cliente capaz de recibir mensajes.
2. Almacenar el Registration ID recibido como resultado del registro anterior.
3. Comunicar a la aplicacin web el Registration ID de forma que sta pueda enviarle
mensajes.
4. Recibir y procesar los mensajes desde el servidor de GCM.
Para las tareas 1 y 4 utilizaremos una librera especfica de GCM que nos proporciona
Google para facilitarnos la vida como desarrolladores (es posible hacerlo sin el uso de
libreras externas, aunque requiere ms trabajo). El punto 2 lo resolveremos fcilmente
mediante el uso de SharedPreferences. Y por ltimo el punto 3 lo implementaremos
mediante la conexin al servicio web SOAP que creamos en el artculo anterior,
sirvindonos para ello de la librera ksoap2, tal como ya describimos en los artculos
sobre servicios web SOAP en Android.
Durante el artculo construiremos una aplicacin de ejemplo muy sencilla con el siguiente
aspecto:

Aplicacin Ejemplo Android GCM

409

En esta aplicacin, el usuario podr introducir un nombre de usuario identificativo y pulsar


el botn Aceptar para que quede guardado en las preferencias de la aplicacin. Tras esto
podr registrarse como cliente capaz de recibir mensajes desde GCM pulsando el botn
Registrar GCM. En caso de realizarse de forma correcta este registro la aplicacin
enviar automticamente el Registration ID recibido y el nombre de usuario almacenado a
la aplicacin web a travs del servicio web. Igualmente el usuario podr des-registrarse en
el servicio GCM para no recibir ms mensajes pulsando el botn Des-registrar GCM.
Obviamente todo este proceso de registro y des-registro debera hacerse de forma
transparente para el usuario de una aplicacin real, en esta ocasin he colocado botones
para ello slo por motivos didcticos.
Antes de nada vamos a preparar nuestro proyecto de Eclipse y vamos a configurar
convenientemente el AndroidManifest para poder hacer uso del servicio GCM y su librera
auxiliar.
Para ello vamos a crear un nuevo proyecto Android sobre un target de Android 2.2 o
superior que incluya las libreras de Google, y vamos a incluir en su carpeta /libs las
libreras de ksoap2 (esto ya vimos como hacerlo en el artculo sobre servicios web) y la
librera cliente de GCM llamada gcm.jar. Cmo podemos obtener esta librera? Para
conseguirla debemos instalar desde el Android SDK Manager el paquete extra llamado
Google Cloud Messaging for Android Library.

Librera Google Cloud Messaging for Android


Una vez instalado podemos ir a la ruta RAIZ_SDK_ANDROID/extras/google/gcm/gcmclient/dist donde deber aparecer la librera gcm.jar que debemos aadir a nuestro
proyecto.

410

Lo siguiente ser configurar nuestro AndroidManifest. Lo primero que aadiremos ser una
clusula <uses-sdk> para indicar como versin mnima del SDK soportada la 8 (Android
2.2). Con esto nos aseguraremos de que la aplicacin no se instala en dispositivos con
versin de Android anterior, no soportadas por el servicio GCM.
1
2
3

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="16" />

A continuacin aadiremos los permisos necesarios para ejecutar la aplicacin y utilizar


GCM:
1
2
3
4
5

<permission
android:name="net.sgoliver.android.permission.C2D_MES
android:protectionLevel="signature" />
<uses-permission android:name="net.sgoliver.android.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

Los dos primeros aseguran que slo esta aplicacin podr recibir los mensajes, el segundo
permite la recepcin en s de mensajes desde GCM (sustituir mi paquete java
net.sgoliver.android por el vuestro propio en estas lineas), el tercero es el permiso para
poder conectarnos a internet y el ltimo es necesario para tareas realizadas durante la
recepcin de mensajes que veremos ms adelante.
Por ltimo, como componentes de la aplicacin, adems de la actividad principal ya
aadida
por
defecto,
deberemos
declarar
un broadcast
receiver llamado GCMBroadcastReceiver, que no tendremos que crearlo porque ya viene
implementado dentro de la librera gcm.jar (solo tenis que modificar el
elemento<category> para
indicar
vuestro
paquete
java),
y
un
servicio
llamado GCMIntentService (Atencin, es obligatorio este nombre exacto para el servicio si
no queremos tener que implementar nosotros mismos el broadcast receiver anterior). Ya
veremos ms adelante para qu son estos dos componentes.
1
<application
2
android:icon="@drawable/ic_launcher"
3
android:label="@string/app_name"
4
android:theme="@style/AppTheme" >
5
6
...
7
8
<receiver android:name="com.google.android.gcm.GCMBroadcastReceiver"
9
android:permission="com.google.android.c2dm.permission.SEND" >
10
<intent-filter>
11
<action android:name="com.google.android.c2dm.intent.RECEIVE" />

411

12
13
14
15
16
17
18
19

<action android:name="com.google.android.c2dm.intent.REGISTRATION"
/>
<category android:name="net.sgoliver.android" />
</intent-filter>
</receiver>
<service android:name=".GCMIntentService" />
</application>

Una vez definido nuestro AndroidManifest con todos los elementos necesarios vamos a
empezar a implementar la funcionalidad de nuestra aplicacin de ejemplo.
Empezamos por la ms sencilla, el botn de guardar el nombre de usuario. Como
comentamos anteriormente, vamos a utilizar preferencias compartidas para esta tarea.
Como ste es un tema ya vistoen el curso no me detendr en el cdigo ya que es bastante
directo.
1
btnGuardarUsuario.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View v) {
4
SharedPreferences prefs =
5
getSharedPreferences("MisPreferencias", Context.MODE_PRIVATE);
6
7
SharedPreferences.Editor editor = prefs.edit();
8
editor.putString("usuario", txtUsuario.getText().toString());
9
editor.commit();
10
}
11 });
Como podis comprobar nos limitamos a almacenar una nueva propiedad llamada
usuario con el texto introducido en el cuadro de texto de la interfaz.
El siguiente botn es el de registro del cliente en GCM, y aqu s nos detendremos un poco
para comentar primero cmo funciona internamente este procedimiento.
La aplicacin android debe solicitar el registro en el servicio GCM mediante una peticin
HTTP POST similar a la que ya vimos en el artculo anterior para la aplicacin web. Por
suerte, este procedimiento se ve simplificado enormemente con el uso de la librera gcm.jar,
ya que el montaje y ejecucin de esta peticin queda encapsulado como una simple llamada
a un mtodo esttico de la clase GCMRegistrar, definida en la librera. Por su parte, tanto la
respuesta a esta peticin de registro como la posterior recepcin de mensajes se reciben en
la aplicacin Android en forma de intents. Y aqu es donde entran en juego los dos
componentes que hemos definido anteriormente en nuestro AndroidManifest. El

412

receiverGCMBroadcastReceiver ser el encargado de esperar y capturar estos intents


cuando se reciban y posteriormente lanzar el servicio GCMIntentService donde se
procesarn en un hilo independiente estas respuestas segn el intent recibido. Como ya
dijimos, el broadcast receiver no ser necesario crearlo ya que utilizaremos el ya
proporcionado por la librera. En cambio la implementacin del IntentService s ser de
nuestra responsabilidad. Aunque una vez ms la librera de GCM nos facilitar esta tarea
como ya veremos ms adelante.
Veamos primero cmo realizar el registro de nuestra aplicacin en GCM al pulsar el botn
de registro. Como hemos dicho esto se limitar a llamar a un mtodo esttico,
llamado register(), de la claseGCMRegistrar. La nica precaucin que tomaremos es
verificar previamente si estamos ya registrados, algo que podremos hacer fcilmente
llamando al mtodo getRegistrationId() de la misma clase. El mtodo register() recibir dos
parmetros, el primero de ellos una referencia al contexto de la aplicacin (normalmente la
actividad desde la que se llama) y en segundo lugar el Sender ID que obtuvimos cuando
creamos el nuevo proyecto en la Google API Console.
1
btnRegistrar.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View v) {
4
5
//Si no estamos registrados --> Nos registramos en GCM
6
final String regId = GCMRegistrar.getRegistrationId(GcmActivity.this);
7
if (regId.equals("")) {
8
GCMRegistrar.register(GcmActivity.this, "224338875065"); //Sender ID
9
} else {
10
Log.v("GCMTest", "Ya registrado");
11
}
12
}
13 });
As de sencillo y rpido. Pero esto es slo la peticin de registro, ahora nos tocar esperar la
respuesta, algo que veremos en breve.
Por su parte, el botn de des-registro se implementar de forma anloga, con la nica
diferencia que esta vez utilizaremos el mtodo unregister() de la clase GCMRegistrar.
1
btnDesRegistrar.setOnClickListener(new OnClickListener() {
2
@Override
3
public void onClick(View v) {
4
5
//Si estamos registrados --> Nos des-registramos en GCM
6
final String regId = GCMRegistrar.getRegistrationId(GcmActivity.this);
7
if (!regId.equals("")) {
8
GCMRegistrar.unregister(GcmActivity.this);

413

9
10
11
12
13

} else {
Log.v("GCMTest", "Ya des-registrado");
}
}
});

Ahora toca procesar las respuestas. Como hemos dicho, para hacer esto tendremos que
implementar el servicio GCMIntentService. Pero no lo haremos desde cero, ya que la
librera de GCM nos proporciona una clase base GCMBaseIntentService de la cual
podemos extender la nuestra, con la ventaja de que tan slo tendremos que sobrescribir

unos pocos mtodos a modo de callbacks, uno por cada posible respuesta o mensaje que
podemos recibir desde el servicio GCM. Estos mtodos son:
onRegistered(context, regId). Se llamar al recibirse una respuesta correcta a la peticin de
registro e incluye como parmetro el Registration ID asignado a nuestro cliente.
onUnregistered(context, regId). Anlogo al anterior pero aplicado a una peticin de desregistro.
onError(context, errorId). Se llamar al recibirse una respuesta de error a una peticin de
registro o des-registro. El cdigo de error concreto se recibe como parmetro.
onMessage(context, intent). Se llamar cada vez que se reciba un nuevo mensaje desde el
servidor de GCM. El contenido del mensaje se recibe en forma de intent, el cual veremos
ms adelante cmo procesar.
Empecemos por el mtodo onRegistered(). Al recibir una respuesta satisfactoria a la
peticin de registro recuperaremos el nombre de usuario almacenado y junto con
el Registration ID recibido nos conectaremos al servicio web que creamos en el artculo
pasado pasndole dichos datos. Esto completar el registro tanto con el servidor de GCM
como con nuestra aplicacin web.
protected void onRegistered(Context context, String regId) {
1
Log.d("GCMTest", "REGISTRATION: Registrado OK.");
2
3
SharedPreferences prefs =
4
context.getSharedPreferences("MisPreferencias",
5
Context.MODE_PRIVATE);
6
7
String usuario = prefs.getString("usuario", "por_defecto");
8
9
registroServidor(usuario, regId);
10
}
El mtodo registroServidor() ser el encargado de realizar la conexin al servicio web y de
la llamada al mtodo web de registro. No me detendr en comentar el cdigo de este
mtodo porque es anlogo a los ejemplos ya vistos en el artculo que dedicamos a servicios
web SOAP en Android. Veamos tan slo el cdigo:
1
private void registroServidor(String usuario, String regId)
2
{

414

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

final String NAMESPACE = "http://sgoliver.net/";


final String URL="http://10.0.2.2:1634/ServicioRegistroGCM.asmx";
final String METHOD_NAME = "RegistroCliente";
final String SOAP_ACTION = "http://sgoliver.net/RegistroCliente";
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
request.addProperty("usuario", usuario);
request.addProperty("regGCM", regId);
SoapSerializationEnvelope envelope =
new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE transporte = new HttpTransportSE(URL);
try
{
transporte.call(SOAP_ACTION, envelope);
SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();
String res = resultado_xml.toString();
if(res.equals("1"))
Log.d("GCMTest", "Registro WS: OK.");
}
catch (Exception e)
{
Log.d("GCMTest", "Registro WS: NOK. " + e.getCause() + " || " + e.getMessage());
}
}

Por su parte, en los mtodos de des-registro y de error me limitar a escribir un mensaje en


el log de la aplicacin para no complicar demasiado el ejemplo, pero en una aplicacin real
deberamos verificar estas respuestas.
1
2
3
4
5
6
7
8

@Override
protected void onUnregistered(Context context, String regId) {
Log.d("GCMTest", "REGISTRATION: Desregistrado OK.");
}
@Override
protected void onError(Context context, String errorId) {
Log.d("GCMTest", "REGISTRATION: Error -> " + errorId);

415

Por ltimo, en el mtodo onMessage() procesaremos el intent con los datos recibidos en el
mensaje y mostraremos una notificacin en la barra de estado de Android.
El intent recibido contendr un elemento en su coleccin de extras por cada dato adicional
que se haya incluido en la peticin que hizo la aplicacin servidor al enviar el mensaje.
Recordis? Aquellos datos adicionales que haba que preceder con el prefijo data.. Si
hacis memoria, en nuestros mensajes de ejemplo tan slo incluamos un dato llamado
data.msg con un mensaje de prueba. Pues bien, estos datos se recuperarn de la coleccin
de extras del intent llamado al mtodo getString() con el nombre del dato, pero esta vez
eliminando el prefijo data.. Veamos cmo quedara todo esto:
1 @Override
2 protected void onMessage(Context context, Intent intent) {
3
String msg = intent.getExtras().getString("msg");
4
Log.d("GCMTest", "Mensaje: " + msg);
5
mostrarNotificacion(context, msg);
6 }
Simple, no?. Al final del mtodo llamamos a un mtodo auxiliar mostrarNotificacion() que
ser el encargado de mostrar la notificacin en la barra de estado de Android. Esto tambin
vimos como hacerlo en detalle en un artculo anterior por lo que tampoco comentaremos el
cdigo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

private void mostrarNotificacion(Context context, String msg)


{
//Obtenemos una referencia al servicio de notificaciones
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager notManager =
(NotificationManager) context.getSystemService(ns);
//Configuramos la notificacin
int icono = android.R.drawable.stat_sys_warning;
CharSequence textoEstado = "Alerta!";
long hora = System.currentTimeMillis();
Notification notif =
new Notification(icono, textoEstado, hora);
//Configuramos el Intent
Context contexto = context.getApplicationContext();
CharSequence titulo = "Nuevo Mensaje";
CharSequence descripcion = msg;
Intent notIntent = new Intent(contexto,
GCMIntentService.class);

416

23
24
25
26
27
28
29
30
31
32
33
34
35

PendingIntent contIntent = PendingIntent.getActivity(


contexto, 0, notIntent, 0);
notif.setLatestEventInfo(
contexto, titulo, descripcion, contIntent);
//AutoCancel: cuando se pulsa la notificain sta desaparece
notif.flags |= Notification.FLAG_AUTO_CANCEL;
//Enviar notificacin
notManager.notify(1, notif);
}

Y slo una indicacin ms, adems de sobrescribir estos mtodos en nuestra clase
GCMIntentService, tambin tendremos que aadir un nuevo constructor sin parmetros que
llame directamente al constructor de la clase base pasndole de nuevo el Sender ID que
obtuvimos al crear el nuevo proyecto en la Google API Console. Quedara algo as:
1
2
3

public GCMIntentService() {
super("224338875065");
}

Si no ha quedado claro del todo cmo quedara la clase GCMIntentService completa puede
descargarse y consultarse el cdigo fuente completo al final del artculo.
Y con esto habramos terminado de implementar nuestra aplicacin Android capaz de
recibir mensajes push desde nuestra aplicacin web de ejemplo. Si ejecutamos ambas y
todo ha ido bien, introducimos un nombre de usuario en la aplicacin Android, pulsamos
Aceptar para guardarlo, nos registramos como clientes en GCM pulsando el botn
Registrar GCM, y seguidamente desde la aplicacin web introducimos el mismo nombre
de usuario del cliente, pulsamos el botn Enviar GCM y en breves segundos nos debera
aparecer la notificacin en la barra de estado de nuestro emulador como se observa en las
imgenes siguientes:

417

Aplicacin Ejemplo Android GCM


Y si desplegamos la barra de estado veremos el mensaje de prueba recibido:

Notificacin GCM Barra de Estado


Como habis podido comprobar en estos tres ltimos artculos, la utilizacin de mensajes
push requiere de un proceso algo laborioso pero para nada complicado. Os animo a que lo
intentis en vuestras aplicaciones ya que puede representar una caracterstica interesante y
til.
Notificaciones Push Android: Google Cloud Messaging (GCM). Implementacin Cliente
(Nueva Versin)
by Sgoliver on 18/08/2013 in Android, Programacin
En los apartados anteriores del curso hemos hablado sobre el servicio Google Cloud
Messaging y hemos visto cmo implementar una aplicacin web que haga uso de dicho
servicio para enviar mensajes a dispositivos Android. Para cerrar el crculo, en este nuevo
apartado nos centraremos en la aplicacin Android cliente.
Esta aplicacin cliente, como ya hemos comentado en alguna ocasin ser responsable de:

418

1. Registrarse contra los servidores de GCM como cliente capaz de recibir mensajes.
2. Almacenar el Registration ID recibido como resultado del registro anterior.
3. Comunicar a la aplicacin web el Registration ID de forma que sta pueda enviarle
mensajes.
4. Recibir y procesar los mensajes desde el servidor de GCM.
En la versin anterior de GCM, las tareas 1 y 4 se realizaban normalmente utilizando como
ayuda una librera adicional (gcm.jar) proporcionada por Google. Sin embargo, en la nueva
versin de GCM incluida como parte de los Google Play Services cambian un poco la
filosofa de trabajo y esta librera ya no es necesaria.
Por su parte, el punto 2 lo resolveremos fcilmente mediante el uso de SharedPreferences.
Y por ltimo el punto 3 lo implementaremos mediante la conexin al servicio web SOAP
que creamos en el apartado anterior, sirvindonos para ello de la librera ksoap2, tal como
ya describimos en el captulo sobre servicios web SOAP en Android.
Durante el captulo construiremos una aplicacin de ejemplo muy sencilla, en la que el
usuario podr introducir un nombre de usuario identificativo y pulsar un botn para que
quede guardado en las preferencias de la aplicacin. Tras esto podr registrarse como
cliente capaz de recibir mensajes desde GCM pulsando un botn llamado Registrar. En
caso de realizarse de forma correcta este registro la aplicacin enviar automticamente el
Registration ID recibido y el nombre de usuario almacenado a la aplicacin servidor a
travs del servicio web. Obviamente todo este proceso de registro debera hacerse de forma
transparente para el usuario de una aplicacin real, en esta ocasin he colocado un botn
para ello slo por motivos didcticos y para poder hacer una prueba ms controlada.

Como en el caso de cualquier otro servicio incluido en los Google Play Services el primer
paso para crear nuestra aplicacin Android ser importar el proyecto de librera de los
servicios, crear nuestro propio proyecto y finalmente hacer referencia a la librera desde
nuestro proyecto. Todo este proceso est explicado en el artculo de introduccin a los
Google Play Services.
El siguiente paso ser configurar nuestro AndroidManifest. Lo primero que revisaremos
ser la clusula<usessdk>, donde como versin mnima del SDK debemos indicar la 8

419

(Android 2.2) o superior. Con esto nos aseguraremos de que la aplicacin no se instala en
dispositivos con versin de Android anterior, no soportadas por los Google Play Services.
A continuacin aadiremos los permisos necesarios para ejecutar la aplicacin y utilizar
GCM:

1
2
3
4
5
6
7
8

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


<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
android:name="net.sgoliver.android.newgcm.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission
android:name="net.sgoliver.android.newgcm.permission.C2D_MESSAGE" />

El primero (INTERNET) nos dar acceso a internet en la aplicacin, el segundo


(GET_ACCOUNTS) es necesario porque GCM requiere una cuenta de Google configurada
en el dispositivo, el tercero (WAKE_LOCK) ser necesario para utilizar un determinado
tipo de broadcast receiver que comentaremos ms adelante, el cuarto (RECEIVE) es el que
permitir que la aplicacin se registre y reciba mensajes de GCM. Los dos ltimos aseguran
que slo nosotros podremos recibir los mensajes de nuestra aplicacin (sustituir mi paquete
java net.sgoliver.android.newgcm por el vuestro propio en estas dos lineas).
Por ltimo, como componentes de la aplicacin, adems de la actividad principal ya
aadida
por
defecto, deberemos
declarar
un
broadcast
receiver,
que
llamaremos GCMBroadcastReceiver (tenis que modificar el elemento <category> con
vuestro paquete java), y un servicio que llamaremosGCMIntentService. Ms adelante
veremos cul ser el cometido de cada uno de estos componentes.
1
<application
2
android:allowBackup="true"
3
android:icon="@drawable/ic_launcher"
4
android:label="@string/app_name"
5
android:theme="@style/AppTheme" >
6
7
...
8
9
<receiver
10
android:name=".GCMBroadcastReceiver"
11
android:permission="com.google.android.c2dm.permission.SEND" >
12
<intent-filter>
13
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
14
<category android:name="net.sgoliver.android.newgcm" />
15
</intent-filter>

420

16
17
18
19
20

</receiver>
<service android:name=".GCMIntentService" />
</application>

Una vez definido nuestro AndroidManifest con todos los elementos necesarios vamos a
empezar a implementar la funcionalidad de nuestra aplicacin de ejemplo. Empezaremos
por el proceso de registro que se desencadena al pulsar el botn Registrar de la aplicacin
tras introducir un nombre de usuario.
Nuestro botn de registro tendr que realizar las siguientes acciones:
1. Verificar que el dispositivo tiene instalado Google Play Services.
2. Revisar si ya tenemos almacenado el cdigo de registro de GCM (registration id) de una
ejecucin anterior.
3. Si no disponemos ya del cdigo de registro realizamos un nuevo registro de la aplicacin y
guardamos los datos.
El cdigo del botn con estos tres pasos, que iremos comentando por partes, sera el
siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

btnRegistrar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v)
{
context = getApplicationContext();
//Chequemos si est instalado Google Play Services
//if(checkPlayServices())
//{
gcm = GoogleCloudMessaging.getInstance(MainActivity.this);
//Obtenemos el Registration ID guardado
regid = getRegistrationId(context);
//Si no disponemos de Registration ID comenzamos el registro
if (regid.equals("")) {
TareaRegistroGCM tarea = new TareaRegistroGCM();
tarea.execute(txtUsuario.getText().toString());
}
//}
//else
//{
// Log.i(TAG, "No se ha encontrado Google Play Services.");

421

25
26
27

//}
}
});

El chequeo de si estn instalados los Google Play Services en el dispositivo no se comporta


demasiado bien al ejecutar la aplicacin sobre el emulador (dependiendo de la versin de
Android utilizada) por lo que he decidido mantenerlo comentado para este ejemplo, pero en
una aplicacin real s debera realizarse. Adems, tambin debera incluirse en el
evento onResume() de la actividad:
1 @Override
2 protected void onResume()
3 {
4
super.onResume();
5
6 // checkPlayServices();
7 }
En cuanto a la lgica para hacer el chequeo podremos ayudarnos de la
clase GooglePlayServicesUtil,
que
dispone
del
mtodo isGooglePlayServicesAvailable() para hacer la verificacin. En caso de no estar
disponibles (si el mtodo devuelve un valor distinto a SUCCESS) an podemos mostrar un
dilogo de advertencia al usuario dando la posibilidad de instalarlos. Esto lo haremos
llamando al mtodo getErrorDialog() de la misma clase GooglePlayServicesUtil. Quedara
algo as:
1
private boolean checkPlayServices() {
2
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
3
if (resultCode != ConnectionResult.SUCCESS)
4
{
5
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode))
6
{
7
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
8
PLAY_SERVICES_RESOLUTION_REQUEST).show();
9
}
10
else
11
{
12
Log.i(TAG, "Dispositivo no soportado.");
13
finish();
14
}
15
return false;
16
}
17
return true;
18 }
Si Google Play Services est instalado en el dispositivo el siguiente paso ser comprobar si
ya tenemos guardado los datos de registro de una ejecucin anterior, en cuyo caso no habr

422

que volver a hacer el registro (salvo en contadas ocasiones que comentaremos ahora). Esta
comprobacin la heremos dentro de un mtodo llamado getRegistrationId(), que entre otras
cosas har uso de preferencias compartidas (Shared Preferences) para recuperar los datos
guardados. Nuestra aplicacin guardar 4 preferencias, que definiremos en nuestra
actividad como constantes:
private static final String PROPERTY_REG_ID = "registration_id";
1
private static final String PROPERTY_APP_VERSION = "appVersion";
2
private
static
final
String
PROPERTY_EXPIRATION_TIME
=
3
"onServerExpirationTimeMs";
4
private static final String PROPERTY_USER = "user";
La primera de ellas es el cdigo de registro de GCM, la segunda guardar la versin de la
aplicacin para la que se ha obtenido dicho cdigo, la tercera indicar la fecha de caducidad
del cdigo de registro guardado, y por ltimo guardaremos el nombre de usuario.
En el mtodo getRegistrationId() lo primero que haremos ser recuperar la
preferenciaPROPERTY_REG_ID. Si sta no est informada saldremos inmediatamente del
mtodo para proceder a un nuevo registro.
Si por el contrario ya tenamos un registration_id guardado podramos seguir utilizndolo

sin tener que registrarnos de nuevo (lo devolveremos como resultado), pero habr tres
situaciones en las que queremos volver a realizar el registro para asegurarnos de que
nuestra aplicacin pueda seguir recibiendo mensajes sin ningn problema:
Si el nombre de usuario ha cambiado.
Si la versin de la aplicacin ha cambiado.
Si se ha sobrepasado la fecha de caducidad del cdigo de registro.
Para verificar esto nuestro mtodo recuperar cada una de las preferencias compartidas,
realizar las verificaciones indicadas y en caso de cumplirse alguna de ellas saldr del
mtodo sin devolver el antiguoregistration_id para que se vuelva a realizar el registro.
1
private String getRegistrationId(Context context)
2
{
3
SharedPreferences prefs = getSharedPreferences(
4
MainActivity.class.getSimpleName(),
5
Context.MODE_PRIVATE);
6
7
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
8
9
if (registrationId.length() == 0)
10
{
11
Log.d(TAG, "Registro GCM no encontrado.");
12
return "";
13
}
14

423

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

String registeredUser =
prefs.getString(PROPERTY_USER, "user");
int registeredVersion =
prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
long expirationTime =
prefs.getLong(PROPERTY_EXPIRATION_TIME, -1);
SimpleDateFormat
sdf =
new
SimpleDateFormat("dd/MM/yyyy
Locale.getDefault());
String expirationDate = sdf.format(new Date(expirationTime));
Log.d(TAG, "Registro GCM encontrado (usuario=" + registeredUser +
", version=" + registeredVersion +
", expira=" + expirationDate + ")");
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion)
{
Log.d(TAG, "Nueva versin de la aplicacin.");
return "";
}
else if (System.currentTimeMillis() > expirationTime)
{
Log.d(TAG, "Registro GCM expirado.");
return "";
}
else if (!txtUsuario.getText().toString().equals(registeredUser))
{
Log.d(TAG, "Nuevo nombre de usuario.");
return "";
}
return registrationId;
}
private static int getAppVersion(Context context)
{
try
{
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
}

HH:mm",

424

62
63
64
65

catch (NameNotFoundException e)
{
throw new RuntimeException("Error al obtener versin: " + e);
}
}

Como podis observar, para consultar la versin actual de la aplicacin utilizamos un


mtodo auxiliargetAppVersion() que obtiene la versin mediante el Package Manager y su
mtodogetPackageInfo().
Bien, pues llegados aqu si el mtodo anterior nos ha devuelto un cdigo de registro (es
decir, que ya tenamos uno guardado) no tendramos que hacer nada ms, significara que
ya estamos registrados en GCM y tan slo tenemos que esperar a recibir mensajes. En caso
contrario, tendremos que realizar un nuevo registro, de lo que nos ocuparemos mediante la
tarea asncrona TareaRegistroGCM.
Esta tarea asncrona tendr que realizar tres acciones principales: registrar la aplicacin
contra los servidores de GCM, registrarnos contra nuestro propio servidor al que tendr que
enviar entre otras cosas el registration_id obtenido de GCM, y por ltimo guardar como
preferencias compartidas los nuevos datos de registro.
1
private class TareaRegistroGCM extends AsyncTask<String,Integer,String>
2
{
3
@Override
4
protected String doInBackground(String... params)
5
{
6
String msg = "";
7
8
try
9
{
10
if (gcm == null)
11
{
12
gcm = GoogleCloudMessaging.getInstance(context);
13
}
14
15
//Nos registramos en los servidores de GCM
16
regid = gcm.register(SENDER_ID);
17
18
Log.d(TAG, "Registrado en GCM: registration_id=" + regid);
19
20
//Nos registramos en nuestro servidor
21
boolean registrado = registroServidor(params[0], regid);
22
23
//Guardamos los datos del registro
24
if(registrado)
25
{
26
setRegistrationId(context, params[0], regid);

425

27
28
29
30
31
32
33
34
35
36

}
}
catch (IOException ex)
{
Log.d(TAG, "Error registro en GCM:" + ex.getMessage());
}
return msg;
}
}

Lo primero que haremos ser obtener una instancia del servicio de Google Cloud
Messaging mediante el mtodo GoogleCloudMessaging.getInstance(). Obtenido este
objeto, el registro en GCM ser tan sencillo como llamar a su mtodo register() pasndole
como parmetro el Sender ID que obtuvimos al crear el proyecto en la Consola de APIs de
Google. Esta llamada nos devolver el registration_idasignado a nuestra aplicacin.
Tras el registro en GCM debemos tambin registrarnos en nuestro servidor, al que al menos
debemos enviarle nuestro registration_id para que nos pueda enviar mensajes
posteriormente. En nuestro caso de ejemplo, adems del cdigo de registro vamos a
enviarle tambin nuestro nombre de usuario. Como ya dijimos este registro lo vamos a
realizar utilizando el servicio web que creamos en el artculo sobre la parte servidor. La
llamada al servicio web es anloga a las que ya explicamos en el artculo sobre servicios
web SOAP por lo que no entrar en ms detalles, tan slo veamos el cdigo.
1
private boolean registroServidor(String usuario, String regId)
2
{
3
boolean reg = false;
4
5
final String NAMESPACE = "http://sgoliver.net/";
6
final String URL="http://10.0.2.2:1634/ServicioRegistroGCM.asmx";
7
final String METHOD_NAME = "RegistroCliente";
8
final String SOAP_ACTION = "http://sgoliver.net/RegistroCliente";
9
10
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
11
12
request.addProperty("usuario", usuario);
13
request.addProperty("regGCM", regId);
14
15
SoapSerializationEnvelope envelope =
16
new SoapSerializationEnvelope(SoapEnvelope.VER11);
17
18
envelope.dotNet = true;
19
20
envelope.setOutputSoapObject(request);
21

426

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

HttpTransportSE transporte = new HttpTransportSE(URL);


try
{
transporte.call(SOAP_ACTION, envelope);
SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();
String res = resultado_xml.toString();
if(res.equals("1"))
{
Log.d(TAG, "Registrado en mi servidor.");
reg = true;
}
}
catch (Exception e)
{
Log.d(TAG, "Error registro en mi servidor: " + e.getCause() + " || " + e.getMessage());
}
return reg;
}

Por ltimo, si todo ha ido bien guardaremos los nuevos datos de registro (usuario,
registration_id, version de la aplicacin y fecha de caducidad) como preferencias
compartidas. Lo haremos todo dentro del mtodosetRegistrationId().
1
private void setRegistrationId(Context context, String user, String regId)
2
{
3
SharedPreferences prefs = getSharedPreferences(
4
MainActivity.class.getSimpleName(),
5
Context.MODE_PRIVATE);
6
7
int appVersion = getAppVersion(context);
8
9
SharedPreferences.Editor editor = prefs.edit();
10
editor.putString(PROPERTY_USER, user);
11
editor.putString(PROPERTY_REG_ID, regId);
12
editor.putInt(PROPERTY_APP_VERSION, appVersion);
13
editor.putLong(PROPERTY_EXPIRATION_TIME,
14
System.currentTimeMillis() + EXPIRATION_TIME_MS);
15
16
editor.commit();
17 }
La forma de guardar los datos mediante preferencias compartidas ya la comentamos en
detalle en elartculo dedicado a las Shared Preferences. Lo nico a comentar es la forma de
calcular la fecha de caducidad del cdigo de registro. Vamos a calcular esa fecha por

427

ejemplo como la actual ms una semana. Para ello obtenemos la fecha actual en
milisegundos
con currentTimeMillis() y
le
sumamos
una
constante EXPIRATION_TIME_MS que hemos definido con el valor 1000 * 3600 * 24 *
7, es decir, los milisegundos de una semana completa.
Y con esto habramos terminado la fase de registro de la aplicacin. Pero para recibir
mensajes an nos faltan dos elementos importantes. Por un lado tendremos que
implementar un Broadcast Receiver que se encargue de recibir los mensajes, y por otro
lado crearemos un nuevo servicio (concretamente un Intent Service) que se encargue
de procesar dichos mensajes. Esto lo hacemos as porque no es recomendable realizar
tareas complejas dentro del propio broadcast receiver, por lo que normalmente utilizaremos
este patrn en el que delegamos todo el trabajo a un servicio, y el broadcast receiver se
limitar a llamar a ste.
En esta ocasin vamos a utilizar un nuevo tipo especfico de broadcast
receiver,WakefulBroadcastReceiver, que nos asegura que el dispositivo estar despierto
el tiempo que sea necesario para que termine la ejecucin del servicio que lancemos para
procesar los mensajes. Esto es importante, dado que si utilizramos un broadcast receiver
tradicional el dispositivo podra entrar en modo de suspensin (sleep mode) antes de que
terminramos de procesar el mensaje.
Crearemos por tanto una nueva clase que extienda de WakefulBroadcastReceiver, la
llamamosGCMBroadcastReceiver, e implementaremos el evento onReceive() para llamar a
nuestro
servicio
de
procesamiento
de
mensajes,
que
recordemos
lo
llamamos GCMIntentService. La llamada al servicio la realizaremos mediante el
mtodo startWakefulService() que recibir como parmetros el contexto actual, y el mismo
intent recibido sobre el que indicamos el servicio a ejecutar mediante su
mtodosetComponent().
1
public class GCMBroadcastReceiver extends WakefulBroadcastReceiver
2
{
3
@Override
4
public void onReceive(Context context, Intent intent)
5
{
6
ComponentName comp =
7
new ComponentName(context.getPackageName(),
8
GCMIntentService.class.getName());
9
10
startWakefulService(context, (intent.setComponent(comp)));
11
12
setResultCode(Activity.RESULT_OK);
13
}
14 }

428

Para el servicio crearemos una nueva clase GCMIntentService que extienda


de IntentService (para ms informacin sobre los Intent Service puedes consultar el artculo
dedicado a ellos) y como siempre implementaremos su evento onHandleIntent(). Aqu lo
primero que haremos ser nuevamente obtener una instancia a los Servicios de Google
Play, y posteriormente obtener el tipo de mensaje recibido (mediante getMessageType()) y
sus parmetros (mediante getExtras()). Dependiendo del tipo de mensaje obtenido
podremos realizar unas acciones u otras. Existen algunos tipos especiales de mensaje
(MESSAGE_TYPE_SEND_ERROR, MESSAGE_TYPE_DELETED, ) para ser
notificado de determinados eventos, pero el que nos interesa ms ser el
tipo MESSAGE_TYPE_MESSAGE que identifica a los mensajes normales o genricos
de GCM. Para nuestro ejemplo, en caso de recibirse uno de estos mensajes simplemente
mostraremos una notificacin en la barra de estado llamando a un mtodo
auxiliarmostrarNotificacion(). La implementacin de este ltimo mtodo tampoco la
comentaremos en detalle puesto que tenis disponible un artculo del curso especialmente
dedicado a este tema.
1
public class GCMIntentService extends IntentService
2
{
3
private static final int NOTIF_ALERTA_ID = 1;
4
5
public GCMIntentService() {
6
super("GCMIntentService");
7
}
8
9
@Override
10
protected void onHandleIntent(Intent intent)
11
{
12
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
13
14
String messageType = gcm.getMessageType(intent);
15
Bundle extras = intent.getExtras();
16
17
if (!extras.isEmpty())
18
{
19
if
20 (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType))
21
{
22
mostrarNotification(extras.getString("msg"));
23
}
24
}
25
26
GCMBroadcastReceiver.completeWakefulIntent(intent);
27
}

429

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

private void mostrarNotification(String msg)


{
NotificationManager mNotificationManager =
(NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setContentTitle("Notificacin GCM")
.setContentText(msg);
Intent notIntent = new Intent(this, MainActivity.class);
PendingIntent contIntent = PendingIntent.getActivity(
this, 0, notIntent, 0);
mBuilder.setContentIntent(contIntent);
mNotificationManager.notify(NOTIF_ALERTA_ID, mBuilder.build());
}
}

S es importante fijarse en que al final del mtodo onHandleIntent(), tras realizar todas las
acciones necesarias para procesar el mensaje recibido, debemos llamar al
mtodo completeWakefulIntent() de nuestro GCMBroadcastReceiver. Esto har que el
dispositivo pueda volver a entrar en modo sleep cuando sea necesario. Olvidar esta llamada
podra implicar consumir rpidamente la batera del dispositivo, y no es lo que queremos,
verdad?
Pues bien, hemos terminado. Ya tenemos nuestro servidor y nuestro cliente GCM
preparados. Si ejecutamos ambas y todo ha ido bien, introducimos un nombre de usuario
en la aplicacin Android, pulsamos Registrar para guardarlo y registrarnos, seguidamente
desde la aplicacin web introducimos el mismo nombre de usuario del cliente y pulsamos el
botn Enviar GCM, en pocos segundos nos debera aparecer la notificacin en la barra de
estado de nuestro emulador como se observa en la imagen siguiente:

Es conveniente utilizar un emulador en el que se ejecute una versin de Android 4.2.2 o


superior, dado que en versiones anteriores Google Play Services podra no funcionar. An

430

as, en ocasiones los mensajes tardar varios minutos en recibirse, por lo que tened algo de
paciencia.

IV. Integracin con Google+


Integracin con Google+ (I): Google+ Sign-In
by Sgoliver on 22/08/2013 in Android, Programacin
En los prximos artculos del Curso de Programacin Android vamos a centrarnos en otra
de las novedades presentadas hace poco como parte de los Google Play Services, en
concreto la integracin de aplicaciones con Google+. Integrar nuestras aplicaciones con
Google+ nos va a permitir entre otras cosas la posibilidad de que los usuarios inicien sesin
en nuestra aplicacin con su cuenta de Google, personalizar la aplicacin en funcin de los
datos de su perfil pblico y sus crculos, o enviar publicaciones a su perfil informando de la
actividad del usuario en nuestra aplicacin. En este primer artculo nos centraremos en la
primera y ms importante de estas posibilidades, el inicio de sesin con Google+.
Al igual que ocurra con las apis de mapas o mensajera push, para hacer uso de la API de
integracin con Google+ necesitaremos crear un nuevo proyecto en la consola de APIs de
Google, habilitar el servicio de Google+, y generar una nueva clave de acceso, en este
caso un nuevo ID de Cliente para autenticacin mediante OAuth 2.0.
Para ello accederemos a la Consola de APIs y crearemos un nuevo proyecto con el nombre
que deseemos utilizando la opcin Create de la lista desplegable situada en la parte
superior izquierda. Una vez creado el proyecto, accederemos a la seccin Services y
habilitaremos el servicio llamado Google+ API.

Tras esto, accederemos a la seccin API Access para generar el ID de acceso al servicio.
Para ello pulsamos sobre la opcin Create an OAuth 2.0 Client ID, lo que nos llevar a
un asistente de configuracin. En la primera pantalla indicaremos el nombre de la
aplicacin y un logo (opcional).

431

En la segunda, seleccionaremos la opcin Installed application y Android como tipo de


aplicacin. Adems tendremos que rellenar el paquete java que utilizado en nuestra
aplicacin y la huella SHA1 del certificado con el que firmaremos la aplicacin. Esto ya lo
hemos comentado en alguna ocasin, mientras estemos desarrollando/depurando la
aplicacin usaremos el certificado de pruebas (debug) instalado con el SDK de Android,
pero cuando subamos nuestra aplicacin a Google Play tendremos que modificar este dato
por el correspondiente al certificado real con el que firmemos la aplicacin final (si no lo
hacemos as la aplicacin no funcionar). Si utilizamos una versin reciente de Eclipse y el
plugin de Android (ADT) podemos obtener la huella SHA1 del certificado de pruebas
accediendo a las preferencias de Eclipse, en la seccin Android / Build. En la siguiente
captura podis ver la localizacin de este dato:

Si no dispusiramos de una versin reciente de las herramientas de desarrollo, tambin


podemos obtener el dato utilizando la utilidad keytool de java, tal como se indica por
ejemplo en el artculo del curso sobre la API de Google Maps.
Por ltimo, activaremos la opcin Deep Linking Enabled y finalizaremos el asistente
pulsando el botn Create client ID.

432

Con esto ya tendramos configurado el proyecto en la Consola de APIs y podramos


comenzar a crear el proyecto en Eclipse. El primer paso, como ocurre con todos los
proyectos que hacen uso de los Google Play Services, ser importar el proyecto de librera
que lo soporta y hacer referencia a l desde nuestro proyecto. Estos pasos se comentan en
detalle en el artculo de introduccin a los Google Play Services.
Una vez preparado el proyecto en Eclipse entramos por fin en la parte interesante. Para
aadir el botn de login de Google+ en nuestra aplicacin bastar con incluir en nuestro
layout un control de tipoSignInButton de la siguiente forma:
1 <com.google.android.gms.common.SignInButton
2
android:id="@+id/sign_in_button"
3
android:layout_width="wrap_content"
4
android:layout_height="wrap_content" />
Este botn ser el que permita al usuario acceder a nuestra aplicacin haciendo uso de su
usuario de Google. Sin embargo, no ser el botn el que desencadene el proceso de
conexin a Google+, sino que actuaremos de una forma algo peculiar. Nuestra actividad
intentar conectarse al servicio desde su inicio de forma que si el usuario ya estaba
logueado con anterioridad el proceso de conexin se transparente para l. De forma
anloga, justo al salir de la actividad nos desconectaremos del servicio.
Pero entonces, para qu nos sirve el botn de login? Pues la clave del prrafo anterior est
en las palabras si el usuario ya estaba logueado con anterioridad. Y no slo eso, para
que la conexin sea completamente transparente y no necesite ninguna intervencin del
usuario, ste debe estar ya logueado y adems debe haber dado su permiso para que la
aplicacin acceda a sus datos de Google+ y pueda realizar determinadas acciones en su

433

nombre. En el caso de que alguno de estos pasos no se hayan realizado ya, el intento de
conexin realizado al inicio de la actividad derivar en un error que deber ser tratado
por el usuario. Y esto es precisamente lo que conseguir el usuario al pulsar el botn de
login colocado en la aplicacin. Pero tampoco hay que preocuparse, porque la API de
Google+ proporciona todos los elementos necesarios para que el usuario pueda resolver
estas acciones, por ejemplo el dilogo de seleccin de la cuenta con la que se acceder a
Google+, o el dilogo donde el usuario podr seleccionar los permisos relacionados con
Google+ que desea conceder a la aplicacin (por ejemplo, la visibilidad de determinados
crculos).
Pues bien, veamos cmo plasmamos en el cdigo de la aplicacin todo esto que hemos
contado con palabras. Empezaremos inicializando los componentes necesarios durante la
creacin de la actividad. La conexin con Google+ se sustenta completamente en la
clase PlusClient, por lo que el primer paso ser crear e inicilizar un objeto de este tipo. Esto
lo conseguimos mediante el mtodo PlusClient.Builder(). En esta inicializacin
indicaremos adems las actividades (de acciones, no de Activity) del usuario en la
aplicacin que la propia aplicacin podr publicar en el perfil de Google+ en nombre del
usuario (por ejemplo acciones del estilo a He escuchado tal cancin, He visto tal
imagen o He comentado tal noticia). Existen varios tipos de actividad predefinidas
como BuyActivity, ListenActivity,CommentActivity para
actividades
de
compras,
reproduccin de msica o comentarios (podis revisar la lista completa en esta pgina y un
tipo genrico (AddActivity) para cuando las actividades que enviar nuestra aplicacin a
Google+ con encaja con ninguno de los tipos predefinidos. La lista de actividades que la
aplicacin podr enviar al perfil de Google+ del usuario se configurar mediante el
mtodosetVisibleActivities(), y sern mostradas al usuario al loguearse por primera vez en
la aplicacin de forma que ste sea consciente de ello y pueda conceder su permiso. Ms
tarde pondr una captura de pantalla donde podr verse esto claramente. En nuestro caso
aadiremos por ejemplo las actividadesAddActivity y ListenActivity para ver el efecto.
Adems de esto, para terminar inicializaremos tambin un dilogo de progreso que
utilizaremos ms tarde. Veamos cmo queda el mtodo onCreate()al completo:
1
@Override
2
protected void onCreate(Bundle savedInstanceState)
3
{
4
super.onCreate(savedInstanceState);
5
setContentView(R.layout.activity_main);
6
7
btnSignIn = (SignInButton)findViewById(R.id.sign_in_button);
8
9
plusClient = new PlusClient.Builder(this, this, this)

434

10
11
12
13
14
15
16
17
18
19

.setVisibleActivities(
"http://schemas.google.com/AddActivity",
"http://schemas.google.com/ListenActivity")
.build();
connectionProgressDialog = new ProgressDialog(this);
connectionProgressDialog.setMessage("Conectando...");
//...
}

Vamos ahora con la conexin y desconexin a Google+. Como dijimos anteriormente, la


conexin la intentaremos realizar desde el inicio de la actividad y nos desconectaremos
juasto al salir, por lo que podemos aprovechar los eventos onStart() y onStop() de la
actividad
para
realizar
estas
acciones.
Utilizaremos
para
ello
los
mtodos connect() y disconnect() de la clase PlusClient.
1
@Override
2
protected void onStart()
3
{
4
super.onStart();
5
plusClient.connect();
6
}
7
8
@Override
9
protected void onStop()
10 {
11
super.onStop();
12
plusClient.disconnect();
13 }
Para capturar el resultado de estas acciones de conexin y desconexin podemos
implementar los eventosonConnected() y onDisconnected(), para lo que nuestra actividad
tendr que implementar la interfazConnectionCallbacks.
1
public class MainActivity extends Activity
2
implements ConnectionCallbacks
3
{
4
//...
5
6
@Override
7
public void onConnected(Bundle connectionHint)
8
{
9
connectionProgressDialog.dismiss();
10
11
Toast.makeText(this, "Conectado!",
12
Toast.LENGTH_LONG).show();
13
}

435

14
15
@Override
16
public void onDisconnected()
17
{
18
Toast.makeText(this, "Desconectado!",
19
Toast.LENGTH_LONG).show();
20
}
21 }
Con lo implementado hasta ahora bastar la mayora de las veces. Sin embargo, como
dijimos al principio, la primera vez que el usuario se intenta conectar se requieren acciones
adicionales, como seleccionar la cuenta a utilizar con Google+ o conceder a la aplicacin
los permisos necesarios para interactuar con nuestro perfil de Google+. En estas
circunstancias la llamada al mtodo connect() no tendr xito. Cmo podemos
solucionarlo? Pues como ya hemos indicado esta situacin intentar solucionarse cuando el
usuario pulse el botn de login de Google+ que hemos colocado en la aplicacin. Pero para
poder solucionarlo antes debemos saber qu ha ocurrido exactamente en la llamada
a connect(). Esto lo podemos saber implementando el evento onConnectionFailed() que se
ejecuta cuando la llamada aconnect() no finaliza correctamente. Este evento recibe como
parmetro un objeto de tipoConnectionResult que contiene el motivo por el que no hemos
podido conectarnos al servicio de Google+. Para poder gestionar este evento haremos que
nuestra actividad implemente otra interfaz ms llamada OnConnectionFailedListener.
1
public class MainActivity extends Activity
2
implements ConnectionCallbacks, OnConnectionFailedListener
3
{
4
//...
5
6
@Override
7
public void onConnectionFailed(ConnectionResult result)
8
{
9
//...
10
11
connectionResult = result;
12
}
13 }
Como podemos ver, en este evento nos limitaremos por el momento a guardar el
objetoConnectionResult para tenerlo disponible cuando el usuario pulse el botn de login.
Y vamos ya por fin con nuestro botn. Qu debemos hacer cuando el usuario pulse el
botn de login? Pues en primer lugar comprobaremos que no estamos ya conectados
mediante una llamada al mtodoisConnected(), en cuyo caso no habr nada que hacer. Si no
estamos an conectados pueden ocurrir dos cosas: que dispongamos ya del resultado del
intento de conexin en forma de objetoConnectionResult, o que an no lo tengamos

436

disponible. Para este ltimo caso utilizaremos el dilogo de progreso que inicializamos en
el onCreate() de la actividad, lo mostraremos mediante su mtodo show()y quedaremos a la
espera de disponer del resultado de la conexin.
En caso de conocer ya el resultado de la conexin, llamaremos a su
mtodostartResolutionForResult(). Este mtodo mgico provocar que se muestren al
usuario las opciones necesarias para resolver los errores detectados durante el intento de
conexin a Google+, entre ellos la seleccin de cuenta o el dilogo de concesin de
permisos de Google+.
1
btnSignIn.setOnClickListener(new OnClickListener() {
2
3
@Override
4
public void onClick(View view)
5
{
6
if (!plusClient.isConnected())
7
{
8
if (connectionResult == null)
9
{
10
connectionProgressDialog.show();
11
}
12
else
13
{
14
try
15
{
16
connectionResult.startResolutionForResult(
17
MainActivity.this,
18
REQUEST_CODE_RESOLVE_ERR);
19
}
20
catch (SendIntentException e)
21
{
22
connectionResult = null;
23
plusClient.connect();
24
}
25
}
26
}
27
}
28 });
Cuando el usuario termine de configurar las opciones de conexin a Google+ se lanzar
automticamente el evento onActivityResult(), momento que aprovecharemos para volver a
realizar la conexin llamando de nuevo a connect() ahora que no deberan quedar acciones
por realizar por parte el usuario. Si todo va bien, este nuevo intento de conexin debera
terminar con xito y el usuario quedara conectado (y entre otras cosas se ejecutar el
evento onConnected() del que ya hemos hablado).

437

1
2
3
4
5
6
7
8
9
10

@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent)
{
if (requestCode == REQUEST_CODE_RESOLVE_ERR &&
responseCode == RESULT_OK)
{
connectionResult = null;
plusClient.connect();
}
}

Con esto casi hemos terminado. Pero nos faltan un par de detalles por cerrar que antes he
omitido a posta para llevar un orden ms lgico en la explicacin. En primer lugar, qu
ocurre cuando el resultado del primer intento de conexin nos llega despus de que el
usuario haya pulsado el botn de login (y por tanto ya se est mostrando el dilogo de
progreso)? En ese caso no slo guardaremos el resultado de la conexin, sino que
desencadenaremos directamente el proceso de resolucin de errores llamando
astartResolutionForResult() igual que hemos hecho en el evento onClick del botn de login.
De esta forma, el evento onConnectionFailed() quedara finalmente de la siguiente forma:
1
@Override
2
public void onConnectionFailed(ConnectionResult result)
3
{
4
if (connectionProgressDialog.isShowing())
5
{
6
if (result.hasResolution())
7
{
8
try
9
{
10
result.startResolutionForResult(this,
11
REQUEST_CODE_RESOLVE_ERR);
12
}
13
catch (SendIntentException e)
14
{
15
plusClient.connect();
16
}
17
}
18
}
19
20
connectionResult = result;
21 }
Por tlimo, nos queda cerrar el dilogo de progreso una vez que el usuario est
correctamente conectado, lo que haremos en el evento onConnected() y utilizaremos para
ello el mtodo dismiss() del dilogo, quedando finalmente as:
1 @Override

438

2
3
4
5
6
7
8

public void onConnected(Bundle connectionHint)


{
connectionProgressDialog.dismiss();
Toast.makeText(this, "Conectado!",
Toast.LENGTH_LONG).show();
}

Y ahora s habramos terminado, por lo que estamos en condiciones de probar la aplicacin.


Recomiendo probar en un dispositivo real conectado por USB, ya que los servicios de
Google Play no funcionan correctamente en el emulador (dependiendo de la versin e
imagen de Android utilizada).
Al ejecutar la aplicacin se mostrar el botn de login de Google+ y comenzar de forma
silenciosa el proceso de conexin.

Al pulsar el botn de Iniciar Sesin debemos ya contar con el resultado de la conexin, por
lo que se nos debe dirigir directamente al proceso de resolucin de errores. En primer lugar
tendremos que seleccionar la cuenta de google con la que queremos contectarnos:

439

Tras seleccionar una cuenta aparecer el dilogo de configuracin de permisos de Google+,


en el que se informa al usuario de los permisos que solicita la aplicacin. Podremos definir
los crculos a los que tendr acceso la aplicacin y los crculos que podrn ver las
publicaciones que la aplicacin realice por nosotros en nuestro perfil de Google+.

En la captura anterior me gustara que prestrais atencin al texto del segundo bloque,
donde indica Permitir que la actividad de aplicaciones y de audio est disponible.
Este mensaje debe coincidir con las actividades que establecimos al llamar al
mtodo setVisibleActivities() al principio del ejemplo. Recuerdo que nosostros
seleccionamos la genrica AddActivity y la de audio ListenActivity.
Tras aceptar esta ltima pantalla deberamos estar ya conectados correctamente a Google+,
apareciendo el mensaje toast que nos informa de ello. Lo que podremos hacer a partir de
aqu queda para el siguiente artculo.
Pero no terminamos an, nos quedan un par de temas importantes por aadir. Si queremos
que nuestra aplicacin cumpla con las polticas de Google+ debemos ofrecer al usuario una
opcin para cerrar sesin en Google+, y otra para que pueda revocar los permisos que ha
concedido a nuestra aplicacin la primera vez que se conect.
En mi ejemplo aadir estas opciones como acciones del men de overflow de la action bar
(si necesitas informacin sobre cmo hacer esto puedes ojear el artculo sobre la action
bar).

440

Cerrar sesin ser tan sencillo como llamar al mtodo clearDefaultAccount() y


desconectarnos condisconnect(). Posteriormente volvemos a llamar a connect() para que se
inicie un nuevo proceso de login si el usuario pulse de nuevo el botn.
La opcin de revocar los permisos de la aplicacin tampoco es mucho ms complicada.
Llamaremos igual que antes al mtodo clearDefaultAccount() para eliminar la vinculacin
de
nuestra
cuenta
con
la
aplicacin,
y
posteriormente
llamaremos
a revokeAccessAndDisconnect() para revocar los permisos concedidos. Este ltimo mtodo
recibe como parmetro el listener con el que podremos capturar el evento de revocacin de
permisos finalizada (onAccessRevoked). En nuestro tan solo mostraremos un toast para
informar
de
ello.
Veamos cmo
quedaran ambas acciones
en el
evento onMenuItemSelected de la action bar.
1
@Override
2
public boolean onMenuItemSelected(int featureId, MenuItem item)
3
{
4
switch (item.getItemId())
5
{
6
//Cerrar Sesin
7
case R.id.action_sign_out:
8
if (plusClient.isConnected())
9
{
10
plusClient.clearDefaultAccount();
11
plusClient.disconnect();
12
plusClient.connect();
13
14
Toast.makeText(MainActivity.this,
15
"Sesin Cerrada.",
16
Toast.LENGTH_LONG).show();
17
}
18
19
return true;
20
//Revocar permisos a la aplicacin
21
case R.id.action_revoke_access:
22
if (plusClient.isConnected())
23
{
24
plusClient.clearDefaultAccount();
25
26
plusClient.revokeAccessAndDisconnect(
27
new OnAccessRevokedListener() {
28
@Override
29
public void onAccessRevoked(ConnectionResult status)
30
{
31
Toast.makeText(MainActivity.this,
32
"Acceso App Revocado",

441

33
34
35
36
37
38
39
40
41
42

Toast.LENGTH_LONG).show();
}
});
}
return true;
default:
return super.onMenuItemSelected(featureId, item);
}
}

Y hasta aqu el primer artculo sobre integracin con Google+. En el siguiente veremos
varias de las funcionalidades que tendremos disponibles al estar conectados con este
servicio.

Integracin con Google+ (II): Datos de perfil y Crculos


by Sgoliver on 19/09/2013 in Android, Programacin
En el artculo anterior de la serie vimos cmo podamos incluir en nuestra aplicacin la
opcin de que el usuario se pueda loguear utilizando su cuenta de Google a travs de
Google+. En este nuevo artculo veremos cmo acceder a la informacin del perfil del
usuario y cmo recuperar la lista de sus contactos y la informacin sobre ellos.
Empecemos por el principio. Una vez el usuario est correctamente logueado en Google+
nuestra aplicacin tendr acceso a todos los datos pblicos de su perfil. El acceso a estos
datos se podr realizar a travs de la clase Person, que contiene los mtodos necesarios para
recuperar cada uno de los datos del perfil.
Para obtener una referencia a un objeto Person para el usuario logueado utilizaremos el
mtodoloadPerson() pasndole como segundo parmetro el ID del usuario si lo conocemos,
o el valor especial me. Por su parte, el primer parmetro de loadPerson() debe ser una
referencia a un objeto que implemente la interfaz PlusClient.OnPersonLoadedListener, que
aade un nico mtodo (onPersonLoaded) que ser llamado cuando se hayan recuperado
los datos del usuario solicitado y pueda utilizarse su objeto Person asociado.
En nuestro caso de ejemplo haremos que sea nuestra actividad principal la que implemente
dicha interfaz e implementaremos el mtodo onPersonLoaded() como uno ms de la
actividad. Indicar aqu que para el ejemplo de este artculo partiremos del ya construido en
el artculo anterior. Veamos entonces cmo accederamos a los datos del usuario logueado:
1
public class MainActivity extends Activity
2
implements ConnectionCallbacks, OnConnectionFailedListener,
3
PlusClient.OnPersonLoadedListener
4
{
5
@Override

442

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

public void onConnected(Bundle connectionHint)


{
//...
//Informacin del perfil del usuario logueado:
plusClient.loadPerson(this, "me");
}
@Override
public void onPersonLoaded(ConnectionResult status, Person person) {
if (status.getErrorCode() == ConnectionResult.SUCCESS)
{
txtInfo.setText(
person.getId() + "\n" +
person.getDisplayName() + "\n" +
person.getPlacesLived().get(0).getValue() + "\n" +
person.getOrganizations().get(1).getName() + "\n" +
person.getUrls().get(0).getValue() + "\n" +
plusClient.getAccountName()
);
}
}
//...
}

Como podemos ver, en el mtodo onConnected() llamamos a loadPerson() para solicitar de


forma asncrona los datos del usuario me, es decir, el usuario actualmente loguado en la
aplicacin. Una vez estos datos estn disponible se ejecutar automticamente el
mtodo onPersonLoaded(), en el que se recibe como parmetro el objeto Person que
encapsula todos los datos del perfil pblico del usuario.
Para acceder a los datos a travs del objeto Person tenemos disponibles multitud de
mtodos que podis consultar en la documentacin oficial de la clase. Yo, a modo de
ejemplo, utilizo cinco de ellos y muestro el resultado en un simple cuadro de texto (txtInfo)
que he aadido a la interfaz. En primero lugar recupero el ID del usuario con getId(), a
continuacin recupero el nombre mostrado en el perfil congetDisplayName(), despus el
lugar de residencia con getPlacesLived(), mi empresa actual congetOrganizations() y el
primero de los enlaces pblicos de mi perfil que corresponde a mi web utilizando getUrls().
Como podis ver algunos de estos mtodos devuelven listas de datos,
como getPlacesLived() ogetOrganizations(), a cuyos elementos accedemos mediante el
mtodo get(i) y obtenemos su valor con getName() o getValue() dependiendo de la entidad

443

recuperada. Como ya he dicho, los datos disponibles son muchos y lo mejor es consultar la
documentacin oficial en cada caso.
Si volveis a mirar el cdigo anterior, al final del todo recupero un dato ms, en este caso
utilizando directamente un mtodo del cliente de Google+ en vez de la clase Person. Este
mtodogetAccountName() se utiliza para consultar la direccin de correo electrnico del
usuario logueado, ya que aunque existe un mtodo getEmails() en la clase Person, ste no
devolver la direccin principal del usuario a menos que ste la haya hecho pblica en su
perfil.
Si ejecutamos ahora la aplicacin de ejemplo podris ver los datos del perfil del usuario que
hayis utilizado para hacer login, en mi caso algo as:

Lo siguiente que vamos a recuperar son los contactos incluidos en los crculos del usuario.
Recordad que tal como vimos en el artculo anterior la aplicacin slo tendr acceso a los
crculos a los que usuario haya dado permiso al loguarse en la aplicacin. Otro detalle a
tener en cuenta es que podremos acceder a los contactos pero no a los nombres de los
crculos que los contienen.
Para recuperar los contactos incluidos en los crculos del usuario que son visibles para la
aplicacin utilizaremos el mtodo loadPeople(). Este mtodo nos devolver un
objeto PersonBuffer con todos los contactos de los crculos visibles. Al igual que pasaba
con loadPerson(), esta carga la har de forma asncrona de forma que cuando haya
finalizado se llamar automticamente al mtodoonPeopleLoaded() del objeto que le
pasemos
como
parmetro,
que
debe
implementar
la
interfaz PlusClient.OnPeopleLoadedListener. Para ello haremos lo mismo que antes,
implementaremos dicha interfaz en nustra actividad principal.
1
public class MainActivity extends Activity
2
implements ConnectionCallbacks, OnConnectionFailedListener,
3
PlusClient.OnPersonLoadedListener, PlusClient.OnPeopleLoadedListener
4
{

444

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

@Override
public void onConnected(Bundle connectionHint)
{
//...
//Personas en mis crculos visibles para la aplicacin:
plusClient.loadPeople(this, Person.Collection.VISIBLE);
}

@Override
public void onPeopleLoaded(ConnectionResult status, PersonBuffer personBuffer, String nextPageTo
{
if (status.getErrorCode() == ConnectionResult.SUCCESS)
{
try
{
int count = personBuffer.getCount();
StringBuffer contactos = new StringBuffer("");
for (int i = 0; i < count; i++)
{
contactos.append(
personBuffer.get(i).getId() + "|" +
personBuffer.get(i).getDisplayName() + "\n");
}
txtContactos.setText(contactos);
}
finally
{
personBuffer.close();
}
}
}
}

Comentemos la implementacin del mtodo onPeopleLoaded(). Lo primero que hacemos


es
obtener
el
nmero
de
contactos
recuperados
llamando
al
mtodo getCount() del PersonBuffer. Hecho esto recorremos la lista accediendo a cada
contacto mediante el mtodo get(i) que devuelve su objetoPerson asociado. A partir de aqu
ya podemos mostrar los datos necesarios de cada contacto utilizando los mtodos de la
clase Person que ya hemos comentado antes. Como ejemplo yo muestro el ID y el nombre
de cada contacto en un cuadro de texto adicional que he aadido a la interfaz. Por ltimo,
pero no menos importante, debemos cerrar el objeto PersonBuffer mediante su
mtdo close() para liberar recursos.

445

Si volvemos a ejecutar ahora la aplicacin obtendremos algo similar a lo siguiente:

Por ltimo, comentar que ahora que tenemos los ID de cada contacto, si en algn momento
necesitamos obtener los datos de su perfil podramos utilizar el mismo
mtodo loadPerson() que hemos comentado antes pasndole su ID como segundo
parmetro. As, por ejemplo, si quisiera recuperar los datos del perfil de Roman
Nurik realizaramos la siguente llamada:
1 //Perfil de un contacto:
2 plusClient.loadPerson(this, "113735310430199015092");
Y hasta aqu este segundo artculo de la serie. En el prximo veremos cmo podemos
publicar contenidos en Google+ desde nuestra aplicacin.

You might also like