Vous êtes sur la page 1sur 276

Entorno de deesarrolllo An

ndroid
d
Por sggoliver on 04
4/08/2010 enn Android, Programacinn

Para empezar
e conn este Cursoo de Program
macin Anddroid, voy a describir los
l pasos bsicos para
disponner en nuesttro PC del entorno y las
l herramieentas necesaarias para comenzar
c a programar
aplicacciones Andrroid.

No vooy a ser ex xhaustivo, yaa existen muy


m buenos tutoriales sobre s la insstalacin de Eclipse y
Androoid, incluida al documenntacin oficiaal de la plataaforma. Adeems, si has llegado
l hastta este blog
quieroo suponer qu ue tienes unnos conocimmientos bsiicos de Eclipse y Javaa, por lo quue tan slo
enumeerar los passos necesariios de installacin y connfiguracin, y proporcioonar los ennlaces a las
distinttas herramien
ntas. Vamoss all.

Paso 1.
1 Descarga e instalacin de Eclipsse.

Si ann no tienes instalado


i Ecclipse, puedees descargar la ltima versin, la 4..2 [Eclipse Juno]
J en la
ltimaa revisin dee este artculoo, desde estee enlace. Reccomiendo deescargar la versin
v Eclippse IDE for
Java Developers,, y por suppuesto desccargar la veersin aproppiada para tu sistema operativo
(Winddows/Mac OS/Linux, y 32/64 3 bits). Durante
D el cuurso siempree utilizar Windows
W 64 bits.
b

Descaargar Eclipsee

La insstalacin con nsiste simplemente en descomprim


d ir el zip desscargado en la ubicacin deseada.
Para ejecutarlo accederemos
a s al ficheroo eclipse e.exe denntro de la ruta dondee hayamos
descommprimido laa aplicacinn, por ejempplo c:\ecl lipse\ecl lipse.exe e. Durante la primera
ejecuccin de la applicacin noss preguntar cul ser laa carpeta doonde queremmos almacenaar nuestros
proyecctos. Indicarremos la rutaa deseada y marcaremos
m la check U
Use this as thhe default para
p que no
vuelvaa a preguntarrlo.

Eclipsse Seleccionaar Workspacce

Paso 2.
2 Descargar el SDK dee Android.
El SDDK de la plaataforma Anndroid se pueede descargar desde aquu (en el moomento de revisar
r este
artcullo la ltimaa versin ess la 20.0.3, que funcionna perfectam mente con Eclipse
E 4.2)). Una vez
descarrgado, bastarr con ejecuttar el instalaador estndarr de Window
ws.

Descaargar SDK Android


A

Paso 3.
3 Descargar el plugin Android
A para Eclipse.

Googlle pone a disposicin


d de los dessarrolladoress un pluginn para Ecliipse llamado Android
Develoopment Too ols (ADT) que q facilita en gran medida
m el deesarrollo dee aplicacionnes para la
platafoorma. Podiis descargarllo mediante las opcionees de actuallizacin de Eclipse,
E acccediendo al
men Help / Insta ware e inndicando la siguiente
all new softw s UR
RL de descarrga:

https://dl-ssl.
.google.com/android/
/eclipse/

Selecccionaremos los
l dos paquuetes disponiibles Develloper Tools y NDK Pluugins y pullsaremos el
botn Next> parra comenzarr con el asistente de instaalacin.
Instalaar Plugin AD
DT

Paso 4.
4 Configura
ar el plugin
n ADT.

Una vez instalado el plugin, teendremos quue configurarrlo indicando la ruta en la


l que hemoos instalado
DK de Andrroid. Para ello,
el SD e iremos a la ventaana de conffiguracin de d Eclipse (Window
( /
Preferrences), y en la seccinn de Android indicaremoos la ruta enn la que se haa instalado. Finalmente
F
pulsarremos OK paara aceptar loos cambios.

Configgurar ADT

Paso 5.
5 Instalar las Platform Tools y los Platforms necesarios.
n

Ademms del SDK de Androidd comentadoo en el paso 2, que conttiene las herrramientas bsicas para
desarrrollar en Android, tambiin deberemmos descargaar las llamaddas Platflorm m Tools, quue contiene
herrammientas espeecficas de la ltima verrsin de la plataforma,
p y una o varrias plataforrmas (SDK
Platforms) de And droid, que no son ms que
q las librerras necesariias para desaarrollar sobrre cada una
de las versiones concretas
c de Android. As,
A si querem mos desarrollar por ejemmplo para Android
A 2.2
tendreemos que desscargar su plataforma
p coorrespondiennte. Mi conssejo personaal es siempree instalar al
menoss 2 plataforrmas: la coorrespondiennte a la lltima versin disponibble de Anddroid, y la
correspondiente a la mnima versin
v de Anndroid que queremos
q quue soporte nuuestra aplicaacin.

Para ello,
e desde Eclipse
E debemmos accederr al men W Window / Anndroid SDK K Manager. En la lista
de paaquetes disp ponibles seleccionaremoos las Anddroid SDK Platform-toools, las plataformas
p
Andrroid 4.1 (APII 16) y Android 2.2 (A API 8), y el paquete extrra Androidd Support Libbrary, que
es unna librera que q nos peermitir utillizar en versiones antiiguas de Android
A caraactersticas
introduucidas por versiones
v ms recientes. Pulsaremos el botn Innstall packagges y esperaremos a
que finnalice la desscarga.
Androoid SDK Man
nager

Paso 6.
6 Configura
ar un AVD..

A la hora
h de probaar y depurar aplicaciones Android no tendremoss que hacerloo necesariammente sobre
un disspositivo fssico, sino quue podremoss configurarr un emuladdor o dispossitivo virtuaal (Android
Virtuaal Device, o AVD) donnde poder reealizar fcilm mente estas tareas. Paraa ello, accedderemos al
AVD Manager
M (m
men Window w / AVD Manager),
M y en
e la seccinn Virtual Deevices podremmos aadir
tantos AVD como o se necesiteen (por ejem
mplo, configgurados paraa distintas veersiones de Android o
distinttos tipos de dispositivo)). Nuevamennte, mi conssejo ser coonfigurar al menos dos AVD, uno
para la mnima versin
v de Android
A que queramos soportar,
s y otro para laa versin ms reciente
disponnible.

AVD Manager
M

Para configurar
c el AVD tan slo tendrem mos que inddicar un nom mbre descripptivo, el verrsin de la
platafoorma Androiid que utilizzar, y las caaractersticass de hardwarre del dispossitivo virtual, como por
ejemplo su resoluccin de panttalla, el tamaao de la tarrjeta SD, o la disponibilidad de GPS S. Adems,
marcaaremos la opccin Snapshhot enabled, que nos peermitir arraancar el emuulador ms rpidamente
en futuuras ejecucio
ones.
Crear nuevo AVD
D

Y conn este paso ya tendram mos preparaadas todas lasl herramieentas necesaarias para comenzar a
desarrrollar aplicacciones Androoid. En prxximos artcullos veremos como crearr un nuevo proyecto,
p la
estructtura y comp ponentes de un
u proyectoo Android, y crearemos unau aplicaciin sencilla para poner
en prctica todos los
l conceptoos aprendidos.

Esttructu
ura de un prroyecto And
droid
Por sggoliver on 09
9/08/2010 enn Android, Programacinn

Seguimmos con el Curso


C de Programacin Android. Paara empezarr a comprendder cmo see construye
una applicacin An
ndroid vamoss a echar un vistazo a la estructura general
g de unn proyecto tippo.

Cuanddo creamos un u nuevo prooyecto Andrroid en Eclippse se generaa automticaamente la esstructura de


carpettas necesariaa para poderr generar poosteriormente la aplicacin. Esta esstructura ser comn a
cualquuier aplicaci
n, independdientemente de su tamao y complejjidad.

En la siguiente
s im
magen vemos los elementtos creados inicialmente
i para un nueevo proyectoo Android:

Descriibamos los elementos


e prrincipales.
Carpeta /src/

Contiene todo el cdigo fuente de la aplicacin, cdigo de la interfaz grfica, clases auxiliares, etc.
Inicialmente, Eclipse crear por nosotros el cdigo bsico de la pantalla (Activity) principal de la
aplicacin, siempre bajo la estructura del paquete java definido.

Carpeta /res/

Contiente todos los ficheros de recursos necesarios para el proyecto: imgenes, vdeos, cadenas de
texto, etc. Los diferentes tipos de recursos de debern distribuir entre las siguientes carpetas:

/res/drawable/. Contienen las imgenes de la aplicacin. Se puede dividir en


/drawable-ldpi, /drawable-mdpi y /drawable-hdpi para utilizar diferentes
recursos dependiendo de la resolucin del dispositivo.
/res/layout/. Contienen los ficheros de definicin de las diferentes pantallas de la
interfaz grfica. Se puede dividir en /layout y /layout-land para definir distintos
layouts dependiendo de la orientacin del dispositivo.
/res/anim/. Contiene la definicin de las animaciones utilizadas por la aplicacin.
/res/menu/. Contiene la definicin de los mens de la aplicacin.
/res/values/. Contiene otros recursos de la aplicacin como por ejemplo cadenas de
texto (strings.xml), estilos (styles.xml), colores (colors.xml), etc.
/res/xml/. Contiene los ficheros XML 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.

Como ejemplo, para un proyecto nuevo Android, se crean los siguientes recursos para la aplicacin:
Carpeta /gen/

Contiene una serie de elementos de cdigo generados automticamente al compilar el proyecto.


Cada vez que generamos nuestro proyecto, la maquinaria de compilacin de Android genera por
nosotros una serie de ficheros fuente en java dirigidos al control de los recursos de la aplicacin.

El ms importante es el que se puede observar en la imagen, el fichero R.java, y la clase R.

Esta clase R contendr en todo momento una serie de constantes con los ID de todos los recursos de
la aplicacin incluidos en la carpeta /res/, de forma que podamos acceder facilmente a estos
recursos desde nuestro cdigo a traves de este dato. As, por ejemplo, la constante
R.drawable.icon contendr el ID de la imagen icon.png contenida en la carpeta
/res/drawable/. Veamos como ejemplo la clase R creada por defecto para un proyecto nuevo:

1
2 package net.sgoliver;
3
4 public final class R {
5 public static final class attr {
}
6
public static final class drawable {
7 public static final int icon=0x7f020000;
8 }
9 public static final class layout {
10 public static final int main=0x7f030000;
}
11 public static final class string {
12 public static final int app_name=0x7f040001;
13 public static final int hello=0x7f040000;
14 }
15}
16

Carpeta /assets/

Contiene todos los dems ficheros auxiliares necesarios para la aplicacin (y que se incluirn en su
propio paquete), como por ejemplo ficheros de configuracin, de datos, etc.
La diferencia entre los recursos incluidos en la carpeta /res/raw/ y los incluidos en la carpeta
/assets/ es que para los primeros se generar un ID en la clase R y se deber acceder a ellos con
los diferentes mtodos de acceso a recursos. Para los segundos sin embargo no se generarn ID y se
podr acceder a ellos por su ruta como a cualquier otro fichero del sistema. Usaremos uno u otro
segn las necesidades de nuestra aplicacin.

Fichero AndroidManifest.xml

Contiene la definicin en XML de los aspectos principales de la aplicacin, como por ejemplo su
identificacin (nombre, versin, icono, ), sus componentes (pantallas, mensajes, ), o los
permisos necesarios para su ejecucin. Veremos ms adelante ms detalles de este fichero.

En el siguiente post veremos los componentes software principales con los que podemos construir
una aplicacin Android.

Componentes de una aplicacin Android


Por sgoliver on 11/08/2010 en Android, Programacin

En el post anterior 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 post 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 en
cualquier otro lenguaje visual.

View

Los objetos 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.
Service

Los servicios son componentes sin interfaz grfica que se ejecutan en segundo plano. En concepto,
son exactamente iguales 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 (activities) si se necesita en algn momento la interaccin con
del usuario.

Content Provider

Un 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 los content 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 post empezaremos ya a ver algo de cdigo, analizando al detalle una aplicacin
sencilla.

Desarrollando una aplicacin Android sencilla


Por sgoliver on 16/08/2010 en Android, Programacin
Despuus de instalaar nuestro enntorno de dessarrollo paraa Android y comentar la estructura bsica
b de
un prooyecto y los diferentes coomponentes software quue podemos utilizar
u ya ess hora de em
mpezar a
escribiir algo de c
digo. Y commo siempre lol mejor es empezar
e por escribir unaa aplicacin sencilla.
s

En un principio mem plante annalizar en esste post el cllsico Hola Mundo


M peroo ms tarde me
m pareci
que see iban a qued
dar algunas cosas
c bsicaas en el tinterro. As que he
h versionaddo a mi maneera el Hola
Mundoo transformndolo en allgo as comoo un Hola Usuario,
U que es igual de sencilla peroo aade un
par dee cosas intereesantes de contar. La applicacin connstar de dos pantallas, porp un lado la pantalla
princippal que soliicitar un noombre al ussuario y unaa segunda pantalla
p en la
l que se mostrar
m un
mensaaje personalizado para el usuario. Sencillo, inntil, pero aprenderem mos muchos conceptos
bsicoos, que para empezar
e no est mal.

En priimer lugar vamos


v a creaar un nuevo proyecto Anndroid tal coomo vimos ala final del primer
p post
de la serie.
s Llamarremos al prooyecto HolaaUsuario, inndicaremos como targett por ejemploo Android
1.6, daremos un n nombre a la aplicaccin e indiccaremos quee se cree una u actividaad llamada
HolaUUsuario.
Como ya vimos esto nos creaa la estructurra de carpetaas del proyeccto y todos los ficheros necesarios
de un Hola
H o bsico, es decir,
Mundo d una soola pantalla donde
d se muuestra nicam
mente un meensaje fijo.

Lo priimero que vaamos a haceer es disear nuestra panntalla princippal modificanndo la que Eclipse
E nos
ha creado por defeecto. Pero dnde
d y cmmo se define cada pantallla de la apliccacin? En Android,
A el
diseoo y la lgicaa de una paantalla estann separados en dos fichheros distintoos. Por un lado,
l en el
ficheroo /res/la ayout/mai in.xml tenndremos el diseo
d puram
mente visual de la pantallla definido
como fichero XML y por otroo lado, en el fichero /sr rc/paquet tejava/Ho olaUsuari io.java,
enconttraremos el cdigo
c java que determina la lgica de la pantallla.

Vamos a modificaar en primer lugar el aspecto de la veentana principal de la applicacin aaadiendo los
controoles (views) que vemoss en la primmera capturaa de pantallaa. Para elloo, vamos a sustituir el
contennido del fich
hero main.x
xml por el siguiente:

<?xml version="
"1.0" encoding="utf-
-8"?>
<Line
earLayout
xmlns:a
android="ht
ttp://sche
emas.androi
id.com/apk
k/res/andro
oid"
a
android:ori
ientation="vertical"
"
a
android:lay
yout_width="fill_par
rent"
a
android:lay
yout_height="fill_pa
arent" >

<TextVi
iew android
d:text="@string/nomb
bre"
and
droid:layou
ut_width="fill_paren
nt"
and
droid:layou
ut_height=
="wrap_cont
tent" />

<EditTe
ext android
d:id="@+id
d/TxtNombre
e"
android:l
layout_heig
ght="wrap_
_content"
android:l
layout_widt
th="fill_p
parent" />

<Button
n android:i
id="@+id/B
BtnHola"
android:l
layout_widt
th="wrap_c
content"
android:l
layout_heig
ght="wrap_
_content"
android:t ing/hola" />
text="@stri
</LinearLayout>

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 en cada detalle porque
se ser tema de otro artculo, 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 uno tras otro y en la
orientacin que indique su propiedad android:orientation.

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 [Aprende ms sobre la clase R en el post anterior].
android:text. Texto del control. El texto de un control se puede especificar
directamente o bien utilizar alguna de las cadenas de texto definidas en los recursos del
proyecto (fichero strings.xml), en cuyo caso indicaremos su identificador precedido del
prefijo @string/.
android:layout_height y android:layout_width. Dimensiones del control
con respecto al layout que lo contiene. Esta propiedad tomar normalmente los valores
wrap_content para indicar que las dimensiones del control se ajustarn al contenido
del mismo, o bien fill_parent para indicar que el ancho o el alto del control se
ajustar al ancho o alto del layout contenedor respectivamente.

Con esto ya tenemos definida la presentacin visual de nuestra ventana principal de la aplicacin.
De igual forma definiremos la interfaz de la segunda pantalla, creando un nuevo fichero llamado
frmmensaje.xml, y aadiendo esta vez tan solo una etiqueta (TextView) para mostrar el mensaje
personalizado al usuario. Veamos cmo quedara nuestra segunda pantalla:

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


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<TextView android:id="@+id/TxtMensaje"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="$mensaje"></TextView>

</LinearLayout>

Una vez definida la interfaz de las pantallas de la aplicacin deberemos implementar 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
HolaUsuario.java. Empecemos por comentar su cdigo por defecto:

public class HolaUsuario extends Activity {


/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}

Como ya vimos en un post 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 HolaUsuario que extiende a Activity. El nico mtodo
que sobreescribiremos 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.main). Con esta llamada estaremos indicando a Android que
debe establecer como interfaz grfica de esta actividad la definida en el recurso R.layout.main,
que no es ms que la que hemos especificado en el fichero /res/layout/main.xml. Una vez ms
vemos la utilidad de las diferentes constantes de recursos creadas automticamente en la clase R al
compilar el proyecto.

En principio vamos a crear una nueva actividad para la segunda pantalla de la aplicacin anloga a
sta primera, para lo que crearemos una nueva clase FrmMensaje que exienda de Activity y
que implemente el mtodo onCreate indicando que utilice la interfaz definida en
R.layout.frmmensaje.

public class FrmMensaje extends Activity {


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frmmensaje);
}
}

Como vemos, el cdigo incluido por defecto 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 HolaUsuario, 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 utilizaremos el mtodo findViewById() indicando el ID de cada
control, definidos como siempre en la clase R:

final EditText txtNombre = (EditText)findViewById(R.id.TxtNombre);


final Button btnHola = (Button)findViewById(R.id.BtnHola);

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 implementaremos el evento onClick de
dicho botn, veamos cmo:
btnHola.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent = new Intent(HolaUsuario.this, FrmMensaje.class);

Bundle bundle = new Bundle();


bundle.putString("NOMBRE", txtNombre.getText().toString());
intent.putExtras(bundle);

startActivity(intent);
}
});

Como ya indicamos en el artculo 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, pero en nuestro caso particular vamos a utilizar el intent para llamar
a una actividad desde otra de la misma aplicacin, para lo que pasaremos al constructor una
referencia a la propia actividad llamadora (HolaUsuario.this), y la clase de la actividad
llamada (FrmMensaje.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, concretamente el nombre que
introduzca el usuario en el cuadro de texto. 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 las actividades.
En nuestro caso slo aadiremos un dato de tipo String mediante el mtodo
putString(clave, valor). Tras esto aadiremos la informacin al intent mediante el mtodo
putExtras(bundle).

Finalizada la actividad principal de la aplicacin pasamos 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).

public class FrmMensaje extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frmmensaje);

TextView txtMensaje = (TextView)findViewById(R.id.TxtMensaje);


Bundle bundle = getIntent().getExtras();

txtMensaje.setText("Hola " + bundle.getString("NOMBRE"));


}
}

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 indicamos en uno de los artculos
anteriores, cualquier aplicacin Android utiliza un fichero especial en formato XML
(AndroidManifest.xml) para definir, entre otras cosas, los diferentes elementos que la componen.
Por tanto, todas las actividades de nuestra aplicacin deben quedar convenientemente recogidas en
este fichero. La actividad principal ya debe aparecer puesto que se cre de forma automtica al
crear el nuevo proyecto Android, por lo que debemos aadir tan slo la segunda. Para este ejemplo
nos limitaremos a incluir la actividad en el XML, ms adelante veremos que opciones adicionales
podemos especificar.

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


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sgoliver"
android:versionCode="1"
android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">


<activity android:name=".HolaUsuario"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".FrmMensaje"></activity>
</application>

<uses-sdk android:minSdkVersion="4" />

</manifest>

Una vez llegado aqu, si todo ha ido bien, deberamos poder ejecutar el proyecto sin errores y
probar nuestra aplicacin en el emulador.

Descarga el cdigo fuente de este artculo.

Espero que esta aplicacin de ejemplo os haya servido 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, o la forma de comunicar diferentes actividades
de Android. En los artculos siguientes veremos algunos de estos temas de forma ms especfica y
ampliaremos con algunos temas ms avanzados.
Interfaz de usuario en Android: Layouts
Por sgoliver on 17/08/2010 en 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.

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
fill_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).

Ejemplo:

1
<FrameLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="fill_parent">
5
6 <EditText android:id="@+id/TxtNombre"
7 android:layout_width="fill_parent"
android:layout_height="fill_parent" />
8
9 </FrameLayout>
10

LinearLayout

El siguiente layout Android en cuanto a nivel de complejidad es el LinearLayout. Este layout


apila uno tras otro todos sus elementos hijos de forma horizontal o vertical segn se establezca su
propiedad android:orientation.

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. Pero en el caso de un LinearLayout, tendremos
otro parmetro con el que jugar, la propiedad android:layout_weight.

1
2 <LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:orientation="vertical">
6
7 <EditText android:id="@+id/TxtNombre"
8 android:layout_width="fill_parent"
android:layout_height="fill_parent" />
9
10
<Button android:id="@+id/BtnAceptar"
11 android:layout_width="wrap_content"
12 android:layout_height="fill_parent" />
13
14</LinearLayout>
15

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
2 <LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="fill_parent"
android:layout_height="fill_parent"
5 android:orientation="vertical">
6
7 <EditText android:id="@+id/TxtDato1"
8 android:layout_width="fill_parent"
9 android:layout_height="fill_parent"
10 android:layout_weight="1" />
11
<EditText android:id="@+id/TxtDato2"
12 android:layout_width="fill_parent"
13 android:layout_height="fill_parent"
14 android:layout_weight="2" />
15
16</LinearLayout>
17

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 un TableColumn) 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 TableRowinsertados, 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.

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 <TableLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
android:layout_height="fill_parent"
4 android:stretchColumns="1" >
5
6 <TableRow>
7 <TextView android:text="Celda 1.1" />
8 <TextView android:text="Celda 1.2" />
9 </TableRow>
10
11 <TableRow>
12 <TextView android:text="Celda 2.1" />
<TextView android:text="Celda 2.2" />
13 </TableRow>
14
15 <TableRow>
16 <TextView android:text="Celda 3"
17 android:layout_span="2" />
</TableRow>
18
19</TableLayout>
20
21
22

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 colocarse debajo del elemento Y y alineado a la derecha del layout padre.
Veamos esto en el ejemplo siguiente:

1
2 <RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android
android:layout_width="fill_parent"
4
android:layout_height="fill_parent" >
5
6 <EditText android:id="@+id/TxtNombre"
7 android:layout_width="fill_parent"
8 android:layout_height="wrap_content" />
9
10 <Button android:id="@+id/BtnAceptar"
android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 android:layout_below="@id/TxtNombre"
13 android:layout_alignParentRight="true" />
14
15</RelativeLayout>
16

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), adems de dejar un margen a su
izquierda de 10 pixeles (android:layout_marginLeft=10px).

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.

Opciones de margen (tambin disponibles para el resto de layouts):

android:layout_margin.
android:layout_marginBottom.
android:layout_marginTop.
android:layout_marginLeft.
android:layout_marginRight.

Opciones de espaciado o padding (tambin disponibles para el resto de layouts):

android:padding.
android:paddingBottom.
android:paddingTop.
android:paddingLeft.
android:paddingRight.

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 en pestaas
(TabHost/TabWidget).
Interfaz de usuario en Android: Controles
bsicos (I)
Por sgoliver on 19/08/2010 en Android, Programacin

En el artculo anterior del Curso de Programacin en Android ya 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 artculos dedicados a Android 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: el clsico
(Button), el de tipo on/off (ToggleButton), y el que puede contener una imagen
(ImageButton). En la imagen siguiente vemos el aspecto por defecto de estos tres controles.

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]

Un control de tipo Button es el botn ms bsico que podemos utilizar. En el ejemplo siguiente
definimos un botn con el texto Plsame asignando su propiedad android: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.

<Button android:id="@+id/BtnBoton1"
android:text="Plsame"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

Control ToggleButton [API]

Un control de tipo ToggleButton es un tipo de botn que puede permanecer en dos estados,
pulsado/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.
<ToggleButton android:id="@+id/BtnBoton2"
android:textOn="ON"
android:textOff="OFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

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 la carpeta /res/drawable. As,
por ejemplo, en nuestro caso hemos incluido una imagen llamada ok.png por lo que haremos
referencia al recurso @drawable/ok.

<ImageButton android:id="@+id/BtnBoton3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ok" />

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

final Button btnBoton1 = (Button)findViewById(R.id.BtnBoton1);

btnBoton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0)
{
lblMensaje.setText("Botn 1 pulsado!");
}
});

En el caso de un botn de tipo ToggleButton 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.

final ToggleButton btnBoton2 = (ToggleButton)findViewById(R.id.BtnBoton2);

btnBoton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0)
{
if(btnBoton2.isChecked())
lblMensaje.setText("Botn 2: ON");
else
lblMensaje.setText("Botn 2: OFF");
}
});

Personalizar el aspecto un botn [y otros controles]

En la imagen anterior vimos el aspecto que presentan por defecto los tres 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 propiedad
android: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 selector se define mediante un fichero XML localizado en 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 plano a un botn ToggleButton, podramos disear
las imgenes necesarias para los estados pulsado (en el ejemplo toggle_on.png) y no pulsado
(en el ejemplo toggle_off.png) y crear un selector como el siguiente:

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


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

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:

<ToggleButton android:id="@+id/BtnBoton4"
android:textOn="ON"
android:textOff="OFF"
android:padding="10dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/toggle_style"/>

En la siguiente imagen vemos el aspecto por defecto de un ToggleButton y cmo ha quedado


nuestro ToggleButton personalizado.
Podis descargar el cdigo fuente de este artculo pulsando aqu: android-botones.

Interfaz de usuario en Android: Controles


bsicos (II)
Por sgoliver on 26/08/2010 en 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 es


android: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
ejemplo android: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 imagen, android:maxWidth y android:maxHeight.

<ImageView android:id="@+id/ImgFoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon" />

En la lgica de la aplicacin, podramos establecer la imagen mediante el mtodo


setImageResorce(), pasndole el ID del recurso a utilizar como contenido de la imagen.

ImageView img= (ImageView)findViewById(R.id.ImgFoto);


img.setImageResource(R.drawable.icon);

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, ).

<TextView android:id="@+id/LblEtiqueta"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Escribe algo:"
android:background="#AA44FF"
android:typeface="monospace" />

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().

final TextView lblEtiqueta = (TextView)findViewById(R.id.LblEtiqueta);


String texto = lblEtiqueta.getText().toString();
texto += "123";
lblEtiqueta.setText(texto);

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, atributo android:text.

<EditText android:id="@+id/TxtTexto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/LblEtiqueta" />

De igual forma, desde nuestro cdigo podremos recuperar y establecer este texto mediante los
mtodos getText() y setText(nuevoTexto) respectivamente:

final EditText txtTexto = (EditText)findViewById(R.id.TxtTexto);


String texto = txtTexto.getText().toString();
txtTexto.setText("Hola mundo!");

Un detalle que puede haber pasado desapercibido. Os habis fijado en que hemos tenido que hacer
un toString() sobre el resultado de getText()? La explicacin para esto es que el mtodo
getText() no devuelve un 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 control EditText,
y es que no slo nos permite editar texto plano sino tambin texto enriquecido o con formato.
Veamos cmo y qu opciones tenemos, 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 (deriva de la interfaz
CharSequence) 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:

//Creamos un nuevo objeto de tipo Editable


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

//Marcamos cono fuente negrita la palabra "simulacro"


str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 11, 19,
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 Span a 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
controles TextView y EditText nos van a permitir hacer esto. Vemos qu ocurre si asignamos al
nuestro control EditText el objeto Editable que hemos creado antes:

txtTexto.setText(str);

Tras ejecutar este cdigo veremos como efectivamente en el cuadro de texto aparece el mensaje con
el formato esperado:
Ya hemos visto c mo asignarr texto con y sin formatto a un cuaddro de texto,, pero qu ocurre a la
hora de
d recuperarr texto con formato desde el contrrol?. Ya vim mos que la funcin ge etText()
devuellve un objetto de tipo Ed ditable y que sobre ste podam mos hacer unn toStrin ng(). Pero
con essta solucin estamos
e perddiendo todo el formato del
d texto, por lo que no podramos
p p ejemplo
por
salvarllo a una basee de datos.

La sollucin a estto ltimo paasa obviameente por recuuperar direcctamente el objeto Edi itable y
serializarlo de alg
gn modo, mejor
m an sii es en un formato
f estaandar. Pues bien, en Anndroid este
trabajoo ya nos vien
ne hecho de fbrica a traavs de la clase Html [A API], que dispone de mtodos para
converrtir cualquieer objeto Sp
panned enn su representacin HT TML equivaalente. Veam mos cmo.
Recupperemos el
e texto de la ventana anterior y utiliccemos el mtodo
Html. .toHtml(S Spannable) para connvertirlo a foormato HTM ML:

//Obt
tiene el te
exto del control con
n etiquetas de forma
ato HTML
Strin
ng aux2 = Html.toHtm
H ml(txtTexto
o.getText());

Hacienndo esto, ob
btendramos una cadena de texto commo la siguiennte, que ya podramos
p p ejemplo
por
almaceenar en unaa base de datos
d o pubblicar en cuualquier webb sin perderr el formatoo de texto
estableecido:

<p>Esto es un <b>simulac
< ro</b>.</p
p>

La opperacin con
ntraria tambbin es possible, es decir, cargar un cuadro de texto de d Android
(EdittText) o una
u etiqueta (TextView w) a partir de
d un fragm
mento de texxto en formaato HTML.
Para ello
e podemoss utilizar el mtodo
m Html.fromHtml(Strin ng) de la sigguiente formaa:

//Asigna texto con formato HTML


txtTe
exto.setTex
xt(
Html.fromH
Html("<p>Esto es un <b>simulacro</b>.</
/p>"),
e.SPANNABLE);
BufferType
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(). S se soporta la inclusin de imgenes, aunque
esto lo dejamos para un artculo aparte (prometido!) ya que requiere algo ms de explicacin.

Podis descargar parte del cdigo de este artculo desde este enlace.

Interfaz de usuario en Android: Controles


bsicos (III)
Por sgoliver on 27/08/2010 en Android, Programacin

Tras hablar de varios de los controles indispensables en cualquier aplicacin Android, como son los
botones 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:

<CheckBox android:id="@+id/ChkMarcame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mrcame!" />

En cuanto a la personalizacin del control podemos decir que ste extiende [indirectamente] del
control TextView, por lo que todas las opciones de formato ya comentadas en artculos anteriores
son vlidas tambin para este control.

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.

if (checkBox.isChecked()) {
checkBox.setChecked(false);
}
En cuanto a los posibles eventos que puede lanzar este control, el ms interesante es sin duda el que
informa de que ha cambiado el estado del control, que recibe el nombre de onCheckedChanged.
Para implementar las acciones de este evento podramos utilizar por tanto la siguiente lgica:

final CheckBox cb = (CheckBox)findViewById(R.id.chkMarcame);

cb.setOnCheckedChangeListener(
new CheckBox.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
cb.setText("Checkbox marcado!");
}
else {
cb.setText("Checkbox desmarcado!");
}
}
});

Control RadioButton [API]

Al igual que los controles checkbox, un radiobutton 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 ellas se desmarcar automticamente la
que estuviera activa anteriormente. En Android, un grupo de botones radiobutton 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:

<RadioGroup android:id="@+id/gruporb"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<RadioButton android:id="@+id/radio1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Opcin 1" />
<RadioButton android:id="@+id/radio2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Opcin 2" />
</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 objetos RadioButton necesarios indicando su ID mediante la propiedad android:id y
su texto mediante android: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
opcionnes, y getC
CheckedRa adioButto onId() quee como su noombre indicaa devolver el ID de la
opcinn marcada (o
o el valor -1 si no hay ninnguna marcaada). Veamoos un ejempllo:

final RadioGrou
up rg = (R
RadioGroup)
)findViewById(R.id.g
gruporb);
rg.clearCheck()
);
rg.ch
heck(R.id.r
radio1);
int idSeleccion
nado = rg.getChecked
dRadioButtonId();

En cuaanto a los ev
ventos lanzados, al iguall que en el caso
c de los checkboxes, el ms impoortante ser
el quee informa ded los cambbios en el elemento seleccionado
s o, llamado tambin
t en este caso
onChe eckedChan nge. Vemos cmo trataar este eventoo del objeto RadioGro oup:

final RadioGrou
up rg = (R
RadioGroup)
)findViewById(R.id.g
gruporb);

rg.se
etOnChecked
dChangeListener(
n
new roup.OnCheckedChange
RadioGr eListener() {
public void onCheckedChang
ged(RadioGroup group
p, int chec
ckedId) {
lbl
lMensaje.setText("ID
D opcion se
eleccionad
da: " + che
eckedid);
}
});

Veamoos finalmen
nte una imaggen del aspeecto de estoos dos nuevoos tipos de controles bsicos
b que
hemoss comentado
o en este artcculo:

Podiss descargar el
e cdigo de este artculoo desde este enlace.

Intterfaz de ussuarioo en Androi


A id: Coontrolees de
seleeccin
n (I)
Por sggoliver on 07
7/09/2010 enn Android, Programacinn
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 listas desplegables (Spinner), listas fijas (ListView), tablas (GridView) y
otros controles especficos de la plataforma como por ejemplo las galeras de imgenes
(Gallery).

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 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 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. Ms
adelante aprenderemos a utilizar el resto de adaptadores en contextos ms especficos.

Veamos cmo crear un adaptador de tipo ArrayAdapter para trabajar con un array genrico de java:

1final String[] datos =


new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};
2
3ArrayAdapter<String> adaptador =
4 new ArrayAdapter<String>(this,
5 android.R.layout.simple_spinner_item, datos);
6

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 de nuestro proyecto con
cualquier estructura y conjunto de controles, ms adelante veremos cmo.
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.

Control Spinner [API]

Las listas desplegables en Android se llaman Spinner. Funcionan de forma similar al de 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<Spinner android:id="@+id/CmbOpciones"
2 android:layout_width="fill_parent"
3 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:

1final Spinner cmbOpciones = (Spinner)findViewById(R.id.CmbOpciones);


2
3adaptador.setDropDownViewResource(
4 android.R.layout.simple_spinner_dropdown_item);
5
6cmbOpciones.setAdapter(adaptador);
Comennzamos com mo siempre por
p obtener una
u referenccia al control a travs dee su ID. Y enn la ltima
lnea asignamos
a el adaptador al control mediante
m el mtodo
m setAAdapter() ). Y la seggunda lnea
para qu
q es? Cuan ndo indicammos en el apartado anterrior cmo coonstruir un adaptador
a viimos cmo
uno dee los parmeetros que le pasbamos era el ID del layout quue utilizarammos para vissualizar los
elemenntos del conntrol. Sin embargo, en el caso deel control Sp pinner, esste layout tan slo se
aplicarr al elemen
nto seleccioonado en la lista, es deecir, al que se muestra directamentte sobre el
propioo control cuando no estt desplegaddo. Sin embargo, antes indicamos que q el funciionamiento
normaal del contro
ol Spinner r incluye enntre otras coosas mostrar una lista em
mergente conn todas las
opcionnes disponib
bles. Pues biien, para personalizar taambin el asspecto de caada elementto en dicha
lista em
mergente ten
nemos el mtodo setDr ropDownVi iewResour rce(ID_layyout), al quue podemos
pasar otro ID de layout distiinto al primmero sobre ele que se moostrarn los elementos de la lista
emerggente. En esste caso heemos utilizaado otro layyout predefiinido an Anndroid paraa las listas
despleegables (anddroid.R.l layout.si imple_spi inner_dro opdown_it tem).

Con estas
e simpless lineas de cdigo
c conseguiremos mostrar
m un control
c comoo el que vem
mos en las
siguienntes imgenes:

Como se puede observar


o en las imgenees, la repressentacin deel elemento seleccionaddo (primera
imagen) y el de las opciones disponibless (segunda im magen) es distinto,
d inclluyendo el segundo
s de
ellos incluso
i alg
n elemento grfico a laa derecha para
p mostrar el estado de
d cada opcin. Como
hemoss comentado o, esto es debido
d a la utilizacin de dos layyouts diferenntes para unno y otros
elemenntos.

En cuanto a los eventos


e lanzados por el control Spi inner, el ms
m comunm mente utilizaado ser el
generaado al selecccionarse unaa opcin dee la lista dessplegable, onItemSelected. Parra capturar
este evento
e se proceder dee forma sim milar a lo ya y visto para otros coontroles anteeriormente,
asignaadole su conttrolador meddiante el mttodo setOn nItemSele ectedList tener():

1 cmb
bOpciones.ssetOnItemSelectedLisstener(
2 new AdapterView
A w.OnItemSelectedList
tener() {
3 public void onItemSelected(AdapterView<?> parent,
4 android.view.View v, int position, long id) {
lblMensaje.setText("Seleccionado: " + datos[position]);
5 }
6
7 public void onNothingSelected(AdapterView<?> parent) {
8 lblMensaje.setText("");
9 }
});
10
11

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

Podis descargar el cdigo fuente de este artculo pulsando aqu.

En el siguiente artculo describiremos el uso de controles de tipo lista (ListView).

Interfaz de usuario en Android: Controles de


seleccin (II)
Por sgoliver on 07/09/2010 en 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="wrap_content"
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
fin
nal String[] datos =
2 new String[]{"Elem11","Elem2","Elem3",""Elem4","Ellem5"};
3
4 ArrrayAdapter<<String> adaptador =
5 new Array
yAdapter<Sttring>(this,
6 androoid.R.layo ut.simple_
_list_item_1, datos);
7
ListView lstOOpciones = (ListVieww)findViewById(R.id..LstOpcione
es);
8
9 lsttOpciones.ssetAdapter(adaptadorr);
10

En estte caso, parra mostrar loos datos de cada elemeento hemos utilizado ottro layout genrico
g de
Androoid para
p los controles de tipo L
ListView
(andrroid.R.la ayout.sim mple_list t_item_1)), formado nicamentee por un TextView
T
con unas
u dimensiones determminadas. Laa lista creadda quedara como se muestra
m en la imagen
siguiennte:

Como podis com mprobar el usou bsico del


d control ListView
L es complettamente anlogo al ya
comenntado para ell control Spi
inner.

Si quisiramos reaalizar cualquuier accin al


a pulsarse sobre un elem
mento de la lista creada tendremos
que im
mplementar el
e evento on nItemClic ck. Veamos cmo con unn ejemplo:

1lstO Opciones.se
etOnItemClickListene er(new OnIt
temClickLis
stener() {
2 @Override
3 public void onItemClick(Adapte erView<?> a, View v,, int posit
tion, long id) {
4 //Acci
iones nece sarias al hacer cli ck
5 }
});
6
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
2 package net.sgoliver;
3
4 public class Titular
5 {
6 private String titulo;
private String subtitulo;
7
8 public Titular(String tit, String sub){
9 titulo = tit;
10 subtitulo = sub;
11 }
12
13 public String getTitulo(){
return titulo;
14 }
15
16 public String getSubtitulo(){
17 return subtitulo;
18 }
}
19
20

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 <?xml version="1.0" encoding="utf-8"?>


2
<LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="wrap_content"
5 android:layout_height="wrap_content"
6 android:orientation="vertical">
7
<TextView android:id="@+id/LblTitulo"
8 android:layout_width="fill_parent"
9 android:layout_height="wrap_content"
10 android:textStyle="bold"
11 android:textSize="20px" />
12
<TextView android:id="@+id/LblSubTitulo"
13 android:layout_width="fill_parent"
14 android:layout_height="wrap_content"
15 android:textStyle="normal"
16 android:textSize="12px" />
17
</LinearLayout>
18
19
20
21

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
2 class AdaptadorTitulares extends ArrayAdapter {
3
4 Activity context;
5
AdaptadorTitulares(Activity context) {
6 super(context, R.layout.listitem_titular, datos);
7 this.context = context;
8 }
9
10 public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
11 View item = inflater.inflate(R.layout.listitem_titular, null);
12
13 TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
14 lblTitulo.setText(datos[position].getTitulo());
15
16 TextView lblSubtitulo =
17(TextView)item.findViewById(R.id.LblSubTitulo);
lblSubtitulo.setText(datos[position].getSubtitulo());
18
19 return(item);
20 }
21}
22

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


adaptador, al que slo pasaremos el contexto (que ser la actividad desde la que se crea el
adaptador). En este constructor tan slo guardaremos el contexto para nuestro uso posterior y
llamaremos al constructor padre tal como ya vimos al principio de este artculo, pasndole el ID del
layout que queremos utilizar (en nuestro caso el nuevo que hemos creado, listitem_titular) y el
array que contiene los datos a mostrar.
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 objeto LayoutInflater 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
2 private Titular[] datos =
3 new Titular[]{
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 new Titular("Ttulo 5", "Subttulo largo 5")};
8
9 //...
//...
10
11AdaptadorTitulares adaptador =
12 new AdaptadorTitulares(this);
13
14ListView lstOpciones = (ListView)findViewById(R.id.LstOpciones);
15
16lstOpciones.setAdapter(adaptador);
17

Hecho esto, y si todo ha ido bien, nuestra nueva lista debera quedar como vemos en la imagen
siguiente:
Podiss descargar el
e cdigo de este artculoo desde este enlace.

Aunquue ya sabem mos utilizar y personalizzar las listass en Androidd, en el prxximo artcullo daremos
algunaas indicacionnes para utiliizar de una forma
f muchho ms eficieente los conttroles de estee tipo, algo
que loos usuarios ded nuestra aplicacin
a a
agradecern enormementte al mejoraarse la respuuesta de la
aplicaccin y reduccirse el consuumo de baterra.

Intterfaz de ussuarioo en Androi


A id: Coontrolees de
seleeccin
n (III)
Por sggoliver on 10
0/09/2010 enn Android, Programacinn

En el artculo anterior ya vimos cmo utilizar los controles de d tipo ListV View en Anndroid. Sin
embarrgo, acabamo os comentanndo que exista una form ma ms eficieente de haceer uso de dicho control,
de form
ma que la reespuesta de nuestra
n apliccacin fuera ms agil y se
s reduciese el consumo de batera,
algo que
q en platafoformas mvilles siempre es e importantte.

Como base para este artcullo vamos a utilizar com mo cdigo que ya escrribimos en el artculo
anterioor, por lo qu
ue si has lleggado hasta aqqu directam
mente te recom
miendo que leas primeroo el primer
post dedicado al control ListV View.

Cuanddo comentam mos cmo crrear nuestro propio adapptador, extenndiendo de AArrayAdap pter, para
personnalizar la forma en que nuestros daatos se ibann a mostrar ene la lista escribimos
e e siguiente
el
cdigoo:

1 cla
ass Adaptad
dorTitulare
Activity context;
es extends ArrayAdapt
ter {
2
3 AdaptadorrTitulares(Activity context) {
4 super(context, R.layout.listitem_titular, datos);
5 this.context = context;
}
6
7 public View getView(int position, View convertView, ViewGroup parent) {
8 LayoutInflater inflater = context.getLayoutInflater();
9 View item = inflater.inflate(R.layout.listitem_titular, null);
10
11 TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
lblTitulo.setText(datos[position].getTitulo());
12
13 TextView lblSubtitulo =
14(TextView)item.findViewById(R.id.LblSubTitulo);
15 lblSubtitulo.setText(datos[position].getSubtitulo());
16
17 return(item);
18 }
}
19
20
21

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 mtodo getView(). De esta forma, en los casos en que ste no 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 View item = convertView;
3
4 if(item == null)
5 {
6 Layou
utInflater inflater = context.getLayout
tInflater()
);
item = inflater.inflate(
(R.layout.listitem_t
titular, nu
ull);
7 }
8
9 TextView lblTitulo = (TextVi
iew)item.findViewByI
Id(R.id.Lbl
lTitulo);
10 lblTitulo
o.setText(datos[posi
ition].getTitulo());
;
11
12 TextView lblSubtitulo = (Tex
xtView)item.findView
wById(R.id.
.LblSubTit
tulo);
tulo.setText(datos[p
lblSubtit position].getSubtitu
ulo());
13
14 return(it
tem);
15}
16
17
18

Si aaadimos ms elementos a la lista y ejjecutamos ahora


a la apliccacin podemos comproobar que al
hacer scroll sobree la lista todo sigue fuuncionando conc normaliidad, con laa diferencia de que le
estamoos ahorrandoo gran cantiddad de trabajjo a la CPU.

Pero vamos
v n poco ms all. Con la optimizacin que acabaamos de impplementar coonseguimos
a ir un
ahorraarnos el trabajo de inflarr el layout definido
d cadaa vez que se muestra unn nuevo elem
mento. Pero
an haay otras dos llamadas reelativamentee costosas quue se siguenn ejecutando en todas lass llamadas.
Me reffiero a la ob
btencin de la referencia a cada uno de los objetoos a modificcar mediantee el mtodo
findV ViewById( (). La bsqqueda por ID D de un control determinnado dentro del rbol de objetos de
un layyout tambin puede ser una tareea costosa dependiendoo de la coomplejidad del d propio
layoutt.Por qu non aprovechhamos que estamos gguardando un layout anterior parra guardar
tambin la refereencia a los controles quue lo formaan de formaa que no teengamos quee volver a
buscarrlos? Pues eso
e es exacttamente lo que q vamos a hacer mediante lo quue en los ejjemplos de
Androoid llaman un n ViewHol lder. La claase ViewHo older tan slos va a conntener 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:

1static class ViewHolder {


2 TextView titulo;
3 TextView subtitulo;
4}

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
2
3 public View getView(int position, View convertView, ViewGroup parent)
{
4 View item = convertView;
5 ViewHolder holder;
6
7 if(item == null)
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 else
17 {
18 holder = (ViewHolder)item.getTag();
19 }
20
21 holder.titulo.setText(datos[position].getTitulo());
holder.subtitulo.setText(datos[position].getSubtitulo());
22
23 return(item);
24}
25
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.
Podis descargar el cdigo fuente de este artculo desde este enlace.

Interfaz de usuario en Android: Controles de


seleccin (IV)
Por sgoliver on 11/09/2010 en 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="fill_parent"
3 android:layout_height="fill_parent"
4 android:numColumns="auto_fit"
5 android:columnWidth="80px"
6 android:horizontalSpacing="5px"
android:verticalSpacing="10px"
7 android:stretchMode="columnWidth" />
8

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 tipo ArrayAdapter 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 Striing[25];
2 //...
3 for
r(int i=1; i<=25; i+++)
datoss[i-1] = "Dato " + i;
4
5 Arr
rayAdapter<<String> adaptador =
6 new ArrayAdapte
A er<String>(this, and
droid.R.lay
yout.simpl
le_list_ite
em_1,
7 dat
tos);
8
9 fin
nal GridView grdOpcioones = (GridView)fin
ndViewById(
(R.id.Grid
dOpciones);
;
10
11grd
dOpciones.ssetAdapter(adaptadorr);

Por deefecto, los daatos del arrayy se aadirnn al control GridView


G ordenados por filas, y por
p
supuessto, si no cabben todos enn la pantalla se podr haccer scroll sobbre la tabla. Vemos en una
u imagen
cmo queda nuesttra aplicacinn de prueba:

En cuaanto a los ev
ventos disponnibles, el ms interesante vuelve a seer el lanzadoo al seleccionnarse una
celda determinada
d a de la tabla: onItemSe elected. EsteE evento podemos
p cappturarlo de laa misma
forma que hacam mos con los coontroles Spi inner y ListView. Veamos V un ejemplo de cmo
c
hacerlo:

1 grd
dOpciones.s
setOnItemSelectedLis stener(
new AdapterView.OnIItemSelectedListenerr() {
2 public
p void
d onItemSel
lected(AdapterView<??> parent,
3 android.view.Vieew v, int position,
p l
long id) {
4 lblMensaje.ssetText("Seleccionaddo: " + dattos[positio
on]);
5 }
6
7 public
p void
d onNothing
gSelected(AdapterVieew<?> paren nt) {
lblMensaje.setTeext("");
8 }
9 });
10
11

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 artculos anteriores, 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.

Y con esto finalizamos todo lo que tena previsto contar sobre los distintos controles disponibles
de serie en Android para construir nuestras interfaces de usuario. Existen muchos ms, y es
posible que los comentemos ms adelante en algn otro artculo, pero por el momento nos vamos a
conformar con los ya vistos. En el prximo artculo, y para terminar con la serie dedicada a los
controles Android, veremos las distintas formas de crear controles de usuario personalizados.

Podis descargar el cdigo fuente de este artculo desde este enlace.

Interfaz de usuario en Android: Controles


personalizados (I)
Por sgoliver on 16/09/2010 en 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 medida de nuestros requisitos.

Android admite por supuesto crear controles personalizados, y permite hacerlo de diferentes
formas:

1. Extendiendo la funcionalidad de un control ya existente.


2. Combinando varios controles para formar un 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. Intentaramos emular
algo as como el editor de mensajes SMS del propio sistema operativo, que nos avisa del nmero de
carateres que contiene el mensaje. En nuestro caso, como resultado obtendremos un control como el
que se muestra en la siguiente imagen:
Como vemos, en la l esquina suuperior dereccha del cuaddro de texto vamos
v a mosstrar el nmeero de
caracteeres del men
nsaje de texto introduciddo, que ira acctualizndose a medida que
q modificaamos el
texto.

Para empezar,
e vam
mos a crear una
u nueva cllase java quee extienda deel control quue queremos utilizar
como base, en estee caso Edit
tText.

1public class Ex
xtendedEditText exte
ends EditTe
ext
2{
3 //...
4 }

Tras esto,
e sobreesccribiremos siempre
s los tres
t construcctores heredaados, donde por
p el momeento nos
limitarremos a llam
mar al mismoo constructorr de la clase padre.

1
2 pubblic Extend
dedEditTextt(Context context, A
AttributeSe
et attrs, int defSty
yle){
super(conntext, attrs,defStylle);
3 }
4
5 pubblic Extend
dedEditTextt(Context context, AttributeSe
A et attrs) {
6 super(conntext, attrs);
7 }
8
pub
blic Extend
dedEditTextt(Context context) {
9 super(conntext);
10}
11

Por lttimo el paso


o ms importtante. Dado queq queremoos modificarr el aspecto deld control para
p aadir
el conttador de caraacteres tendrremos que soobreescribir el evento onnDraw(), queq es llamaado por
Androoid cada vez que hay quee redibujar ell control en pantalla.
p Estte mtodo reecibe como parmetro
p
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 ya que se ser
tema de otro artculo, 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.

1Paint p1 = new Paint(Paint.ANTI_ALIAS_FLAG);


2p1.setColor(Color.BLACK);
3p1.setStyle(Style.FILL);
4
5Paint p2 = new Paint(Paint.ANTI_ALIAS_FLAG);
p2.setColor(Color.WHITE);
6

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.

1
2 @Override
3 public void onDraw(Canvas canvas)
{
4 //Llamamos al mtodo de la clase base (EditText)
5 super.onDraw(canvas);
6
7 //Dibujamos el fondo negro del contador
8 canvas.drawRect(this.getWidth()-30, 5,
9 this.getWidth()-5, 20, p1);
10
//Dibujamos el nmero de caracteres sobre el contador
11 canvas.drawText("" + this.getText().toString().length(),
12 this.getWidth()-28, 17, p2);
13}
14

Como puede comprobarse, a estos mtodos se les pasar como parmetro las coordenadas del
elemento a dibujar relativas al espacio ocupado por el control y el pincel a utilizar en cada caso.
Hechoo esto, ya tennemos finalizzado nuestroo cuadro de texto
t personalizado con contador de
caracteeres. Para a
adirlo a la interfaz de nuuestra aplicaacin lo incluuiremos en el
e layout XM
ML de la
ventanna tal como haramos
h o control,, teniendo enn cuenta que deberemos hacer
conn cualquier otro
referenncia a l con
n el nombre completo
c dee la nueva claase creada (iincluido el paquete
p java)), que en
mi casso particular sera net.s sgoliver.ExtendedEditTex xt.

1<net
t.sgoliver.
.ExtendedEditText
2 android:id
d="@+id/TxtPrueba"
3 android:la
ayout_width="fill_paarent"
4 android:la
ayout_heig ht="wrap_c
content" />
>

En el siguiente
s arttculo verem
mos cmo creear un controol personalizzado utilizanddo la segundda de las
opcionnes expuestaas, es decir, combinando
c varios contrroles ya exisstentes. Commentaremos adems
a
como aadir eventtos y propieddades personnalizadas a nuestro
n contrrol y cmo hacer
h referenncia a
dichass propiedadees desde su definicin
d XM
ML.

Podiss descargar el
e cdigo de este artculoo desde este enlace.

Intterfaz de usu
uario en An
ndroid
d: Con
ntroless
perrsonallizadoss (II)
Por sggoliver on 23
3/12/2010 enn Android, Programacinn

Ya vimos cmo Android offrece tres foormas difereentes de creear controlees personalizzados para
nuestrras aplicacio
ones y dedicamos el artculo anterioor a comentaar la primerra de las possibilidades,
que coonsista en ex
xtender la fuuncionalidadd de un contrrol ya existennte.

En estte segundo artculo


a sobree el tema vam mos a centraarnos en la creacin
c de controles
c coompuestos,
es deccir, controless personalizaados construuidos a partiir de varios controles esstandar, com mbinando la
funcioonalidad de todos
t ellos enn un slo coontrol reutilizzable en otraas aplicacionnes.

Como ejemplo ilu ustrativo vam


mos a crearr un control de identificcacin (loginn) formado por varios
controoles estandarr de Androidd. La idea ess conseguir un
u control coomo el que se
s muestra la siguiente
imagen esquemtiica:
A efectos didcticos, y para no complicar ms el ejemplo, vamos a aadir tambin a la derecha del
botn Login 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 <LinearLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="wrap_content"
android:orientation="vertical"
5 android:padding="10dip">
6
7 <TextView android:id="@+id/TextView01"
8 android:layout_width="wrap_content"
9 android:layout_height="wrap_content"
android:text="Usuario:"
10 android:textStyle="bold" />
11
12 <EditText android:id="@+id/TxtUsuario"
13 android:layout_height="wrap_content"
14 android:layout_width="fill_parent" />
15
16 <TextView android:id="@+id/TextView02"
android:layout_width="wrap_content"
17 android:layout_height="wrap_content"
18 android:text="Contrasea:"
19 android:textStyle="bold" />
20
21 <EditText android:id="@+id/TxtPassword"
android:layout_height="wrap_content"
22 android:layout_width="fill_parent" />
23
24 <LinearLayout android:orientation="horizontal"
25 android:layout_width="fill_parent"
26 android:layout_height="fill_parent" >
27
28 <Button
android:layout_width="wrap_content"
29 android:layout_height="wrap_content"
30 android:id="@+id/BtnAceptar"
31 android:text="Login"
32 android:paddingLeft="20dip"
android:paddingRight="20dip" />
33
34
35 <Text
tView andro
oid:id="@+id/LblMenssaje"
36 android:la
a yout_width
h="wrap_co ntent"
android:la
a yout_heigh
ht="wrap_coontent"
37 android:pa
a ddingLeft=
="10dip"
38 android:te
a xtStyle="b
bold" />
39
40 </LinearL
Layout>
41
42</L
LinearLayou
ut>
43
44
45
46
47
48

Visuallmente, nuesstro control ya


y quedara como se obsserva en la siiguiente imaagen:

A conntinuacin crearemos
c suu clase javaa asociada donde definniremos todda la funcionnalidad de
nuestrro control. Dado
D que noss hemos basaado en un LinearLay yout para coonstruir el coontrol, esta
nueva clase deberr heredar taambin de laa clase java LinearLa ayout de Android.
A Reddefiniremos
adems los dos coonstructores bsicos:
b

1
2 pubblic class ControlLog
C in extends
s LinearLay
yout
{
3
public ControlLogiin(Context context) {
4 super(cont
s ext);
5 inicializa
i r();
6 }
7
8 public ControlLogiin(Context context, AttributeS
Set attrs) {
super(conntext, attrs);
9 inicializzar();
10 }
11}
12

Como se puede observar,


o toddo el trabajoo lo dejamoss para el mtodo inicialiizar(). En esste mtodo
inflareemos el layo
out XML quee hemos deffinido, obtenndremos las referencias a todos los controles
c 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:

1
2 private void inicializar()
3 {
4 //Utilizamos el layout 'control_login' como interfaz del control
String infService = Context.LAYOUT_INFLATER_SERVICE;
5
LayoutInflater li =
6 (LayoutInflater)getContext().getSystemService(infService);
7 li.inflate(R.layout.control_login, this, true);
8
9 //Obtenemoslas referencias a los distintos control
10 txtUsuario = (EditText)findViewById(R.id.TxtUsuario);
txtPassword = (EditText)findViewById(R.id.TxtPassword);
11 btnLogin = (Button)findViewById(R.id.BtnAceptar);
12 lblMensaje = (TextView)findViewById(R.id.LblMensaje);
13
14 //Asociamos los eventos necesarios
15 asignarEventos();
16 }
17

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 como net.sgoliver.ControlLogin. Vamos a insertar nuestro nuevo control
en la actividad principal de la aplicacin:

1
2 <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 android:padding="10dip" >
7
8 <net.sgoliver.ControlLogin android:id="@+id/CtlLogin"
android:layout_width="fill_parent"
9 android:layout_height="wrap_content"
10 android:background="#0000AA" />
11</LinearLayout>
12

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 los
atributos layout_width, layout_height y background. Si ejecutamos ahora la aplicacin
veremos cmo ya hemos conseguido gran parte de nuestro objetivo:
Vamos a aadir ahora
a algo ms
m de funcioonalidad. Enn primer luggar, podemos aadir alggn mtodo
pblicco exclusivoo de nuestroo control. Como
C ejempplo podemos aadir unn mtodo quue permita
modifi
ficar el texto de la etiqueta de resultaado del loginn. Esto no tieene ninguna dificultad:

1public void set


tMensaje(String msg)
)
2{
3 lblMensajee.setText(msg);
4 }

En seegundo lugaar, todo conntrol que see precie debbe tener alggunos eventtos que noss permitan
responnder a las acciones del usuario
u de la aplicacin. As por ejem
mplo, los bootones tienenn un evento
OnCli ick, las lisstas un evennto OnItem mSelected d, etc. Puess bien, nosootros vamos a dotar a
nuestrro control dee un eventoo personalizaado, llamadoo OnLogin n, que se lannce cuando el usuario
introduuce sus cred
denciales de identificaci
i n y pulsa el botn Loggin.

Para ello,
e lo prim
mero que vam mos a hacerr es concrettar los detallles de dichoo evento, crreando una
interfaaz java para definir su liistener. Estaa interfaz tann slo tendr un mtodo llamado on
nLogin()
que deevolver los dos datos inntroducidos por p el usuariio (usuario y contrasea)). Vemos cmmo queda:

1pack
kage net.sg
goliver;
2
3public interface OnLoginListener
4{
5 void onLog
gin(String usuario, String pas
ssword);
}
6

A conntinuacin, deberemos
d a
aadir un nuuevo miembbro de tipo OnLoginL Listener a la clase
Contr rolLogin, y un mtoddo pblico quue permita suscribirse
s all nuevo evennto.

1 pub
blic class ControlLog
C in extends
s LinearLay
yout
2 {
3 private OnLoginLis
stener listener;
4
//...
5
6 public void
v setOnLoginListen
ner(OnLoginListener l)
7 {
8 listener = l;
9 }
}
10
11

Con esto,
e la apliccacin princcipal ya pueede suscribirrse al eventoo OnLogin n y ejecutarr su propio
cdigoo cuando stte se generee. Pero cunndo se geneera exactameente? Dijimoos antes quee queremos
lanzarr el evento OnLogin
O cuuando el usuuario pulse ell botn Loggin de nuesstro control. Pues bien,
para hacerlo,
h volvvamos al mtodo
m asiggnarEvent tos() que antes dejam mos aparcaddo. En este
mtoddo vamos a implementar
i r el evento OnClick
O deel botn de Login
L para lanzar
l el nueevo evento
OnLog gin del co ontrol. Confundido?. Inntento explicarlo de otrra forma. Vamos
V a aprrovechar el
eventoo OnClick( () del botn Login (quue es un evennto interno a nuestro conntrol, no se ver desde
fuera) para lanzar hacia el extterior el evennto OnLogi in() (que ser el que debe
d capturarr y tratar la
aplicaccin que hagga uso del coontrol).

Para ello,
e implementaremos el e evento OnClick com mo ya hemoss hecho en otras
o ocasionnes y como
accionnes generareemos el evvento OnLog gin de nuestro
n listenner pasndoole los datoos que ha
introduucido el usu
uario en los cuadros
c de teexto Usuariio y Contraasea:

1
2 private void asignarEve
a ntos()
{
3
btnLogin..setOnClickListener((new OnClic
ckListener(()
4 {
5 @Overrride
6 publiic void onClick(View v) {
7 listener.o
l nLogin(txt
tUsuario.getText().ttoString(),
,
txtPass
sword.getTeext().toSt
tring());
8 }
9 });
10}
11
Con toodo esto, la aplicacin principal
p ya puede
p impleementar el evvento OnLogin de nuesttro control,
hacienndo por ejem mplo la validaacin de las credenciales del usuarioo y modificaando convenientemente
el textto de la etiqu
ueta de resulttado:

1
2
@Ov
verride
3 pubblic void on
nCreate(Bundle saveddInstanceState)
4 {
5 super.onCCreate(sav
vedInstanceeState);
6 setContenntView(R.layout.mainn);
7
ctlLogin = (ControlLogin)fin ndViewById((R.id.CtlL
Login);
8
9
ctlLogin..setOnLoginListener((new OnLogi
inListener(()
10 {
11 @Overridee
12 public void onLogin(String ussuario, String passwword)
13 {
//Vallidamos el usuario y la contrasea
14 if (usuario.equuals("demo") && passsword.equalls("demo"))
15 ctlLogin.s
c etMensaje(
("Login correcto!");;
16 else
17 ctlLogin.s
c etMensaje(
("Vuelva a intentarllo.");
18 }
});
19}
20
21

Veamoos lo que ocurre


o al ejjecutar ahorra la aplicaacin princiipal e introdducir las crredenciales
correcctas:
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, 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>
</resources>
5

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 a ControlLogin 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);
inicializar();
3
4 // Procesamos los atributos XML personalizados
5 TypedArray a =
6 getContext().obtainStyledAttributes(attrs,
7 R.styleable.ControlLogin);
8
9 String textoBoton = a.getString(
R.styleable.ControlLogin_login_text);
10
11 btnLogin.setText(textoBoton);
12
13 a.recycle();
14}
15
16

Ya slo nos qued da utilizarlo.. Para ello debemos


d priimero declarrar un nuevvo espacio de
d nombres
(nameespace) locall para el paquuete java utiilizado, que en nuestro caso
c he llamaado sgo:

1xmln
ns:sgo="htt
tp://schem
mas.android
d.com/apk/res/net.sg
goliver"

Tras esto,
e slo quueda asignaar el valor del
d nuevo atributo
a preccedido del nuevo
n nameespace, por
ejemplo con el tex
xto Entrar:

1
2 <LinearLayout
t
3 xmlns:and
droid="http://schemaas.android.com/apk/rres/android
d"
xmlns:sgo
o="http://schemas.anndroid.com/apk/res/nnet.sgolive
er"
4
android:o
orientation="verticaal"
5 android:l
layout_wid
dth="fill_pparent"
6 android:l
layout_height="fill__parent"
7 android:p
padding="10dip" >
8
9 <net.sgol
liver.ControlLogin android:id=="@+id/CtlLLogin"
andro
oid:layout_width="fiill_parent"
10 andro
oid:layout_height="wwrap_content"
11 andro
oid:background="#00000AA"
12 sgo:l
login_text="Entrar" />
13</L
LinearLayou
ut>
14

Finalmmente, si ejeecutamos dee nuevo la aplicacin


a p
principal verremos cmoo el botn de
d login se
inicialliza con el texto definiido en el attributo logi
in_text y que todo contina fuuncionando
correcctamente.
Como resumen, en e este artculo hemos visto
v cmo construir unn control anndroid persoonalizado a
partir de otros controles
c esstandar, com
mponiendo su interfaz, aadiendoo mtodos y eventos
personnalizados, e incluso aaddiendo nuevvas opciones en tiempo de
d diseo aadiendo atrributos xml
exclussivos.

Podiss descargar el
e cdigo fueente de este artculo
a pulssando aqu.

Esperoo que os sea til y que siigis los artcculos que quuedan por veenir.

Intterfaz de usu
uario en An
ndroid
d: Con
ntroless
perrsonallizadoss (III))
Por sggoliver on 10
0/02/2011 enn Android, Programacinn

En arttculos anterriores del cuurso ya comentamos doss de las possibles vas queq tenemos para crear
controoles personallizados en Android:
A la primera de elllas extendieendo la funciionalidad de un control
ya existente, y com
mo segunda opcin creanndo un nuevvo control coompuesto porr otros ms sencillos.
s

En estte nuevo artculo vamoss a describirr la tercera de


d las posibiilidades que tenamos disponibles,
d
que coonsiste en crrear un conttrol completaamente desdde cero, sin utilizar com
mo base otros controles
existenntes. Como ejemplo, vam mos a consttruir un conttrol que nos permita seleeccionar un color entre
varios disponibless.

Los coolores dispon nibles van a ser slo cuaatro, que se mostrarn enn la franja suuperior del control.
c En
la partte inferior see mostrar ell color seleccionado en cada
c momennto, o permaanecer negroo si an no
se ha seleccionado o ningn coolor. Valga la l siguiente imagen commo muestra del d aspecto que tendr
nuestrro control de seleccin de color:
Por supuesto este control no tiene mucha utilidad prctica dada su sencillez, pero creo que puede
servir como ejemplo para comentar los pasos necesarios para construir cualquier otro control ms
complejo. Empecemos.

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 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 y tamao. 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 200*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 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
2 {
3 int ancho = calcularAncho(widthMeasureSpec);
4 int alto = calcularAlto(heightMeasureSpec);
5
6 setMeasuredDimension(ancho, alto);
7 }
8
private int calcularAlto(int limitesSpec)
9 {
10 int res = 100; //Alto por defecto
11
12 int modo = MeasureSpec.getMode(limitesSpec);
13 int limite = MeasureSpec.getSize(limitesSpec);
14
if (modo == MeasureSpec.AT_MOST) {
15 res = limite;
16 }
17 else if (modo == MeasureSpec.EXACTLY) {
18 res = limite;
}
19
20 return res;
21}
22
23private int calcularAncho(int limitesSpec)
24{
25 int res = 200; //Ancho por defecto
26
int modo = MeasureSpec.getMode(limitesSpec);
27 int limite = MeasureSpec.getSize(limitesSpec);
28
29 if (modo == MeasureSpec.AT_MOST) {
30 res = limite;
31 }
else if (modo == MeasureSpec.EXACTLY) {
32 res = limite;
33 }
34
35 return res;
36}
37
38
39
40
41
42

Como nota importante, al final del evento onMeasure() siempre debemos llamar al mtodo
setMeasuredDimension() 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. 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,
porque se ser tema central de un prximo artculo. 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:

1
2
3 @Override
4 protected void onDraw(Canvas canvas)
5 {
6 //Obtenemos las dimensiones del control
int alto = getMeasuredHeight();
7 int ancho = getMeasuredWidth();
8
9 //Colores Disponibles
10 Paint pRelleno = new Paint();
11 pRelleno.setStyle(Style.FILL);
12
pRelleno.setColor(Color.RED);
13
canvas.drawRect(0, 0, ancho/4, alto/2, pRelleno);
14
15 pRelleno.setColor(Color.GREEN);
16 canvas.drawRect(ancho/4, 0, 2*(ancho/4), alto/2, pRelleno);
17
18 pRelleno.setColor(Color.BLUE);
19 canvas.drawRect(2*(ancho/4), 0, 3*(ancho/4), alto/2, pRelleno);
20
pRelleno.setColor(Color.YELLOW);
21 canvas.drawRect(3*(ancho/4), 0, 4*(ancho/4), alto/2, pRelleno);
22
23 //Color Seleccionado
24 pRelleno.setColor(colorSeleccionado);
25 canvas.drawRect(0, alto/2, ancho, alto, pRelleno);
26
27 //Marco del control
Paint pBorde = new Paint();
28 pBorde.setStyle(Style.STROKE);
29 pBorde.setColor(Color.WHITE);
30 canvas.drawRect(0, 0, ancho-1, alto/2, pBorde);
31 canvas.drawRect(0, 0, ancho-1, alto-1, pBorde);
}
32
33
34

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 los rellenos de cada color seleccionable.
Para indicar que se trata del color de relleno a utilizar utilizaremos la llamada a
setStyle(Style.FILL). Tras esto, ya slo debemos dibujar cada uno de los cuadros en su
posicin correspondiente con drawRect(), estableciendo antes de cada uno de ellos el color
deseado con setColor(). Por ltimo, dibujamos el marco del control definiendo un nuevo objeto
Paint, esta vez con estilo Style.STROKE dado que se utilizar para dibujar slo lneas, no
rellenos.

Con esto, ya deberamos tener un control con el aspecto exacto que definimos en un principio. 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 los colores de la zona superior, y que
utilizaremos para actualizar el color de la zona inferior con el color seleccionado. 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 el color sobre el que se ha seleccionado y actualizaremos el valor del atibuto
colorSeleccionado. Finalmente, llamamos al mtodo invalidate() para refrescar la
interfaz del control, reflejando as el cambio en el color seleccionado, si se ha producido.

1
2
3 @Override
4 public boolean onTouchEvent(MotionEvent event)
5 { //Si se ha pulsado en la zona superior
6 if (event.getY() > 0 && event.getY() < (getMeasuredHeight()/2))
7 {
8 //Si se ha pulsado dentro de los lmites del control
9 if (event.getX() > 0 && event.getX() < getMeasuredWidth())
{
10 //Determinamos el color seleccionado segn el punto pulsado
11 if(event.getX() > (getMeasuredWidth()/4)*3)
12 colorSeleccionado = Color.YELLOW;
13 else if(event.getX() > (getMeasuredWidth()/4)*2)
14 colorSeleccionado = Color.BLUE;
else if(event.getX() > (getMeasuredWidth()/4)*1)
15 colorSeleccionado = Color.GREEN;
16 else
17 colorSeleccionado = Color.RED;
18
19 //Refrescamos el control
this.invalidate();
20 }
21 }
22
23 return super.onTouchEvent(event);
24}
25
26
En segundo lugar crearemos un evento externo personalizado, que lanzaremos cuando el usuario
pulse sobre la zona inferior del control, como una forma de aceptar definitivamente el color
seleccionado. Llamaremos a este evento onSelectedColor(). Para crearlo actuaremos de la
misma forma que ya vimos en el artculo anterior. Primero definiremos una interfaz para el listener
de nuestro evento:

1package net.sgoliver.android;
2
3public interface OnColorSelectedListener
4{
5 void onColorSelected(int color);
}
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 public class SelectorColor extends View
{
3 private OnColorSelectedListener listener;
4
5 //...
6
7 public void setOnColorSelectedListener(OnColorSelectedListener l)
8 {
listener = l;
9 }
10}
11

Y ya slo nos quedara lanzar el evento en el momento preciso. Esto tambin lo haremos dentro del
evento onTouch(), cuando detectemos que el usuario ha pulsado en la zona inferior de nuestro
control:

1 @Override
2 public boolean onTouchEvent(MotionEvent event)
{
3 //Si se ha pulsado en la zona superior
4 if (event.getY() > 0 && event.getY() < (getMeasuredHeight()/2))
5 {
6 //...
}
7 //Si se ha pulsado en la zona inferior
8 else if (event.getY() > (getMeasuredHeight()/2) &&
9 event.getY() < getMeasuredHeight())
10 {
11 //Lanzamos el evento externo de seleccin de color
listener.onColorSelected(colorSeleccionado);
12 }
13
14 return super.onTouchEvent(event);
15}
16
17
18

Con esto, nuestra aplicacin principal ya podra suscribirse a este nuevo evento para estar
informada cada vez que se seleccione un color. Sirva la siguiente plantilla a modo de ejemplo:

1
2
3 public
{
class ControlPersonalizado extends Activity
4 private SelectorColor ctlColor;
5
6 @Override
7 public void onCreate(Bundle savedInstanceState)
8 {
super.onCreate(savedInstanceState);
9 setContentView(R.layout.main);
10
11 ctlColor = (SelectorColor)findViewById(R.id.scColor);
12
13 ctlColor.setOnColorSelectedListener(new OnColorSelectedListener()
14 {
15 @Override
public void onColorSelected(int color)
16 {
17 //Aqu se tratara el color seleccionado (parmetro 'color'
18 //...
19 }
});
20 }
21}
22
23

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.

Podis descargar el cdigo fuente del artculo pulsando aqu.

Interfaz de usuario en Android: Tab Layout


Por sgoliver on 07/10/2011 en Android, Programacin

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 elemento TabWidget, 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.

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

1 <TabHost android:id="@android:id/tabhost"
android:layout_width="match_parent"
2 android:layout_height="match_parent">
3
4 <LinearLayout
5 android:orientation="vertical"
6 android:layout_width="fill_parent"
android:layout_height="fill_parent" >
7
8 <TabWidget android:layout_width="match_parent"
9 android:layout_height="wrap_content"
10 android:id="@android:id/tabs" />
11
12 <FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
13 android:id="@android:id/tabcontent" >
14
15 <LinearLayout android:id="@+id/tab1"
16 android:orientation="vertical"
17 android:layout_width="match_parent"
18 android:layout_height="match_parent" >
<TextView android:id="@+id/textView1"
19 android:text="Contenido Tab 1"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content" />
22 </LinearLayout>
23
<LinearLayout android:id="@+id/tab2"
24 android:orientation="vertical"
25 android:layout_width="match_parent"
26 android:layout_height="match_parent" >
27 <TextView android:id="@+id/textView2"
28 android:text="Contenido Tab 2"
android:layout_width="wrap_content"
29 android:layout_height="wrap_content" />
30 </LinearLayout>
31
32 </FrameLayout>
33 </LinearLayout>
34
35</TabHost>
36
37
38
39
40
41

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 mtodo setIndicator(texto, icono). Veamos el
cdigo completo.

1
2 Resources res = getResources();
3
4 TabHost tabs=(TabHost)findViewById(android.R.id.tabhost);
5 tabs.setup();
6
TabHost.TabSpec spec=tabs.newTabSpec("mitab1");
7 spec.setContent(R.id.tab1);
8 spec.setIndicator("TAB1",
9 res.getDrawable(android.R.drawable.ic_btn_speak_now));
10tabs.addTab(spec);
11
12spec=tabs.newTabSpec("mitab2");
spec.setContent(R.id.tab2);
13spec.setIndicator("TAB2",
14 res.getDrawable(android.R.drawable.ic_dialog_map));
15tabs.addTab(spec);
16
17tabs.setCurrentTab(0);
18

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 icono android.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 cuuanto a los eventos diisponibles del d control TabHost, aunque noo suele serr necesario
capturrarlos, podem
mos ver a modo
m de ejemmplo el ms interesante de
d ellos, OnTabChang ged, que se
lanza cada vez qu ue se cambiaa de pestaaa y que nos informa
i de la nueva pesstaa visualiizada. Este
eventoo podem
mos im
mplementarloo y asignarlo mediantte el mtodo
setOn nTabChang gedListener() de la siguiente forma:
f

1tabs.setOnTabCChangedListener(new OnTabChang geListener(


() {
2 @Override
3 public void onTabChanged(Strinng tabId) {
4 Log.i(
("AndroidT absDemo", "Pulsada pestaa: " + tabId);
;
5 }
});
6

En el mtodo on nTabChang ged() reciibimos com mo parmetroo la etiquetta identificaativa de la


pestaa (no su ID)), que debim
mos asignar cuando
c cream
mos su objetto TabSpec c corresponddiente. Para
este ejjemplo, lo nico
que haaremos al deetectar un caambio de peestaa ser escribir
e en el
e log de la
aplicaccin un mennsaje informaativo con la etiqueta de la nueva pesstaa visualiizada. As poor ejemplo,
al cam
mbiar a la seg
gunda pestaa recibiremos el mensajje de log: P
Pulsada pestaaa: mitab2.

Puedes descargar el cdigo fuente compleeto del ejempplo utilizado en este artcculo a travss de este
enlacee.

Meens en And
droid (I):
( Cooncepttos bsicos
Por sggoliver on 21
1/03/2011 enn Android, Programacinn

En loss dos siguieentes artcullos del Cursso de Prograamacin Anndroid nos vamos
v a cenntrar en la
creacin de menss de opcionees en sus difeerentes variaantes.
En Android podemos encontrar 3 tipos diferentes de mens:

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:

1
2 <?xml version="1.0" encoding="utf-8"?>
<menu
3 xmlns:android="http://schemas.android.com/apk/res/android">
4
5 <item android:id="@+id/MnuOpc1" android:title="Opcion1"
6 android:icon="@drawable/tag"></item>
7 <item android:id="@+id/MnuOpc2" android:title="Opcion2"
8 android:icon="@drawable/filter"></item>
<item android:id="@+id/MnuOpc3" android:title="Opcion3"
9 android:icon="@drawable/chart"></item>
10
11</menu>
12

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

Una vez definido el men en el fichero XML, tendremos que implementar el evento
onCreateOptionsMenu() de la actividad que queremos que lo muestre. 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
mtoddo infate( D del menu definido en XML, que en nuestro
() pasndolle como parmetro el ID
caso ser
s R.menu u.menu_pr rincipal. Por ltimoo devolveremmos el valorr true paraa confirmar
que deebe mostrarsse el men.

1
@Ove
erride
2public boolean onCreateOptionsMenuu(Menu menu
u) {
3 //Alternat
tiva 1
4 MenuInflat
ter inflater = getMeenuInflater();
5 inflater.i
inflate(R. menu.menu_
_principal, menu);
return true;
6}
7

Y ya hemos term minado, conn estos sencillos pasoss nuestra applicacin yaa debera mostrar
m sin
probleemas el men que hemoss construdo, aunque toddava nos falltara implem
mentar la funncionalidad
de cadda una de lass opciones mostradas.
m

Como hemos com mentado anntes, este mismo


m men tambin lo l podramoos crear dirrectamente
mediaante cdigo, tambin deesde el evennto onCreateOption nsMenu(). Para ello, parap aadir
cada opcin
o del men
m podemmos utilizar el
e mtodo add()
a sobree el objeto de
d tipo Mennu que nos
llega como
c parmmetro del evvento. Este mtodo
m recibbe 4 parmeetros: ID deel grupo asoociado a la
opcinn (veremos quq es esto en el siguiente artculo,, por ahora utilizaremos
u s Menu.NON NE), un ID
nico para la opciin (que declararemos coomo constanntes de la claase), el ordeen de la opcin (que no
nos innteresa por ahora,
a utilizaaremos Menu u.NONE) y el texto de la opcin. Por P otra aprtte, el icono
de cadda opcin lo o establecereemos mediannte el mtoddo setIcon() pasnddole el ID del d recurso.
Veamoos cmo qu uedara el cdigo utilizanndo esta alternativa, quue generara un men exxactamente
igual al
a del ejempllo anterior:

1 private static final intt MNU_OPC1 = 1;


2 private static final intt MNU_OPC2 = 2;
3 pri vate static final int
t MNU_OPC3 = 3;
4
5 //...
6
7 @Override
public boolean onCreateOptionsMenu(Menu menu) {
8 //Alternativa 2
9 menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
10"Opcion1").setIcon(R.drawable.tag);
11 menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
"Opcion2").setIcon(R.drawable.filter);
12 menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
13"Opcion3").setIcon(R.drawable.chart);
14 return true;
}

Construido el men, la implementacin de cada una de las opciones se incluir en el evento


onOptionsItemSelected() 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
mtodo getItemId(). 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
2 @Override
3 public boolean onOptionsItemSelected(MenuItem item) {
4 switch (item.getItemId()) {
5 case R.id.MnuOpc1:
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:
lblMensaje.setText("Opcion 3 pulsada!");;
11 return true;
12 default:
13 return super.onOptionsItemSelected(item);
14 }
15}
16

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>
< denntro del item
m corresponddiente a la opcin
o 3. De
D esta forma, el XML
quedarra ahora com
mo sigue:

1
2 <?xxml version="1.0" encoding="utf f-8"?>
3 <meenu
4 x
xmlns:androoid="http://schemas. .android.coom/apk/res
s/android">
>
5
6 <ittem android
d:id="@+id/ /MnuOpc1" android:tit
a tle="Opcio
on1"
androidd:icon="@ddrawable/taag"></item>
7
<it
tem android
d:id="@+id/ /MnuOpc2" android:tit
a tle="Opcio
on2"
8 androidd:icon="@ddrawable/fiilter"></item>
9 <ittem android
d:id="@+id/ /MnuOpc3" android:tit
a tle="Opcio
on3"
10 androidd:icon="@ddrawable/chhart">
11 <menu>
<itemm android:iid="@+id/SubMnuOpc1""
12 android:title="Opc cion 3.1" />
/
13 <itemm android:iid="@+id/SubMnuOpc2""
14 android:title="Opc cion 3.2" />
/
15 </menu>
16</item>
17
</m
menu>
18
19

Si volvemos a ejeecutar ahora el proyecto y pulsamoss la opcin 3 nos apareccer el correspondiente


submeen con las dos
d nuevas opciones
o aaadidas. Lo veemos en la siiguiente imaagen:

Comprobamos com mo efectivam


mente apareccen las dos nuevas
n opcioones en la lissta emergentte, y que el
ttulo de
d la lista see corresponde con el textto de la opcin elegida en el men prrincipal (Oppcion3).
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
2menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion1").setIcon(R.drawable.tag);
3menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion2").setIcon(R.drawable.filter);
4//menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
"Opcion3").setIcon(R.drawable.chart);
5
6SubMenu smnu = menu.addSubMenu(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion3")
7 .setIcon(R.drawable.chart);
8smnu.add(Menu.NONE, SMNU_OPC1, Menu.NONE, "Opcion 3.1");
9smnu.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.

El cdigo de este artculo podis descargarlo desde este enlace.

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


Por sgoliver on 30/03/2011 en 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.
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 del ejemplo del artculo anterior, al que vamos a
aadir en primer lugar un men contextual que aparezca al pulsar sobre la etiqueta de texto que
mostrbamos en la ventana principal de la aplicacin. 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 a registerForContextMenu():

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
8 //Asociamos los mens contextuales a los controles
registerForContextMenu(lblMensaje);
9 }
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>
<item android:id="@+id/CtxLblOpc2"
7
android:title="OpcEtiqueta2"></item>
8
9 </menu>
10

Por su parte el evento onCreateContextMenu() quedara de la siguiente forma:

1@Override
public void onCreateContextMenu(ContextMenu menu, View v,
2 ContextMenuInfo menuInfo)
3{
4 super.onCreateContextMenu(menu, v, menuInfo);
5
6 MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_ctx_etiqueta, menu);
7}
8
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
2 @Override
3 public boolean onContextItemSelected(MenuItem item) {
4
switch (item.getItemId()) {
5
case R.id.CtxLblOpc1:
6 lblMensaje.setText("Etiqueta: Opcion 1 pulsada!");
7 return true;
8 case R.id.CtxLblOpc2:
9 lblMensaje.setText("Etiqueta: Opcion 2 pulsada!");
return true;
10 default:
11 return super.onContextItemSelected(item);
12 }
13}
14

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.

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) {


super.onCreate(savedInstanceState);
2 setContentView(R.layout.main);
3
4 //Obtenemos las referencias a los controles
5 lblMensaje = (TextView)findViewById(R.id.LblMensaje);
6 lstLista = (ListView)findViewById(R.id.LstLista);
7
//Rellenamos la lista con datos de ejemplo
8 String[] datos =
9 new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};
10
11 ArrayAdapter<String> adaptador =
12 new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, datos);
13
14 lstLista.setAdapter(adaptador);
15
16 //Asociamos los mens contextuales a los controles
17 registerForContextMenu(lblMensaje);
18 registerForContextMenu(lstLista);
}
19
20
21
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"
android:title="OpcLista2"></item>
8
9 </menu>
10

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:

@Override
1 public void onCreateContextMenu(ContextMenu menu, View v,
2 ContextMenuInfo menuInfo)
3 {
4 super.onCreateContextMenu(menu, v, menuInfo);
5
MenuInflater inflater = getMenuInflater();
6
7 if(v.getId() == R.id.LblMensaje)
8 inflater.inflate(R.menu.menu_ctx_etiqueta, menu);
9 else if(v.getId() == R.id.LstLista)
10 {
AdapterView.AdapterContextMenuInfo info =
11 (AdapterView.AdapterContextMenuInfo)menuInfo;
12
13 menu.setHeaderTitle(
14 lstLista.getAdapter().getItem(info.position).toString());
15
inflater.inflate(R.menu.menu_ctx_lista, menu);
16 }
17}
18
19
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 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 evento onCreateContextMenu(), 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 parmetro menuInfo 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 de
onContextItemSelected(). 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()) {
case R.id.CtxLblOpc1:
8 lblMensaje.setText("Etiqueta: Opcion 1 pulsada!");
9 return true;
10 case R.id.CtxLblOpc2:
11 lblMensaje.setText("Etiqueta: Opcion 2 pulsada!");
return true;
12 case R.id.CtxLstOpc1:
13 lblMensaje.setText("Lista[" + info.position + "]: Opcion 1
14pulsada!");
15 return true;
16 case R.id.CtxLstOpc2:
lblMensaje.setText("Lista[" + info.position + "]: Opcion 2
17pulsada!");
18 return true;
19 default:
20 return super.onContextItemSelected(item);
}
21}
22
23

Como vemos, aqu u tambin utilizamos


u laa informacin del objetoo Adapter rContextM MenuInfo
para saber qu eleemento de laa lista se ha pulsado, conn la nica diiferencia de que en esta ocasin lo
obteneemos median nte una llam
mada al mtoddo getMenu uInfo() ded la opcin de men (Me enuItem)
recibidda como parrmetro.

Si volvemos a ejeecutar el proyecto en estte punto poddremos compprobar el asppecto de nueestro men
contexxtual al pulsaar cualquier elemento dee la lista:

A modo de resum men, en estee artculo hemos


h visto cmo crearr mens conntextuales asociados
a a
minados elem
determ mentos y controles de nuestra
n interffaz de la applicacin. Heemos visto cmo
c crear
menss bsicos y algunas parrticularidades que existeen a la horaa de asociar mens conttextuales a
elemenntos de un control de tipo lista. Para no alaargar este artculo
a dediicaremos unn tercero a
comenntar algunas opciones meenos frecuenntes, pero iguualmente tiiles, de los mens
m en Anndroid.

Como siempre, el cdigo fuennte de este arrtculo podiis descargarllo desde estee enlace.

Meens en And
droid (III):
( O
Opcio nes avvanzad
das
Por sggoliver on 06
6/10/2011 enn Android, Programacinn

En loss artculos anteriores del


d curso yaa hemos vissto cmo crrear mens bsicos parra nuestras
aplicacciones, tanto
o mens prinncipales commo de tipo coontextual. Sin embargo, se nos queddaron en el
mbin nos pueden ser neecesarios o interesantes a la hora de desarrollar
tinteroo un par de teemas que tam
una applicacin. Poor un lado veeremos los grupos
g de opciones, y poor otro la actuualizacin dinmica de
un meen segn deeterminadas condiciones
c .
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
2 <menu
3 xmlns:android="http://schemas.android.com/apk/res/android">
4
5 <item android:id="@+id/MnuOpc1" android:title="Opcion1"
6 android:icon="@drawable/tag"></item>
7 <item android:id="@+id/MnuOpc2" android:title="Opcion2"
android:icon="@drawable/filter"></item>
8 <item android:id="@+id/MnuOpc3" android:title="Opcion3"
9 android:icon="@drawable/chart">
10 <menu>
11 <group android:id="@+id/grupo1">
12
13 <item android:id="@+id/SubMnuOpc1"
android:title="Opcion 3.1" />
14 <item android:id="@+id/SubMnuOpc2"
15 android:title="Opcion 3.2" />
16
17 </group>
18 </menu>
</item>
19
20</menu>
21
22

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


2mnu.setGroupEnabled(R.id.grupo1, false);
3
4//Ocultar todo el grupo
5mnu.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 o CheckBox 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
</group>
8

Si optamos por construir el men directamente mediante cdigo debemos utilizar el mtodo
setGroupCheckable() 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:

private static final int MNU_OPC1 = 1;


1 private static final int MNU_OPC2 = 2;
2 private static final int MNU_OPC3 = 3;
3 private static final int SMNU_OPC1 = 31;
4 private static final int SMNU_OPC2 = 32;
5
6 private static final int GRUPO_MENU_1 = 101;
7
private int opcionSeleccionada = 0;
8
9 //...
10
11private void construirMenu(Menu menu)
12{
13 menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
14"Opcion1").setIcon(R.drawable.tag);
menu.add(Menu.NONE, MNU_OPC2, Menu.NONE,
15"Opcion2").setIcon(R.drawable.filter);
16
17 SubMenu smnu = menu.addSubMenu(Menu.NONE, MNU_OPC3, Menu.NONE, "Opcion3")
18 .setIcon(R.drawable.chart);
19
20 smnu.add(GRUPO_MENU_1, SMNU_OPC1, Menu.NONE, "Opcion 3.1");
smnu.add(GRUPO_MENU_1, SMNU_OPC2, Menu.NONE, "Opcion 3.2");
21
22 //Establecemos la seleccin exclusiva para el grupo de opciones
23 smnu.setGroupCheckable(GRUPO_MENU_1, true, true);
24
25 //Marcamos la opcin seleccionada actualmente
26 if(opcionSeleccionada == 1)
smnu.getItem(0).setChecked(true);
27 else if(opcionSeleccionada == 2)
28 smnu.getItem(1).setChecked(true);
29}
30
31@Override
public boolean onCreateOptionsMenu(Menu menu) {
32
33 construirMenu(menu);
34
35 return true;
36}
37
38
39
40

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 llamado
opcionSeleccionada) de nuestra actividad. Esta marcacin manual la hacemos mediante el
mtodo getItem() 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 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 atributo
opcionSeleccionada 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) {
switch (item.getItemId()) {
3
4 //...
5 //Omito el resto de opciones por simplicidad
6
7 case SMNU_OPC1:
8 opcionSeleccionada = 1;
9 item.setChecked(true);
return true;
10 case SMNU_OPC2:
11 opcionSeleccionada = 2;
12 item.setChecked(true);
13 return true;
14
15 //...
}
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 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.

1 private void construirMenu(Menu menu, boolean extendido)


{
2 menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
3 "Opcion1").setIcon(R.drawable.tag);
4 menu.add(Menu.NONE, MNU_OPC2, Menu.NONE,
5 "Opcion2").setIcon(R.drawable.filter);
6
SubMenu smnu = menu.addSubMenu(Menu.NONE, MNU_OPC3, Menu.NONE, "Opcion3")
7 .setIcon(R.drawable.chart);
8
9 smnu.add(GRUPO_MENU_1, SMNU_OPC1, Menu.NONE, "Opcion 3.1");
10 smnu.add(GRUPO_MENU_1, SMNU_OPC2, Menu.NONE, "Opcion 3.2");
11
12 //Establecemos la seleccin exclusiva para el grupo de opciones
smnu.setGroupCheckable(GRUPO_MENU_1, true, true);
13
14 if(extendido)
15 menu.add(Menu.NONE, MNU_OPC4, Menu.NONE,
16"Opcion4").setIcon(R.drawable.tag);
17
18 //Marcamos la opcin seleccionada actualmente
19 if(opcionSeleccionada == 1)
smnu.getItem(0).setChecked(true);
20 else if(opcionSeleccionada == 2)
21 smnu.getItem(1).setChecked(true);
22}
23

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
2 @Override
public boolean onPrepareOptionsMenu(Menu menu)
3 {
4 menu.clear();
5
6 if(chkMenuExtendido.isChecked())
7 construirMenu(menu, true);
8 else
construirMenu(menu, false);
9
10 return super.onPrepareOptionsMenu(menu);
11}
12

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.

Si queris descargar el cdigo fuente completo del ejemplo utilizado en este artculo podis hacerlo
desde este enlace.

Interfaz de usuario en Android: Widgets (I)


Por sgoliver on 23/02/2011 en Android, Programacin

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 texto (TextView) que
albergar el mensaje a mostrar. Veamos cmo queda el layout xml, que para este ejemplo
llamaremos miwidget.xml:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dip">

<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#000000"
android:padding="10dip" >

<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF"
android:padding="5dip" >
<TextView android:id="@+id/txtMensaje"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="#000000"
android:text="Mi Primer Widget" />

</FrameLayout>

</FrameLayout>

</LinearLayout>

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 unos pocos bsicos que se
indican a continuacin:

Contenedores: FrameLayout, LinearLayout y RelativeLayout


Controles: Button, ImageButton, ImageView, TextView, ProgressBar,
Chronometer y AnalogClock.

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:

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


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/miwidget"
android:minWidth="146dip"
android:minHeight="72dip"
android:label="Mi Primer Widget"
android:updatePeriodMillis="3600000"
/>

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. 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 44 celdas donde se pueden colocar
aplicaciones, accesos directos y widgets. Teniendo en cuenta las diferentes dimensiones de estas
celdas segn 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 * 74) 2


alto_mnimo = (num_celdas * 74) 2

Atendiendo a esta frmula, si queremos que nuestro widget ocupe un tamao mnimo de 2 celdas de
ancho por 1 celda de alto, deberemos indicar unas dimensiones de 146dp x 72dp.

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:

onEnabled(): lanzado cuando se aade al escritorio la primera instancia de un widget.


onUpdate(): lanzado periodicamente cada vez que se debe actualizar un widget.
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.

package net.sgoliver.android;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;

public class MiWidget extends AppWidgetProvider {


@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
//Actualizar el widget
//...
}
}
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>
...
<receiver android:name=".MiWidget" android:label="Mi Primer Widget">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/miwidget_wprovider" />
</receiver>
</application>

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 telfono (pulsacin larga sobre el
escritorio o tecla Men, seleccionar la opcin Widgets, y por ltimo seleccionar nuestro Widget).
Os dejo una demostracin en video.

Como podis ver en el video, 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.

El cdigo fuente de este artculo podis obtenerlo pulsando aqu.

Interfaz de usuario en Android: Widgets (II)


Por sgoliver on 17/03/2011 en 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.

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:

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


2 <LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:orientation="vertical">
6
7 <TextView android:id="@+id/LblMensaje"
8 android:layout_width="wrap_content"
android:layout_height="wrap_content"
9 android:text="Mensaje personalizado:" />
10
11 <EditText android:id="@+id/TxtMensaje"
12 android:layout_height="wrap_content"
13 android:layout_width="fill_parent" />
14
<LinearLayout
15 android:layout_width="fill_parent"
16 android:layout_height="fill_parent"
17 android:orientation="horizontal" >
18
19 <Button android:id="@+id/BtnAceptar"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content"
android:text="Aceptar" />
22
23 <Button android:id="@+id/BtnCancelar"
24 android:layout_width="wrap_content"
25 android:layout_height="wrap_content"
26 android:text="Cancelar" />
27
</LinearLayout>
28
29</LinearLayout>
30
31
32
33
34

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 {


1
2 private int widgetId = 0;
3
4 @Override
5 public void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
setContentView(R.layout.widget_config);
7
8
//Obtenemos el Intent que ha lanzado esta ventana
9 //y recuperamos sus parmetros
10 Intent intentOrigen = getIntent();
11 Bundle params = intentOrigen.getExtras();
12
13 //Obtenemos el ID del widget que se est configurando
widgetId = params.getInt(
14 AppWidgetManager.EXTRA_APPWIDGET_ID,
15 AppWidgetManager.INVALID_APPWIDGET_ID);
16
17 //Establecemos el resultado por defecto (si se pulsa el botn 'Atrs'
18 //del telfono ser ste el resultado devuelto).
19 setResult(RESULT_CANCELED);
20
//...
21 }
22}
23
24
25
26

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:

1//Obtenemos la referencia a los controles de la pantalla


2final Button btnAceptar = (Button)findViewById(R.id.BtnAceptar);
3final Button btnCancelar = (Button)findViewById(R.id.BtnCancelar);
4final EditText 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 mtodo finish() ya que el resultado CANCELED ya se ha establecido por defecto
anteriormente:

1
//Implementacin del botn "Cancelar"
2btnCancelar.setOnClickListener(new OnClickListener() {
3 @Override
4 public void onClick(View arg0) {
5 //Devolvemos como resultado: CANCELAR (RESULT_CANCELED)
6 finish();
}
7});
8

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 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 evento onUpdate() 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 widget MiWidget.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
mtodo setResult(resultado, intent). Por ltimo llamaremos al mtodo finish()
para finalizar la actividad.

Con estas indicaciones, veamos cmo quedara el cdigo del botn Aceptar:

1
2 //Implementacin del botn "Aceptar"
3 btnAceptar.setOnClickListener(new OnClickListener() {
@Override
4 public void onClick(View arg0) {
5 //Guardamos el mensaje personalizado en las preferencias
6 SharedPreferences prefs =
7 getSharedPreferences("WidgetPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
8
editor.putString("msg_" + widgetId,
9 txtMensaje.getText().toString());
10 editor.commit();
11
12 //Actualizamos el widget tras la configuracin
13 AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(WidgetConfig.this);
14 MiWidget.actualizarWidget(WidgetConfig.this, appWidgetManager,
15widgetId);
16
17 //Devolvemos como resultado: ACEPTAR (RESULT_OK)
18 Intent resultado = new Intent();
19 resultado.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
setResult(RESULT_OK, resultado);
20 finish();
21 }
22});
23

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:
1<activity android:name=".WidgetConfig">
2 <intent-filter>
3 <action android:name="android.apwidget.action.APPWIDGET_CONFIGURE"/>
4 </intent-filter>
</activity>
5

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 22 celdas. Veamos cmo quedara finalmente:

1
2<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
3 android:initialLayout="@layout/miwidget"
4 android:minWidth="146dip"
5 android:minHeight="146dip"
6 android:label="Mi Primer Widget"
android:updatePeriodMillis="1800000"
7 android:configure="net.sgoliver.android.WidgetConfig"
8/>
9

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:

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


2 <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:padding="5dip">
6
7 <FrameLayout
android:layout_width="fill_parent"
8 android:layout_height="fill_parent"
9 android:background="#000000"
10 android:padding="10dip" >
11
12 <LinearLayout
13 android:layout_width="fill_parent"
android:layout_height="fill_parent"
14 android:background="#FFFFFF"
15 android:padding="5dip"
16 android:orientation="vertical">
17
18 <TextView android:id="@+id/LblMensaje"
19 android:layout_width="fill_parent"
android:layout_height="wrap_content"
20 android:textColor="#000000"
21 android:text="mensaje" />
22
23 <TextView android:id="@+id/LblHora"
24 android:layout_width="fill_parent"
android:layout_height="wrap_content"
25 android:textColor="#000000"
26 android:text="hora_actual" />
27
28 <Button android:id="@+id/BtnActualizar"
29 android:layout_width="fill_parent"
30 android:layout_height="wrap_content"
android:text="Actualizar" />
31
32 </LinearLayout>
33
34 </FrameLayout>
35
36</LinearLayout>
37
38
39
40
41
42

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 de
cada 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 y Chronometer, como por
ejemplo setTextViewText(idControl, valor) para establecer el textode un control
TextView. 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 es LblMensaje haramos lo siguiente:

1RemoteViews controles = new RemoteViews(context.getPackageName(),


2R.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:

1
2 @Override
3 public void onUpdate(Context context,
4 AppWidgetManager appWidgetManager,
5 int[] appWidgetIds) {
6
//Iteramos la lista de widgets en ejecucin
7 for (int i = 0; i < appWidgetIds.length; i++)
8 {
9 //ID del widget actual
10 int widgetId = appWidgetIds[i];
11
12 //Actualizamos el widget actual
actualizarWidget(context, appWidgetManager, widgetId);
13 }
14}
15
16public static void actualizarWidget(Context context,
17 AppWidgetManager appWidgetManager, int widgetId)
{
18 //Recuperamos el mensaje personalizado para el widget actual
19 SharedPreferences prefs =
20 context.getSharedPreferences("WidgetPrefs", Context.MODE_PRIVATE);
21 String mensaje = prefs.getString("msg_" + widgetId, "Hora actual:");
22
23 //Obtenemos la lista de controles del widget actual
RemoteViews controles =
24 new RemoteViews(context.getPackageName(), R.layout.miwidget);
25
26 //Actualizamos el mensaje en el control del widget
27 controles.setTextViewText(R.id.LblMsg, mensaje);
28
29 //Obtenemos la hora actual
Calendar calendario = new GregorianCalendar();
30 String hora = calendario.getTime().toLocaleString();
31
32 //Actualizamos la hora en el control del widget
33 controles.setTextViewText(R.id.LblHora, hora);
34
35 //Notificamos al manager de la actualizacin del widget actual
36 appWidgetManager.updateAppWidget(widgetId, controles);
}
37
38
39
40
41

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() del widget 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 tipo broadcast 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 el evento
onReceive() del widget 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 broadcast cada vez que se pulse
el botn del widget. Para ello, en el mtodo actualizarWidget() construiremos un nuevo
Intent asocindole una accin personalizada, que en nuestro caso llamaremos por ejemplo
net.sgoliver.ACTUALIZAR_WIDGET. Como parmetro del nuevo Intent insertaremos
mediante putExtra() el ID del widget actual de forma que ms tarde podamos saber el widget
concreto que ha lanzado el mensaje. Por ltimo crearemos el PendingIntent mediante el
mtodo getBroadcast() y lo asociaremos al evento onClick del control llamando a
setOnClickPendingIntent() pasndole el ID del control, en nuestro caso el botn de
Actualizar. Veamos cmo queda todo esto:

1
//Asociamos los 'eventos' al widget
2 Intent intent = new Intent("net.sgoliver.ACTUALIZAR_WIDGET");
3 intent.putExtra(
4 AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
5
6 PendingIntent pendingIntent =
7 PendingIntent.getBroadcast(context, widgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
8
9 controles.setOnClickPendingIntent(R.id.BtnActualizar, pendingIntent);
10
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:

1<intent-filter>
2 <action android:name="net.sgoliver.ACTUALIZAR_WIDGET"/>
3</intent-filter>

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 el ID del widget que lo ha
lanzado, obtendremos una referencia al widget manager, y por ltimo llamaremos nuestro mtodo
esttico de actualizacin pasndole estos datos.

1
2 @Override
3 public void onReceive(Context context, Intent intent) {
4 if (intent.getAction().equals("net.sgoliver.ACTUALIZAR_WIDGET")) {
//Obtenemos el ID del widget a actualizar
5
int widgetId = intent.getIntExtra(
6 AppWidgetManager.EXTRA_APPWIDGET_ID,
7 AppWidgetManager.INVALID_APPWIDGET_ID);
8
9 //Obtenemos el widget manager de nuestro contexto
10 AppWidgetManager widgetManager =
AppWidgetManager.getInstance(context);
11
12 //Actualizamos el widget
13 if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
14 actualizarWidget(context, widgetManager, widgetId);
15 }
16}
17

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.

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 de Alarmas, 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.

Como siempre, tenis disponible el cdigo fuente del artculo pulsando este enlace.
Preferencias en Android I: Shared Preferences
Por sgoliver on 14/03/2011 en 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 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 de 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
clase SharedPrefences, 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 modificarlas.
MODE_WORLD_WRITABLE. Todas las aplicaciones pueden leer y modificar estas
preferencias.

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:

1SharedPreferences prefs =
2 getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
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 String escribiramos lo siguiente:

1SharedPreferences prefs =
2 getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
3
4String 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 mtodo getString() 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:

1SharedPreferences prefs =
2 getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
3
4SharedPreferences.Editor editor = prefs.edit();
5editor.putString("email", "modificado@email.com");
6editor.putString("nombre", "Prueba");
editor.commit();
7

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 con el siguiente patrn:

/data/data/paquetejava/shared_prefs/nombre_coleccion.xml

As, por ejemplo, en nuestro caso encontraramos nuestro fichero de preferencias en la ruta:

/data/data/net.sgoliver.android/shared_prefs/MisPreferencias.xml
Si descargamos este fichero desde el DDMS y lo abrimos con cualquier editor de texto veremos un
contenido como el siguiente:

1<?xml version='1.0' encoding='utf-8' standalone='yes' ?>


2<map>
3 <string name="nombre">prueba</string>
4 <string name="email">modificado@email.com</string>
</map>
5

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.

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


Por sgoliver on 13/10/2011 en 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 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 la que se muestra en la imagen siguiente
(correpondiente a la pantalla de opciones de la galera de imgenes de Android).
Si obsservamos la imagen antterior vemoss cmo las diferentes
d oppciones se organizan
o deentro de la
pantallla de opciones en variaas categoras (Generall Settings y Slideshoww Settings). Dentro de
cada categora
c puueden apareecer varias opciones dee diversos tipos,
t como por ejempplo de tipo
checkbbox (Confifirm deletionns) o de tippo lista de seleccin (Display sizze). He ressaltado las
palabrras pantallaa de opcionnes, categooras, y tipos de opccin porquue sern estoos los tres
elemenntos principaales con los que vamos a definir el conjunto
c de opciones o preferencias
p de nuestra
aplicaccin. Empeccemos.

Como hemos indiccado, nuestrra pantalla dee opciones laa vamos a deefinir mediaante un XML L, de forma
similaar a como deefinimos cuaalquier layouut, aunque enn este caso deberemos colocarlo
c enn la carpeta
/res/ /xml. El contenedor principal de d nuestra pantalla dee preferencias ser ell elemento
<Pref ferenceSc creen>. Este elementoo representarr a la pantaalla de opcioones en s, dentro
d de la
cual inncluiremos el
e resto de elementos.
e D
Dentro de sste podremoos incluir nuuestra lista de
d opciones
organiizadas por caategoras, quue se represeentar mediaante el elemeento <Pref ferenceCa ategory>
al quee daremos unu texto desscriptivo utiilizando su atributo an ndroid:ti itle. Dentrro de cada
categoora podremoos aadir cuualquier nm mero de opciiones, las cuuales puedenn ser de distiintos tipos,
entre los
l que destaacan:

xPreference. Marcaa seleccionabble.


CheckBox
tPreference. Cadenna simple de texto.
EditText
ference. Lista
ListPref L de valoores seleccioonables (excllusiva).
MultiSel
lectListPreferen nce. Lista dee valores seleeccionables (mltiple).

Cada uno de esto os tipos de preferencia


p r
requiere la definicin
d d diferentess atributos, que
de q iremos
viendoo en los sigu
uientes apartaados.

Check
kBoxPrefereence

Representa un tipoo de opcin que slo pueede tomar doos valores diistintos: actiivada o desacctivada. Es
el equuivalente a un
u control dee tipo checkkbox. En estte caso tan slo
s tendremmos que espeecificar los
atributos: nombre interno de la opcin (android:key), texto a mostrar (android:title) y
descripcin de la opcin (android:summary). Veamos un ejemplo:

1<CheckBoxPreference
2 android:key="opcion1"
3 android:title="Preferencia 1"
android:summary="Descripcin de la preferencia 1" />
4

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
atributo android:dialogTitle. Un ejemplo sera el siguiente:

1<EditTextPreference
2 android:key="opcion2"
3 android:title="Preferencia 2"
4 android:summary="Descripcin de la preferencia 2"
android:dialogTitle="Introduce valor" />
5

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 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:

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


<resources>
2 <string-array name="pais">
3 <item>Espaa</item>
4 <item>Francia</item>
5 <item>Alemania</item>
6 </string-array>
<string-array name="codigopais">
7 <item>ESP</item>
8 <item>FRA</item>
9 <item>ALE</item>
10 </string-array>
</resources>
11
12
13

En la preferencia utilizaremos los atributos android:entries y android:entryValues


para hacer referencia a estas listas, como vemos en el ejemplo siguiente:

1
<ListPreference
2 android:key="opcion3"
3 android:title="Preferencia 3"
4 android:summary="Descripcin de la preferencia 3"
5 android:dialogTitle="Indicar Pais"
android:entries="@array/pais"
6 android:entryValues="@array/codigopais" />
7

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.

1
<MultiSelectListPreference
2 android:key="opcion4"
3 android:title="Preferencia 4"
4 android:summary="Descripcin de la preferencia 4"
5 android:dialogTitle="Indicar Pais"
android:entries="@array/pais"
6 android:entryValues="@array/codigopais" />
7

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.

1 <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
2 <PreferenceCategory android:title="Categora 1">
3 <CheckBoxPreference
4 android:key="opcion1"
5 android:title="Preferencia 1"
android:summary="Descripcin de la preferencia 1" />
6 <EditTextPreference
7 android:key="opcion2"
8 android:title="Preferencia 2"
9 android:summary="Descripcin de la preferencia 2"
10 android:dialogTitle="Introduce valor" />
</PreferenceCategory>
11 <PreferenceCategory android:title="Categora 2">
12 <ListPreference
13 android:key="opcion3"
14 android:title="Preferencia 3"
android:summary="Descripcin de la preferencia 3"
15 android:dialogTitle="Indicar Pais"
16 android:entries="@array/pais"
17 android:entryValues="@array/codigopais" />
18 </PreferenceCategory>
</PreferenceScreen>
19
20
21
22
23

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 todo el trabajo por nosotros. Esta clase se llama
PreferenceActivity. Tan slo deberemos crear una nueva actividad (yo la he llamado
PantallaOpciones) que extienda a esta clase e implementar su evento onCreate() para
aadir una llamada al mtodo addPreferencesFromResource(), mediante el que
indicaremos el fichero XML en el que hemos definido la pantalla de opciones. Lo vemos mejor
directamente en el cdigo:

1
public class PantallaOpciones extends PreferenceActivity {
2 @Override
3 public void onCreate(Bundle savedInstanceState) {
4 super.onCreate(savedInstanceState);
5
6 addPreferencesFromResource(R.xml.opciones);
}
7}
8

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.

Por supuesto, tendremos que aadir esta actividad al fichero AndroidManifest.xml, al igual que
cualquier otra actividad que utilicemos en la aplicacin.

1<activity android:name=".PantallaOpciones"
2 android:label="@string/app_name">
3 </activity>
Ya slo nos queda aadir a nuestra applicacin alggn mecaniismo para mostrar m la pantalla
p de
prefereencias. Estaa opcin suele estar en un
u men, peero por simpplificar el ejemplo vamoos a aadir
simpleemente un bo otn (btnPrreferenci ias) que llaame a la venntana de prefferencias.

Al puulsar este botn llam maremos a la ventaana de preeferencias mediante m e mtodo


el
start tActivity y(), como ya hemos viisto en algunna ocasin, ala que pasareemos como parmetros
p
el conntexto de la aplicacin (nos
( vale con nuestra acctividad prinncipal) y la clase de la ventana
v de
prefereencias (Pan
ntallaOpc ciones.cl lass).

1
btnP
Preferencia
as = (Button)findVie ewById(R.id.BtnPrefeerencias);
2
3btnPPreferencia
as.setOnClickListene er(new OnCl
lickListeneer() {
4 @Override
5 public void onClick(View v) {
6 startA
Activity(new Intent(AndroidPre efScreensAcctivity.th
his,
PanttallaOpcionnes.class));
7
}
8});
9

Y estoo es todo, yay slo nos queda ejeccutar la apliicacin en el e emulador y pulsar ell botn de
prefereencias para mostrar
m nuesstra nueva pantalla de oppciones. Debbe quedar coomo muestraa la imagen
siguiennte:

La priimera opcinn podemos marcarla o desmarcarlaa directamennte pulsandoo sobre la chheck de su


derechha. La segun
nda, de tipo texto,
t nos mostrar
m al puulsarla un peequeo form
mulario para solicitar el
valor de
d la opcin.
Por lltimo, la opccin 3 de tippo lista, noss mostrar una
u ventana emergente con la lista de valores
posibles, donde poodremos seleeccionar sloo uno de elloos.

v establecidos los valores de lass preferenciaas podemoss salir de laa ventana dee opciones
Una vez
simpleemente pulssando el botnb Atrss del dispoositivo o del d emuladoor. Nuestra actividad
Panta allaOpcio ones se haabr ocupadoo por nosotroos de guardaar correctam mente los vallores de las
opcionnes haciend do uso de la l API de preferencias
p s compartidaas (Shared Preferencess). Y para
comprrobarlo vamo os a aadir otro
o botn (b btnObtene erOpcione es) a la apliicacin de ejjemplo que
recupeere el valor actual
a de las 3 preferenciias y los escriba al log de
d la aplicaciin.

La forrma de acceeder a las preferencias


p compartidass de la apliccacin ya laa vimos en el artculo
anterioor sobre este tema. Obtenemoos la listta de prefferencias mediante m e mtodo
el
getDefaultSharedPreferences() y posteriormente utilizamos los distintos mtodos
get() para recuperar el valor de cada opcin dependiendo de su tipo.

1
2 btnObtenerPreferencias.setOnClickListener(new OnClickListener() {
@Override
3
public void onClick(View v) {
4 SharedPreferences pref =
5 PreferenceManager.getDefaultSharedPreferences(
6 AndroidPrefScreensActivity.this);
7
8 Log.i("", "Opcin 1: " + pref.getBoolean("opcion1", false));
Log.i("", "Opcin 2: " + pref.getString("opcion2", ""));
9 Log.i("", "Opcin 3: " + pref.getString("opcion3", ""));
10 }
11});
12

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:

110-08 09:27:09.681: INFO/(1162): Opcin 1: true


210-08 09:27:09.681: INFO/(1162): Opcin 2: prueba
310-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.

Podis descargar el cdigo fuente completo del ejemplo pulsando este enlace.

Bases de Datos en Android (I): Primeros pasos


Por sgoliver on 31/01/2011 en 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.

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, ser
transaccional 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;
2
import android.content.Context;
3 import android.database.sqlite.SQLiteDatabase;
4 import android.database.sqlite.SQLiteDatabase.CursorFactory;
5 import android.database.sqlite.SQLiteOpenHelper;
6
7 public class UsuariosSQLiteHelper extends SQLiteOpenHelper {
8
//Sentencia SQL para crear la tabla de Usuarios
9 String sqlCreate = "CREATE TABLE Usuarios (codigo INTEGER, nombre TEXT)";
10
11 public UsuariosSQLiteHelper(Context contexto, String nombre,
12 CursorFactory factory, int version) {
13 super(contexto, nombre, factory, version);
14 }
15
@Override
16 public void onCreate(SQLiteDatabase db) {
17 //Se ejecuta la sentencia SQL de creacin de la tabla
18 db.execSQL(sqlCreate);
19 }
20
@Override
21 public void onUpgrade(SQLiteDatabase db, int versionAnterior, int
22versionNueva) {
23 //NOTA: Por simplicidad del ejemplo aqu utilizamos directamente la
24 opcin de
// eliminar la tabla anterior y crearla de nuevo vaca con el
25nuevo formato.
26 // Sin embargo lo normal ser que haya que migrar datos de la
27tabla antigua
28 // a la nueva, por lo que este mtodo debera ser ms elaborado.
29
30 //Se elimina la versin anterior de la tabla
db.execSQL("DROP TABLE IF EXISTS Usuarios");
31
32 //Se crea la nueva versin de la tabla
33 db.execSQL(sqlCreate);
34 }
35}
36
37

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

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


getReadableDatabase() 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 sentencias INSERT correspondientes. Por ltimo cerramos la
conexin con la base de datos llamando al mtodo close().

1 package net.sgoliver.android;
2
3 import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
4 import android.os.Bundle;
5
6 public class AndroidBaseDatos extends Activity
7 {
8 @Override
public void onCreate(Bundle savedInstanceState)
9
{
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.main);
12
13 //Abrimos la base de datos 'DBUsuarios' en modo escritura
14 UsuariosSQLiteHelper usdbh =
15 new UsuariosSQLiteHelper(this, "DBUsuarios", null, 1);
16
SQLiteDatabase db = usdbh.getWritableDatabase();
17
18 //Si hemos abierto correctamente la base de datos
19 if(db != null)
20 {
21 //Insertamos 5 usuarios de ejemplo
for(int i=1; i<=5; i++)
22 {
23 //Generamos los datos
24 int codigo = i;
25 String nombre = "Usuario" + i;
26
27 //Insertamos los datos en la tabla Usuarios
db.execSQL("INSERT INTO Usuarios (codigo, nombre) " +
28 "VALUES (" + codigo + ", '" + nombre +"')");
29 }
30
31 //Cerramos la base de datos
32 db.close();
}
33 }
34}
35
36
37
38
39
40

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 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_dat
os

En el caso de nuestro ejemplo, la base de datos se almacenara por tanto en la ruta siguiente:

/data/data/net.sgoliver.android/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.
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:\android-sdk-
windows\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/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 Usuarios y 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):

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.

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
Por sgoliver on 03/02/2011 en 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:

1//Insertar un registro
db.execSQL("INSERT INTO Usuarios (usuario,email) VALUES
2('usu1','usu1@email.com') ");
3
4//Eliminar un registro
5db.execSQL("DELETE FROM Usuarios WHERE usuario='usu1' ");
6
7//Actualizar un registro
db.execSQL("UPDATE Usuarios SET email='nuevo@email.com' WHERE usuario='usu1'
8");

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


2ContentValues nuevoRegistro = new ContentValues();
3nuevoRegistro.put("usuario", "usu10");
4nuevoRegistro.put("email","usu10@email.com");
5
6//Insertamos el registro en la base de datos
db.insert("Usuarios", null, nuevoRegistro);
7

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 email del usuario de nombre usu1 haramos lo siguiente:

1//Establecemos los campos-valores a actualizar


2ContentValues valores = new ContentValues();
3valores.put("email","usu1_nuevo@email.com");
4
5//Actualizamos el registro en la base de datos
db.update("Usuarios", valores, "usuario='usu1'");
6

Como podemos ver, como tercer parmetro del mtodo update() pasamos directamente la
condicin del UPDATE 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 usu2 haramos lo siguiente:
1//Eliminamos el registro del usuario 'usu2'
2db.delete("Usuarios", "usuario='usu2'");

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.

Un ltimo detalle sobre estos mtodos. Tanto en el caso de execSQL() como en los casos de
update() o delete() podemos utilizar argumentos dentro de las condiones de la sentencia
SQL. Esto 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[]{"usu1"};
3 db.execSQL("DELETE FROM Usuarios WHERE usuario=?", args);
4
5 //Actualizar dos registros con update(), utilizando argumentos
6 ContentValues valores = new ContentValues();
7 valores.put("email","usu1_nuevo@email.com");
8
String[] args = new String[]{"usu1", "usu2"};
9 db.update("Usuarios", valores, "usuario=? OR usuario=?", args);
10

Esta forma de pasar a la sentencia SQL determinados datos variables puede ayudarnos adems a
escribir cdigo ms limpio y evitar posibles errores.

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
Por sgoliver on 07/02/2011 en 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 usuario,email FROM Usuarios WHERE


1usuario='usu1' ");

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:

1String[] args = new String[] {"usu1"};


Cursor c = db.rawQuery(" SELECT usuario,email FROM Usuarios WHERE usuario=? ",
2args);

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 clase
SQLiteDatabase. 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 registros que queremos que nos
devuelva la consulta. Veamos el mismo ejemplo anterior utilizando el mtodo query():

1String[] campos = new String[] {"usuario", "email"};


2String[] args = new String[] {"usu1"};
3
4Cursor 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 mtodos
getXXX(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, 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 String[] campos = new String[] {"usuario", "email"};
3 String[] args = new String[] {"usu1"};
4
Cursor c = db.query("Usuarios", campos, "usuario=?", args, null, null, null);
5
6 //Nos aseguramos de que existe al menos un registro
7 if (c.moveToFirst()) {
8 //Recorremos el cursor hasta que no haya ms registros
9 do {
String usuario = c.getString(0);
10 String email = c.getString(1);
11 } while(c.moveToNext());
12}
13

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.

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 (I): Memoria Interna


Por sgoliver on 05/07/2011 en 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 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 mtodo
openFileOutput(), 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 (por
defecto) para acceso privado desde nuestra aplicacin, MODE_APPEND para aadir datos a un
fichero ya existente, MODE_WORLD_READABLE para permitir a otras aplicaciones leer el fichero, o
MODE_WORLD_WRITABLE para permitir a otras aplicaciones escribir sobre el fichero.

Este mtodo devuelve una referencia al stream de salida asociado al fichero (en forma de objeto
FileOutputStream), 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 un OutputStreamWriter para escribir una cadena de texto al fichero.

try
{
OutputStreamWriter fout=
new OutputStreamWriter(
openFileOutput("prueba_int.txt", Context.MODE_PRIVATE));

fout.write("Texto de prueba.");
fout.close();
}
catch (Exception ex)
{
Log.e("Ficheros", "Error al escribir fichero a memoria interna");
}
Est bien,
b ya hemmos creado un
u fichero dee texto en laa memoria interna, perro dnde exaactamente?
Tal coomo ocurra con las basees de datos SQLite,
S Andrroid almacenna por defeccto los ficherros creados
en unaa ruta determ
minada, que en
e este caso seguir el siiguiente patrrn:

/data
a/data/pa
aquete_java/files/nombre
e_fichero
o

En mi caso particu
ular, la ruta ser
s

a/data/ne
/data et.sgoliver.andr
roid/file
es/prueba
a_int.txt
t

Si ejeecutamos el cdigo antterior podrem mos comproobar en el DDMS cm mo el fichero se crea


correcctamente en la ruta indiccada (Al final del artcuulo hay un ennlace a una aplicacin de
d ejemplo
sencillla donde incluyo un botn por cada uno
u de los puntos
p que vaamos a comeentar en el artculo).
a

Por ottra parte, leerr ficheros deesde la mem


moria interna es igual de sencillo, y procederemo
p os de forma
anlogga, con la n nica diferenccia de que utilizaremos
u el mtodo openFileI
o Input() paara abrir el
ficheroo, y los mtoodos de lectuura de javaa.io para leeer el contennido.

try
{
Buffere
edReader fiin =
new Buffe
eredReader((
ne
ew InputSt
treamReader
r(
openFileInput
t("prueba_i
_int.txt")));

String texto = fi
in.readLin
ne();
fin.clo
ose();
}
catch
h (Exceptio
on ex)
{
Log.e("
"Ficheros",
, "Error al
a leer fic
chero desd
de memoria interna");
}

La seggunda formaa de almacennar ficheros en la memooria interna del disposittivo es incluuirlos como
recursso en la pro opia aplicaccin. Aunquue este mtoodo es til en muchos casos, sloo debemos
utilizaarlo cuando no necesiteemos realizaar modificacciones sobree los ficheros, ya que tendremos
limitaddo el acceso a slo lectuura.
Para incluir un fichero como recursoo de la applicacin deebemos colocarlo en la carpeta
/ress/raw de nuestro
n proyyecto de Eclipse. Esta caarpeta no sueele estar creada por defeecto, por lo
que deeberemos crrearla manuaalmente en Eclipse desde el men contextual con la opciin New /
Folderr.

Una vez
v creada laa carpeta /r raw podremmos colocar ene ella cualqquier ficheroo que querammos que se
incluyya con la ap plicacin en tiempo de compilacinn en forma de recurso. Nosotros inncluiremos
como ejemplo un fichero de textot llamado prueba_rraw.txt. Ya en tiempo de d ejecucinn podremos
accedeer a este fich
hero, slo enn modo de lectura, de unaa forma simiilar a la que ya hemos viisto para el
resto de
d ficheros ene memoria interna.
i

Para acceder
a al ficchero, accedderemos en primer
p lugarr a los recurssos de la apllicacin conn el mtodo
getRe esources( () y sobre stos utilizaaremos el mtodo openRawResou urce(id_dell_recurso)
para abrir
a el ficheero en modoo lectura. Este
E mtodo devuelve un u objeto In nputStrea am, que ya
podrem mos manipu ular como quueramos mediante los mtodos
m de la
l API java a.io. Com mo ejemplo,
nosotrros convertirremos el streeam en un objeto
o Buffe eredRead der para leerr el texto coontenido en
el fichhero de ejem
mplo (por suppuesto los fiicheros de reecurso tambin pueden ser binarios,, como por
ejemplo ficheros de d imagen, video,
v etc). Veamos
V cm
mo quedara el e cdigo:

try
{
InputSt
tream fraw =
getResour
rces().open
nRawResour
rce(R.raw.p
prueba_raw
w);

edReader br
Buffere rin =
new Buffe
eredReader(
(new Input der(fraw));
tStreamRead

String linea = br
rin.readLi
ine();

lose();
fraw.cl
}
catch
h (Exceptio
on ex)
{
Log.e("
"Ficheros",
, "Error al
a leer fic de recurso raw");
chero desd
}

Como puede versse en el cddigo anteriorr, al mtodoo openRaw wResource e() le pasaamos como
parm
metro el ID del fichero inncluido coomo recurrso, que seguir el e patrn
R.raw del_fichero, por lo que en
w.nombre_d e nuestro caaso particulaar ser R.ra
aw.prueba a_raw.
Para no
n alargar mu ucho el artcculo, dejamoos la gestin de ficheros en la memorria externa (tarjeta SD)
para el
e prximo artculo,
a donnde tambin publicar unna sencilla aplicacin
a d ejemplo que
de q incluya
toda laa funcionalid
dad que hemmos comentaddo sobre la gestin
g de ficcheros.

Ficcheross en An
ndroid
d (II): Mem
moria Extern
E na
(Taarjeta SD)
Por sggoliver on 06
6/07/2011 enn Android, Programacinn

En el artculo anterior del currso hemos visto


v cmo manipular
m fiicheros locaalizados en la memoria
internaa de un disspositivo Anndroid. Sin embargo, como c ya indicamos, essta memoriaa suele ser
relativvamente limiitada y no ess aconsejablee almacenar en ella ficheeros de gran tamao. La alternativa
naturaal es utilizarr para ello laa memoria externa
e del dispositivo, constituida normalmennte por una
tarjetaa de memoriaa SD.

Una nota
n rpida antes
a de emppezar con estte tema. Parra poder probbar aplicacioones que hagan uso de
la memmoria extern na en el emuulador de Anndroid necessitamos teneer configuraddo en Eclipsse un AVD
que tennga estableccido correctaamente el tam
mao de la taarjeta SD. Enn mi caso, he
h definido por
p ejemplo
un tam
mao de tarjeeta de 10 Mbb:

Seguimmos. A diferrencia de la memoria


m intterna, la tarjeeta de memooria no tienee por qu estar presente
en el dispositivo,
d e incluso esttndolo puedde no estar reconocida
r p el sistem
por ma. Por tantoo, el primer
paso recomendado
r o a la hora de trabajar con ficheroos en memorria externa es e asegurarnnos de que
dicha memoria
m estt presente y disponible para leer y/oo escribir en ella.

Para esto
e la API de Androidd proporcionna (como mtodo esttico dela classe Environ nment) el
mtoddo getExte ernalStor rageStatu us(), que no
n dice si la memoria exxterna est diisponible y
si se puede
p leer y escribir en ella. Este mtodo
m devueelve una serrie de valorees que nos inndicarn el
estadoo de la memo oria externa, siendo los ms
m importanntes los siguuientes:

MEDIA_MO OUNTED, quue indica quue la memoriia externa esst disponible y podemoss tanto leer
como escriibir en ella.
MEDIA_MO OUNTED_R READ_ONLY Y, que indicaa que la memmoria externna est dispoonible pero
slo podemmos leer de ella.
e
Otra serie de valores que
q indicarnn que existe algn probleema y que poor tanto no podemos
p ni
leer ni esccribir en la memoria externa
e (MEDDIA_UNMOUNTED, ME EDIA_REMO OVED, ).
Podis connsultar todos estos estadoos en la docuumentacin oficial
o de la clase Enviroonment.
Con todo esto en cuenta, podramos realizar un chequeo previo del estado de la memoria externa del
dispositivo de la siguiente forma:

1
2
boolean sdDisponible = false;
3 boolean sdAccesoEscritura = false;
4
5 //Comprobamos el estado de la memoria externa (tarjeta SD)
6 String estado = Environment.getExternalStorageState();
7
8 if (estado.equals(Environment.MEDIA_MOUNTED))
9 {
sdDisponible = true;
10 sdAccesoEscritura = true;
11}
12else if (estado.equals(Environment.MEDIA_MOUNTED_READ_ONLY))
13{
sdDisponible = true;
14 sdAccesoEscritura = false;
15}
16else
17{
18 sdDisponible = false;
sdAccesoEscritura = false;
19}
20
21

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 tarjeta SD tenemos que obtener en primer
lugar la ruta al directorio raz de esta memoria. Para ello utilizaremos el mtodo
getExternalStorageDirectory() 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 objeto OutputStreamWriter para escribir al fichero un
mensaje de texto. Veamos cmo quedara el cdigo:

try
1 {
2 File ruta_sd = Environment.getExternalStorageDirectory();
3
4 File f = new File(ruta_sd.getAbsolutePath(), "prueba_sd.txt");
5
6 OutputStreamWriter fout =
7 new OutputStreamWriter(
new FileOutputStream(f));
8
9 fout.write("Texto de prueba.");
10 fout.clos
se();
11 }
cat
tch (Exception ex)
12{
13 icheros", "Error al escribir fichero a tarjeta SD
Log.e("Fi D");
14}
15
16
17

Ademms de esto,, tendremoss que especcificar en el e fichero AndroidMan


A nifest.xml quue nuestra
aplicaccin necesitta permiso de
d escrituraa en la mem moria externna. Para aadir un nuevvo permiso
usarem
mos como siempre laa clusula <uses-pe ermission n> utilizanddo el valorr concreto
andr roid.perm mission.W WRITE_EXT TERNAL_ST TORAGE. Con esto, nuestro
AndrooidManifest.xxml quedaraa de forma siimilar a ste:

1
2 <maanifest xmlns:androidd="http://schemas.anndroid.com//apk/res/a
android"
3 package
e="net.sgoliver.andr roid"
4 android
d:versionCode="1"
android
d:versionName="1.0"> >
5 <uses-sdk
k android:mminSdkVersion="7" />
6
7 <uses-per
rmission
8 oid:name="android.pe
andro ermission.WRITE_EXTEERNAL_STORAAGE" />
9 </uses-pe
ermission>
10
<applicat
tion an
ndroid:icoon="@drawab
ble/icon"
11anddroid:label
l="@string/app_name" ">
12 <acti
ivity androoid:name=".AndroidFiicheros"
13 android:label=="@string/app_name">>
14 <intent-fi
< lter>
15 <action android:name="andrroid.intentt.action.M
MAIN" />
<category android:name="anndroid.inteent.catego
ory.LAUNCHE
ER" />
16 </intent-f
< ilter>
17 </act
tivity>
18
19 </applica
ation>
20 </m
manifest>
21

Si ejeecutamos ah
hora el cdiigo y nos vamos
v al exxplorador dee archivos del DDMS podremos
comprrobar cmosse ha creado correctamennte el ficherro en el direectorio raiz de
d nuestra SD
S (carpeta
/sdca ard/).
Por su parte, leer un fichero desde la tarjeta SD es igual de sencillo. Obtenemos el directorio raiz de
la memoria externa con getExternalStorageDirectory(), 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
2 try
3 {
4 File ruta_sd = Environment.getExternalStorageDirectory();
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();
fin.close();
13}
14catch (Exception ex)
15{
16 Log.e("Ficheros", "Error al leer fichero desde tarjeta SD");
17 }
18

Como vemos, el cdigo es anlogo al que hemos visto para la escritura de ficheros.

Dej pendiente poneros disponible el cdigo de alguna aplicacin sencilla que contenga todos los
temas vistos en estos dos ltimos artculos sobre gestin de ficheros en Android. Y como lo
prometido es deuda, os dejo aqu el enlace al cdigo fuente completo de una aplicacin de ejemplo,
que simplemente habilita un botn para realizar cada una de las tareas comentadas, mostrando el
resultado de cada accin en un cuadro de texto superior de la pantalla proncipal. Os dejo una
imagen para que os hagis una idea:
Traatamieento de
d XM
ML en Andro
A oid (I): SAX
X
Por sggoliver on 18
8/01/2011 enn Android, Programacinn

En loss siguientes artculos de


d este Tutoorial de Dessarrollo paraa Android vamos
v a comentar las
distinttas posibilid
dades que teenemos a laa hora de trrabajar con datos en foormato XML L desde la
platafoorma Androiid.

A da de hoy, en casi todas lasl grandes plataformas


p de desarrolllo existen varias
v formass de leer y
escribiir datos en formato
f XML L. Los dos modelos
m mss extendidoss son SAX (SSimple API forf XML) y
DOM (Documentt Object Moodel). Posterriormente, han h ido aparreciendo otrros tantos, con c ms o
menoss xito, entree los que desstaca StAX (Streaming
( A for XML
API L). Pues bienn, Android no
n se queda
atrs en
e este sentid do e incluye estos tres modelos
m principales para el tratamiennto de XML,, o para ser
ms exxactos, los dos
d primeross como tal y una versin anloga del d tercero (X XmlPull). Poor supuesto
con cuualquiera dee los tres modelos
m poddemos hacerr las mismaas tareas, peero ya verem mos cmo
dependdiendo de laa naturaleza de
d la tarea que m eficiente utilizar un
q queramos realizar vaa a resultar ms
modello u otro.

Antes de empezarr, unas anotaciones resppecto a los ejemplos


e quue voy a utillizar. Estas tcnicas
t se
puedenn utilizar paara tratar cuualquier docuumento XM
ML, tanto onnline como local,
l pero por
p utilizar
algo conocido porr la mayora de vosotros todos los ejemplos van a trabajar soobre los datoos XML de
un doccumento RSS online, conncretamentee sobre el cannal RSS de portada
p de euuropapress.ccom.

Un documento RS
SS de este feeed tiene la estructura sigguiente:

<rss version="2
2.0">
<
<channel>
<title>
>Europa Press</title
e>
<link>h
http://www
w.europapre
ess.es/</link>
<descri
iption>Noticias de Portada.</
P descriptio
on>
<image>
<url>http://s01.europapress.net/eplogo.gif</url>
<title>Europa Press</title>
<link>http://www.europapress.es</link>
</image>
<language>es-ES</language>
<copyright>Copyright</copyright>
<pubDate>Sat, 25 Dec 2010 23:27:26 GMT</pubDate>
<lastBuildDate>Sat, 25 Dec 2010 22:47:14 GMT</lastBuildDate>
<item>
<title>Ttulo de la noticia 1</title>
<link>http://link_de_la_noticia_2.es</link>
<description>Descripcin de la noticia 2</description>
<guid>http://identificador_de_la_noticia_2.es</guid>
<pubDate>Fecha de publicacin 2</pubDate>
</item>
<item>
<title>Ttulo de la noticia 2</title>
<link>http://link_de_la_noticia_2.es</link>
<description>Descripcin de la noticia 2</description>
<guid>http://identificador_de_la_noticia_2.es</guid>
<pubDate>Fecha de publicacin 2</pubDate>
</item>
...
</channel>
</rss>

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:

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;
}

public void setDescripcion(String d) {


descripcion = d;
}

public void setGuid(String g) {


guid = g;
}

public void setFecha(String f) {


fecha = f;
}
}

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.

public class RssHandler extends DefaultHandler {


private List<Noticia> noticias;
private Noticia noticiaActual;
private StringBuilder sbTexto;

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();
}
@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 mtodo getNoticias() 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 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:

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import java.net.URL;
import javax.xml.parsers.SAXParser;
import java.net.MalformedURLException;
import javax.xml.parsers.SAXParserFactory;

public class RssParserSax


{
private URL rssUrl;

public RssParserSax(String url)


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

public List<Noticia> parse()


{
SAXParserFactory factory = SAXParserFactory.newInstance();

try
{
SAXParser parser = factory.newSAXParser();
RssHandler handler = new RssHandler();
parser.parse(this.getInputStream(), handler);
return handler.getNoticias();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}

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
fbrica correspondiente [lo que se consigue obteniendo una instancia de la fbrica con
SAXParserFactory.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:

public void onCreate(Bundle savedInstanceState)


{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

RssParserSax saxparser =
new RssParserSax("http://www.europapress.es/rss/rss.aspx");

List<Noticia> noticias = saxparser.parse();

//Manipulacin del array de noticias


//...
}

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.

Tan slo una anotacin final. 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:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sgoliver"
android:versionCode="1"
android:versionName="1.0">

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

<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".AndroidXml"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
<uses-sdk android:minSdkVersion="7" />
</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

Podis descargar el cdigo fuente de este artculo pulsando aqu.

En los siguientes artculos veremos los otros dos mtodos de tratamiento XML en Android que
hemos comentado (DOM y StAX) y por ltimo intentaremos comentar las diferencias entre ellos
dependiendo del contexto de la aplicacin.

ACTUALIZACIN: Android propone un modelo SAX alternativo que puede ayudar a simplicar
algunas acciones y disminuir la complejidad del handler necesario. En este artculo puedes aprender
a utilizar esta nueva variante de SAX para Android.

Tratamiento de XML en Android (II): SAX


Simplificado
Por sgoliver on 19/01/2011 en 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 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 dicho handler, 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
atributo noticiaActual 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 import java.io.IOException;
import java.io.InputStream;
2
import java.net.MalformedURLException;
3 import java.net.URL;
4 import java.util.ArrayList;
5 import java.util.List;
6
7 import org.xml.sax.Attributes;
8
import android.sax.Element;
9 import android.sax.EndElementListener;
10 import android.sax.EndTextElementListener;
11 import android.sax.RootElement;
12 import android.sax.StartElementListener;
13 import android.util.Xml;
14
public class RssParserSax2
15 {
16 private URL rssUrl;
17 private Noticia noticiaActual;
18
19 public RssParserSax2(String url)
{
20 try
21 {
22 this.rssUrl = new URL(url);
23 }
24 catch (MalformedURLException e)
{
25 throw new RuntimeException(e);
26 }
27 }
28
29 public List<Noticia> parse()
{
30 final List<Noticia> noticias = new ArrayList<Noticia>();
31
32 RootElement root = new RootElement("rss");
33 Element channel = root.getChild("channel");
34 Element item = channel.getChild("item");
35
36 item.setStartElementListener(new StartElementListener(){
public void start(Attributes attrs) {
37 noticiaActual = new Noticia();
38 }
39 });
40
41 item.setEndElementListener(new EndElementListener(){
42 public void end() {
noticias.add(noticiaActual);
43 }
44 });
45
46 item.getChild("title").setEndTextElementListener(
47 new EndTextElementListener(){
public void end(String body) {
48 noticiaActual.setTitulo(body);
49 }
50 });
51
52 item.getChild("link").setEndTextElementListener(
53 new EndTextElementListener(){
public void end(String body) {
54 noticiaActual.setLink(body);
55 }
56 });
57
58 item.getChild("description").setEndTextElementListener(
new EndTextElementListener(){
59 public void end(String body) {
60 noticiaActual.setDescripcion(body);
61 }
62 });
63
64 item.getChild("guid").setEndTextElementListener(
new EndTextElementListener(){
65 public void end(String body) {
66 noticiaActual.setGuid(body);
67 }
68 });
69
item.getChild("pubDate").setEndTextElementListener(
70
new EndTextElementListener(){
71 public void end(String body) {
72 noticiaActual.setFecha(body);
73 }
74 });
75
try
76 {
77 Xml.parse(this.getInputStream(),
78 Xml.Encoding.UTF_8,
79 root.getContentHandler());
80 }
catch (Exception ex)
81 {
82 throw new RuntimeException(ex);
83 }
84
85 return noticias;
}
86
87 private InputStream getInputStream()
88
89 {
90 try
{
91 return rssUrl.openConnection().getInputStream();
92 }
93 catch (IOException e)
94 {
throw new RuntimeException(e);
95 }
96 }
97 }
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

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.

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.

El cdigo fuente de este artculo puede descargarse desde este enlace.

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


Por sgoliver on 24/01/2011 en 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
2 <noticias>
<noticia>
3 <titulo>T1</titulo>
4 <link>L1</link>
5 </noticia>
6 <noticia>
7 <titulo>T2</titulo>
<link>L2</link>
8 </noticia>
9 <noticias>
10
Este XML se traducira en un rbol parecido al siguiente:

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 public
{
class RssParserDom
2 private URL rssUrl;
3
4 public RssParserDom(String url)
5 {
6 try
{
7 this.rssUrl = new URL(url);
8 }
9 catch (MalformedURLException e)
10 {
11 throw new RuntimeException(e);
}
12 }
13
14 public List<Noticia> parse()
15 {
16 //Instanciamos la fbrica para DOM
DocumentBuilderFactory factory =
17 DocumentBuilderFactory.newInstance();
18 List<Noticia> noticias = new ArrayList<Noticia>();
19
20 try
21 {
//Creamos un nuevo parser DOM
22 DocumentBuilder builder = factory.newDocumentBuilder();
23
24 //Realizamos lalectura completa del XML
25 Document dom = builder.parse(this.getInputStream());
26
27 //Nos posicionamos en el nodo principal del rbol (<rss>)
Element root = dom.getDocumentElement();
28
29 //Localizamos todos los elementos <item>
30 NodeList items = root.getElementsByTagName("item");
31
32 //Recorremos la lista de noticias
33 for (int i=0; i<items.getLength(); i++)
34 {
Noticia noticia = new Noticia();
35
36 //Obtenemos la noticia actual
37 Node item = items.item(i);
38
39 //Obtenemos la lista de datos de la noticia actual
40 NodeList datosNoticia = item.getChildNodes();
41
//Procesamos cada dato de la noticia
42 for (int j=0; j<datosNoticia.getLength(); j++)
43 {
44 Node dato = datosNoticia.item(j);
45 String etiqueta = dato.getNodeName();
46
47 if (etiqueta.equals("title"))
{
48 String texto = obtenerTexto(dato);
49 noticia.setTitulo(texto);
50 }
51 else if (etiqueta.equals("link"))
{
52
noticia.setLink(dato.getFirstChild().getNodeValue());
53 }
54 else if (etiqueta.equals("description"))
55 {
56 String texto = obtenerTexto(dato);
noticia.setDescripcion(texto);
57 }
58 else if (etiqueta.equals("guid"))
59 {
60 noticia.setGuid(dato.getFirstChild().getNodeValue());
61 }
else if (etiqueta.equals("pubDate"))
62 {
63 noticia.setFecha(dato.getFirstChild().getNodeValue())
64 ;
65 }
}
66
67
68 noticias.add(noticia);
69 }
}
70 catch (Exception ex)
71 {
72 throw new RuntimeException(ex);
73 }
74
return noticias;
75 }
76
77 private String obtenerTexto(Node dato)
78 {
79 StringBuilder texto = new StringBuilder();
80 NodeList fragmentos = dato.getChildNodes();
81
for (int k=0;k<fragmentos.getLength();k++)
82 {
83 texto.append(fragmentos.item(k).getNodeValue());
84 }
85
86 return texto.toString();
}
87
88 private InputStream getInputStream()
89 {
90 try
91 {
92 return rssUrl.openConnection().getInputStream();
}
93 catch (IOException e)
94 {
95 throw new RuntimeException(e);
96 }
}
97
}
98
99
10
0
10
1
10
2
10
3
10
4
10
5
10
6
10
7
10
8
10
9
11
0
11
1
11
2
11
3

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:

1String 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():

1
2 private String obtenerTexto(Node dato)
{
3 StringBuilder texto = new StringBuilder();
4 NodeList fragmentos = dato.getChildNodes();
5
6 for (int k=0;k<fragmentos.getLength();k++)
7 {
texto.append(fragmentos.item(k).getNodeValue());
8
}
9
10 return texto.toString();
11}
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 objeto Document), 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.

El cdigo fuente completo de este artculo se puede descargar desde este enlace.

Tratamiento de XML en Android (IV):


XmlPull
Por sgoliver on 25/01/2011 en 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.

Veamos cmo podemos hacer esto:

1 public class RssParserPull


2 {
private URL rssUrl;
3
4 public RssParserPull(String url)
5 {
6 try
7 {
8 this.rssUrl = new URL(url);
}
9 catch (MalformedURLException e)
10 {
11 throw new RuntimeException(e);
12 }
}
13
14
public List<Noticia> parse()
15 {
16 List<Noticia> noticias = null;
17 XmlPullParser parser = Xml.newPullParser();
18
19 try
{
20 parser.setInput(this.getInputStream(), null);
21
22 int evento = parser.getEventType();
23
24 Noticia noticiaActual = null;
25
26 while (evento != XmlPullParser.END_DOCUMENT)
27 {
String etiqueta = null;
28
29 switch (evento)
30 {
31 case XmlPullParser.START_DOCUMENT:
32
33 noticias = new ArrayList<Noticia>();
break;
34
35 case XmlPullParser.START_TAG:
36
37 etiqueta = parser.getName();
38
39 if (etiqueta.equals("item"))
40 {
41 noticiaActual = new Noticia();
42 }
else if (noticiaActual != null)
43 {
44 if (etiqueta.equals("link"))
45 {
46 noticiaActual.setLink(parser.nextText());
}
47 else if (etiqueta.equals("description"))
48 {
49 noticiaActual.setDescripcion(parser.nextText(
50 ));
51 }
else if (etiqueta.equals("pubDate"))
52 {
53 noticiaActual.setFecha(parser.nextText());
54 }
55 else if (etiqueta.equals("title"))
{
56 noticiaActual.setTitulo(parser.nextText());
57 }
58 else if (etiqueta.equals("guid"))
59 {
60 noticiaActual.setGuid(parser.nextText());
}
61 }
62 break;
63
64 case XmlPullParser.END_TAG:
65
66 etiqueta = parser.getName();
67
if (etiqueta.equals("item") && noticiaActual != null)
68
{
69 noticias.add(noticiaActual);
70 }
71 break;
72 }
73
evento = parser.next();
74 }
75 }
76 catch (Exception ex)
77 {
78 throw new RuntimeException(ex);
}
79
80 return noticias;
81 }
82
83 private InputStream getInputStream()
84 {
try
85 {
86 return rssUrl.openConnection().getInputStream();
87 }
88 catch (IOException e)
89 {
90 throw new RuntimeException(e);
}
91 }
92 }
93
94
95
96
97
98
99
10
0
10
1
10
2
10
3
10
4
10
5
10
6
10
7

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() y
parser.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 mediante parser.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 que nextText()
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.

El cdigo fuente completo de este artculo podis descargarlo pulsando aqu.


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
Por sgoliver on 23/01/2012 en 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
2 //Creamos un fichero en la memoria interna
3 OutputStreamWriter fout =
4 new OutputStreamWriter(
openFileOutput("prueba.xml",
5 Context.MODE_PRIVATE));
6
7 StringBuilder sb = new StringBuilder();
8
9 //Construimos el XML
10sb.append("");
11sb.append("" + "Usuario1" + "");
sb.append("" + "ApellidosUsuario1" + "");
12sb.append("");
13
14//Escribimos el resultado a un fichero
15fout.write(sb.toString());
16fout.close();
17
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 de salida,
asignar el fichero como salida del serializador y construir el XML mediante los mtodos
startTag(), text() y endTag() 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
2
3 //Creamos el serializer
XmlSerializer ser = Xml.newSerializer();
4
5 //Creamos un fichero en memoria interna
6 OutputStreamWriter fout =
7 new OutputStreamWriter(
8 openFileOutput("prueba_pull.xml",
Context.MODE_PRIVATE));
9
10//Asignamos el resultado del serializer al fichero
11ser.setOutput(fout);
12
13//Construimos el XML
14ser.startTag("", "usuario");
15
16ser.startTag("", "nombre");
ser.text("Usuario1");
17ser.endTag("", "nombre");
18
19ser.startTag("", "apellidos");
20ser.text("ApellidosUsuario1");
21ser.endTag("", "apellidos");
22
ser.endTag("", "usuario");
23
24ser.endDocument();
25
26fout.close();
27
28

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 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 de InputStream podemos utilizarlo como entrada a cualquiera de los mecanismos
de lectura de XML (SAX, DOM, XmlPull).

1
2 //Obtenemos la referencia al fichero XML de entrada
3 FileInputStream fil = openFileInput("prueba.xml");
4
//DOM (Por ejemplo)
5 DocumentBuilderFactory factory =
6 DocumentBuilderFactory.newInstance();
7
8 DocumentBuilder builder = factory.newDocumentBuilder();
9 Document dom = builder.parse(fil);
10
//A partir de aqu se tratara el rbol DOM como siempre.
11//Por ejemplo:
12Element root = dom.getDocumentElement();
13
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
2 //Obtenemos la referencia al fichero XML de entrada
3 InputStream is = getResources().openRawResource(R.raw.prueba);
4
//DOM (Por ejemplo)
5 DocumentBuilderFactory factory =
6 DocumentBuilderFactory.newInstance();
7
8 DocumentBuilder builder = factory.newDocumentBuilder();
9 Document dom = builder.parse(is);
10
//A partir de aqu se tratara el rbol DOM como siempre.
11//Por ejemplo:
12Element root = dom.getDocumentElement();
13
14//...
15
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
2 //Obtenemos la referencia al fichero XML de entrada
3 AssetManager assetMan = getAssets();
InputStream is = assetMan.open("prueba_asset.xml");
4
5 //DOM (Por ejemplo)
6 DocumentBuilderFactory factory =
7 DocumentBuilderFactory.newInstance();
8
9 DocumentBuilder builder = factory.newDocumentBuilder();
10Document dom = builder.parse(is);
11
//A partir de aqu se tratara el rbol DOM como siempre.
12//Por ejemplo:
13Element root = dom.getDocumentElement();
14
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 mtodo getXml() 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 comentamos en 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)
Log.i("XmlTips", "Inicio del documento");
10
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.

Como siempre, podis descargar el cdigo fuente completo de un sencillo programa de ejemplo que
utiliza cada una de las alternativas comentadas.
Localizacin geogrfica en Android (I)
Por sgoliver on 27/04/2011 en 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 al location 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:

1LocationManager locManager
(LocationManager)getSystemService(LOCATION_SERVICE);
=
2List<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
mtodo getProvider(nombre) y posteriormente utilizar los mtodos disponibles para conocer
sus propiedades, por ejemplo getAccuracy() para saber su precisin (tenemos disponibles las
constantes Criteria.ACCURACY_FINE para precisin 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.

1LocationManager locManager =
2(LocationManager)getSystemService(LOCATION_SERVICE);
3List<String> listaProviders = locManager.getAllProviders();
4
5LocationProvider provider = locManager.getProvider(listaProviders.get(0));
int precision = provider.getAccuracy();
6boolean obtieneAltitud = provider.supportsAltitude();
7int consumoRecursos = provider.getPowerRequirement();

Al margen de esto, hay que tener en cuenta que la lista de proveedores devuelta por el mtodo
getAllProviders() 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:

1Criteria req = new Criteria();


2req.setAccuracy(Criteria.ACCURACY_FINE);
3req.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


2String mejorProviderCrit = locManager.getBestProvider(req, false);
3
4//Lista de proveedores por criterio
5List<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.

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//Si el GPS no est habilitado


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

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 tipo toast, 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 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, 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 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
public void onLocationChanged(Location location) {
3 mostrarPosicion(location);
4 }
5
6 public void onProviderDisabled(String provider){
7 lblEstado.setText("Provider OFF");
8 }
9
public void onProviderEnabled(String provider){
10 lblEstado.setText("Provider ON");
11 }
12
13 public void onStatusChanged(S
String provider, int t status, Bundle
B extr
ras){
14 lblEs
stado.setT ext("Provi
ider Statu s: " + stat
tus);
}
15};
16
17
18

Como podis ver,, en nuestro caso de ejeemplo nos liimitamos a mostrar al usuario u la innformacin
recibidda en el eveento, bien seea un simplee cambio dee estado, o una u nueva posicin,
p en cuyo caso
llamammos al mtodo auxiliar mostrarPo
m osicion() ) para refrescar todos loos datos de lal posicin
en la pantalla.
p Parra este ejempplo hemos construido
c unna interfaz muy
m sencillaa, donde se muestran
m 3
datos de
d la posicin (latitud, loongitud y prrecisin) y un
u campo parra mostrar el estado del proveedor.
Ademms, se incluy yen dos botoones para coomenzar y detener
d la reccepcin de nuevas
n actuaalizaciones
de la posicin.
p Noo incluyo aqqu el cdigoo de la interffaz para no alargar
a ms el artculo, pero
p puede
consulltarse en el cdigo
c fuentte suministrrado al final del texto. ElE aspecto dee nuestra venntana es el
siguiennte:

En el mtodo mo ostrarPos sicion() nos vamos a limitar a mostrar loss distintos datos d de la
posicin pasada como
c parmetro en los controles coorrespondienntes de la innterfaz, utilizzando para
ello los
l mtodoss proporcioonados por la clase Location
L . En nuesttro caso uttilizaremos
getLa atitude() ), getAlt titude() y getAccu uracy() para
p obtenerr la latitud, longitud y
precisiin respectiv
vamente. Por supuesto, hay
h otros mtodos dispoonibles en estta clase paraa obtener la
altura,, orientacin
n, velocidad, etc que se pueden coonsultar en laa documentaacin oficial de la clase
Locatiion. Veamoss el cdigo:

1 private void mostrarPos


m icion(Loca
ation loc) {
2 if(loc !== null)
{
3 lblLaatitud.setText("Latiitud: " + String.valu
S ueOf(loc.g
getLatitude
e()));
4 lblLoongitud.setText("Lonngitud: " +
5 Str
ring.valueOOf(loc.getLongitude(()));
6 lblPrecision.setText("Precision: " +
7 String.valueOf(loc.getAccuracy()));
}
8 else
9 {
10 lblLatitud.setText("Latitud: (sin_datos)");
11 lblLongitud.setText("Longitud: (sin_datos)");
lblPrecision.setText("Precision: (sin_datos)");
12 }
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
2
3 private void comenzarLocalizacion()
{
4 //Obtenemos una referencia al LocationManager
5 locManager =
6 (LocationManager)getSystemService(Context.LOCATION_SERVICE);
7
8 //Obtenemos la ltima posicin conocida
Location loc =
9
locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
10
11 //Mostramos la ltima posicin conocida
12 mostrarPosicion(loc);
13
14 //Nos registramos para recibir actualizaciones de la posicin
15 locListener = new LocationListener() {
public void onLocationChanged(Location location) {
16 mostrarPosicion(location);
17 }
18
19 //Resto de mtodos del listener
20 //...
21 };
22
locManager.requestLocationUpdates(
23 LocationManager.GPS_PROVIDER, 30000, 0, locListener);
24}
25
26
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:

1btnDesactivar.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 para simular 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. Por ahora os dejo el cdigo fuente completo
para que podis hacer vuestras propias pruebas.

Localizacin geogrfica en Android (II)


Por sgoliver on 08/05/2011 en 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:

private void actualizarPosicion()


1 {
2 //Obtenemos una referencia al LocationManager
3 locationManager =
4 (LocationManager)getSystemService(Context.LOCATION_SERVICE);
5
//Obtenemos la ltima posicin conocida
6 Location location =
7 locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
8
9 //Mostramos la ltima posicin conocida
10 muestraPosicion(location);
11
12 //Nos registramos para recibir actualizaciones de la posicin
locationListener = new LocationListener() {
13 public void onLocationChanged(Location location) {
14 muestraPosicion(location);
15 }
16 public void onProviderDisabled(String provider){
lblEstado.setText("Provider OFF");
17 }
18 public void onProviderEnabled(String provider){
19 lblEstado.setText("Provider ON");
20 }
21 public void onStatusChanged(String provider, int status, Bundle
extras){
22 Log.i("LocAndroid", "Provider Status: " + status);
23 lblEstado.setText("Provider Status: " + status);
24 }
25 };
26
locationManager.requestLocationUpdates(
27
LocationManager.GPS_PROVIDER, 15000, 0, locationListener);
28}
29
30private void muestraPosicion(Location loc) {
31 if(loc != null)
32 {
lblLatitud.setText("Latitud: " + String.valueOf(loc.getLatitude()));
33 lblLongitud.setText("Longitud: " +
34Strring.valueO
Of(loc.getLongitude(()));
35 lblPr
recision.s etText("Pr
recision: " +
Str
ring.valueO
Of(loc.getAccuracy())));
36 Log.i
i("LocAndroid", Striing.valueOf(
37 loc.getLat
l itude() + " - " + St
tring.valueeOf(loc.geetLongitude
e())));
38 }
39 else
{
40 lblLa
atitud.setText("Latiitud: (sin_datos)");;
41 lblLo
ongitud.setText("Lonngitud: (sin_datos)"");
42 lblPr
recision.setText("Prrecision: (sin_datoss)");
43 }
44}
45
46
47
48
49
50

Si ejeccutamos en este momennto la aplicaccin en el emmulador y pulsamos


p el botn
b Activaar veremos
cmo los cuadros de texto se rellenan
r conn la informaccin de la lltima posicin conocida (si existe),
pero sin embargo estos datos non cambiarn en ningnn momento ya y que por ell momento el e emulador
de Anndroid tan s lo cuenta con
c esa infoormacin. C Cmo podem mos simular la actualizaacin de la
posicin del dispo
ositivo para ver
v si nuestraa aplicacin responde exxactamente como
c esperaamos?

Pues bien,
b para hacer
h esto tenemos variaas opciones. La primeraa de ellas, y la ms senncilla, es el
envo manual de una nueva posicin al a emulador de Androidd, para sim mular que sste hubiera
cambiado su locaalizacin. Essto se puedee realizar deesde la persspectiva de DDMS, en la pestaa
Emulaator Controll, donde poddemos enconntrar una secccin llamadda Location Controls, mostrada
m en
la imaagen siguientte (click paraa ampliar):
Con estos
e controles podemoos enviar dee forma maanual al emuulador en ejecucin
e unnas nuevas
coordeenadas de posicin,
p parra simular que
q stas se hubieran reecibido a traavs del prooveedor de
localizzacin utilizzado. De estta forma, sii introducim
mos unas cooordenadas de
d longitud y latitud y
pulsammos el botn n Send mienntras nuestraa aplicacin se ejecuta en el emulaador, esto prrovocar la
ejecuccin del eveento onLocationChanged() y por consiguuiente se moostrarn estoos mismos
datos en sus conttroles corresspondientes de la interfaz, como vemosv en laa siguiente captura de
pantalla:

Por suupuesto, si hacemos


h nueevos envos de coordennadas desde Eclipse vereemos cmo sta se va
actualiizando en nuestra
n apliccacin sin ningn
n tipo de problammas. Sin embbargo este mtodo
m de
manuaal no resultaa demasiadoo adecuado ni cmodo para probarr toda la funncionalidad de nuestra
aplicaccin, por ejeemplo la actuualizacin dee posicin caada 15 segunndos.

Por elllo, Android proporciona otro mtoddo algo mennos manual de simular cambios freecuentes de
posicin para probar nuestrass aplicacionees. Este mttodo consistte en proporrcionar, en vez
v de una
sla coordenada
c cada vez, una
u lista de coordenadaas que se irran envianddo automticcamente al
emulaador una traas otra a unna determinnada velociddad, de form ma que poddamos simuular que el
dispossitivo se mueve constantemente y queq nuestra aplicacin
a reesponde de forma correcta y en el
momeento adecuad do a esos cammbios de poosicin. Y essta lista de coordenadas
c se puede prroporcionar
de dos formas diistintas, en formato GP PX o como fichero KM ML. Amboss tipos de fichero
f son
ampliaamente utilizzados por applicaciones y dispositivoos de localizzacin, comoo GPS, apliccaciones de
cartoggrafa y map pas, etc. Loos ficheros KML podeemos generaarlos por ejemplo a traavs de la
aplicaccin Googlee Earth o maanualmente con cualquiier editor dee texto, peroo recomiendoo consultar
los doos enlaces an
nteriores para obtener ms
m informacin sobre caada formato.. Para este ejemplo,
ej yo
he gennerado un ficchero KML ded muestra con
c una listaa de 1000 posiciones geoogrficas al azar.
a

Para utilizar
u este fichero
f comoo fuente de datos para simular
s cambbios en la poosicin del dispositivo,
d
accedeemos nuevam mente a los Location
L Coontrols y pullsamos sobree la pestaa GPX o KML L, segn el
formatto que hayamos elegidoo, que en nuuestro caso ser s KML. Pulsamos
P el botn Loaad KML
para seeleccionar nuestro
n ficherro y veremos la lista de coordenadas
c s como en laa siguiente im
magen:
Una vez cargado el
e fichero, teendremos dissponibles loss cuatro botoones inferiorres para (de izquierda
i a
derechha):

Avanzar au utomticameente por la liista.


Ir a la posiicin anterior de la lista de
d forma maanual.
Ir a la posiicin siguiennte de la listaa de forma manual.
m
Establecer la velocidadd de avance automtico.

Entenddido esto, vamos


v a utilizar la lista de posicionnes para proobar nuestraa aplicacin. Para ello,
ejecutaamos la aplicacin en el emuladoor, pulsamos nuestro botn b Activvar para coomenzar a
detectaar cambios de posicinn, y pulsam mos el botn de avancce automticco (botn verde) v que
acabammos de comeentar. Si obsservamos rppidamente laa pantalla dee nuestra apllicacin vereemos cmo
se acttualizan varias veces loos valores ded latitud y longitud actuales.
a Cmo es possible? No
habam
mos configurado el Loc cationLis stener parra obtener acctualizacionees de posicin cada 15
segunddos? Antes de
d contestarr a esto, dejeemos que la aplicacin se
s ejecute duurante un minuto
m ms.
Tras unos
u 60 segu
undos de ejecucin detenemos la captura
c de posiciones
p pulsando nueestro botn
Desaactivar.

Ahoraa vayamos a la ventana de d log del DDMS


D y veaamos los meensajes de loog ha generaado nuestra
aplicaccin para in ntentar sabeer qu ha ocurrido.
o Enn mi caso, los mensajjes generadoos son los
siguienntes (en tu caso
c deben seer muy pareccidos):

1 05-08 10:50:3
37.921: INFO/LocAndr
roid(251): 7.0 - -11
1.9999983333333334
05-08 10:50:3
38.041: INFO/LocAndr
roid(251): Provider Status: 2
2 05-08 10:5
50:38.901: INFO/L
LocAndroid
d(251): 7.000001666666666 - -
3 11.9999966666
666668
4 05-08 10:5
50:39.941: INFO/L
LocAndroid
d(251): 7.000001666666666 - -
5 11.9999966666
666668
6 05-08 10:5
50:41.011: INFO/L
LocAndroid
d(251): 7.000003333333333 - -
11.9999950000
000002
7 05-08 10:5
50:43.011: INFO/L
LocAndroid
d(251): 7.000005000000001 - -
8 11.9999933333
333334
9 05-08 10:50:45.001: INFO/LocAndroid(251): 7.000006666666667 - -
10 11.999991666666665
05-08 10:50:46.061: INFO/LocAndroid(251): 7.000008333333333 - -
1111.999989999999999
1205-08 10:50:47.131: INFO/LocAndroid(251): 7.000008333333333 - -
1311.999989999999999
1405-08 10:50:47.182: INFO/LocAndroid(251): Provider Status: 1
05-08 10:51:02.232: INFO/LocAndroid(251): 7.000023333333333 - -11.999975
1505-08 10:51:02.812: INFO/LocAndroid(251): 7.000023333333333 - -
1611.999973333333333
1705-08 10:51:02.872: INFO/LocAndroid(251): Provider Status: 2
1805-08 10:51:03.872: INFO/LocAndroid(251): 7.000024999999999 - -
1911.999973333333333
05-08 10:51:04.912: INFO/LocAndroid(251): 7.000026666666668 - -
2011.999971666666665
2105-08 10:51:05.922: INFO/LocAndroid(251): 7.000026666666668 - -
2211.999971666666665
2305-08 10:51:06.982: INFO/LocAndroid(251): 7.000028333333334 - -11.99997
2405-08 10:51:08.032:
11.999968333333333
INFO/LocAndroid(251): 7.000028333333334 - -
2505-08 10:51:09.062: INFO/LocAndroid(251): 7.00003 - -11.999968333333333
2605-08 10:51:10.132: INFO/LocAndroid(251): 7.000031666666667 - -
2711.999966666666667
2805-08 10:51:12.242: INFO/LocAndroid(251): 7.000033333333333 - -
11.999965000000001
2905-08 10:51:13.292: INFO/LocAndroid(251): 7.000033333333333 - -
3011.999963333333335
3105-08 10:51:13.342: INFO/LocAndroid(251): Provider Status: 1
3205-08 10:51:28.372: INFO/LocAndroid(251): 7.000048333333333 - -
11.999950000000002
33
05-08 10:51:28.982: INFO/LocAndroid(251): 7.000048333333333 - -
3411.999950000000002
3505-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:

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.

Podes descargar el cdigo fuente utilizado en este artculo desde este enlace.

Mapas en Android (I): Preparativos y ejemplo


bsico
Por sgoliver on 25/05/2011 en Android, Programacin

Siguiendo con nuestro Curso de Programacin en Android, tras haber hablado en los dos ltimos
artculos sobre localizacin geogrfica mediante GPS (aqu y aqu), en este nuevo artculo vamos a
empezar a hablar de un complemento ideal para este tipo de servicios y aplicaciones, como es la
utilizacin de mapas en nuestras aplicaciones haciendo uso de la API Android de Google Maps.

Antes de empezar a utilizar el servicio de mapas de Google es necesario realizar algunas tareas
previas. En primer lugar, debemos asegurarnos de que tenemos instalado el paquete correspondiente
a la versin de Android para la que desarrollamos enriquecido con las APIs de Google. Estos
paquetes se llaman normalmente Google APIs by Google, Android API x, revisin y. Esto
podemos comprobarlo y descargarlo si es necesario desde Eclipse accediendo al Android SDK and
AVD Manager. En mi caso, utilizar el paquete correspondiente a Android 2.1 (API 7) + APIs de
Google:
Para poder
p probarr nuestras aplicaciones
a en el emullador tambin tendremoos que crearr un nuevo
e paquete como targett:
AVD que utilice este

Y porr supuesto, al
a crear nuesstro proyectto de Eclipse tambin teendremos quue seleccionnarlo como
target en las propiiedades:
Con toodo esto ya tendramos
t c
creado nuesttro proyecto de Eclipse y estaramos preparados para poder
ejecutaar aplicacion mulador sobrre la versinn correcta dee Android y las APIs neccesarias de
nes en el em
Googlle. Por tanto,, ya podemos centrarnoss en la utilizaacin de dichhas APIs.

Esto ltimo
tambiin requiere algunos passos previos. Para poder utilizar
u la API
A de Google Maps se
requieere la obtencin previa de
d una clave de uso (APII Key), que estar
e asociada al certificcado con el
que firrmamos digiitalmente nuuestra aplicaccin. Esto quuiere decir que
q si cambiaamos el certiificado con
el quee firmamos nuestra applicacin (allgo que se hace norm malmente como paso previo a la
publiccacin de la aplicacin en
e el Markeet) tendremoos que cambbiar tambinn la clave dee uso de la
API.

En el tema de lo os certificadoos no voy a entrar muucho puesto que lo trataaremos en un u artculo


posterrior, por ahora tan slo diremos que durantee la construuccin y deepuracin de d nuestras
aplicacciones en ell emulador de Android se utiliza automticam
a mente un certtificado de depuracin
d
creadoo por defecto. Por tantoo, para podeer depurar applicaciones en el emulaador que haggan uso de
Googlle Maps tenddremos que solicitar
s una clave asociaada a nuestroo certificadoo de depuraciin.

Para ello,
e en prim
mer lugar deebemos locallizar el fichhero donde se
s almacenann los datos de nuestro
certificcado de dep
puracin, llaamado debug.keystore. Podem mos saber la ruta de este fichero
accediiendo a las preferencias
p de Eclipse, seccin Anddroid, apartaado Build (m
mostrado en la
l siguiente
imagen), y copiar la ruta que aparece
a en ell campo Deefault Debugg Keystore:
Una vez
v conocem mos la localiizacin del fichero
f debuug.keystore, vamos a acccder a l mediante
m la
herram
mienta keyt tool.exe de java parra obtener el hash MD55 del certificcado. Esto lo l haremos
desde la linea de comandos
c dee Windows mediante
m el siguiente
s com
mando (clickk para ampliiar):

Copiarremos el datto que aparece identificaado como H Huella digitaal de certificaado (MD5) y con ste
accedeeremos a laa web de Google (htttp://code.gooogle.com/anndroid/maps--api-signup.hhtml) para
solicittar una clavee de uso de la
l API de Google Maps para depuraar nuestras aplicaciones
a (Insisto, si
posterriormente vaamos a publlicar nuestraa aplicacin en el Markket deberem mos solicitar otra clave
q utilicemos). Dicha web
asociaada al certifiicado real que w nos sollicitar la marcam MD5 de nuestro
certificcado y nos proporcionar
p r la clave dee uso de la API,
A como see muestra enn la siguientee imagen:
Con esto, terminamos con todos los pasos previos para la utilizacin de los servicios de Google
Maps dentro de nuestras aplicaciones Android.

Una vez contamos con la clave de uso de la API, la inclusin de mapas en nuestra aplicacin es una
tarea relativamente sencilla y directa. En este primer artculo sobre mapas nos vamos a limitar a
mostrar el mapa en la pantalla inicial de la aplicacin, habilitaremos su manipulacin
(desplazamientos y zoom), y veremos cmo centrarlo en una coordenadas concretas. En prximos
artculos aprenderemos a manipular de forma dinmica estos mapas, a mostrar marcas sobre ellos, y
a realizar otras operaciones ms avanzadas.

Para incluir un mapa de Google Maps en una ventana de nuestra aplicacin utilizaremos el control
MapView. Estos controles se pueden aadir al layout de la ventana como cualquier otro control
visto hasta el momento, tan slo teniendo en cuenta que tendremos que indicar la clave de uso de
Google Maps en su propiedad android:apiKey como se muestra a cotinuacin:

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


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<com.google.android.maps.MapView
android:id="@+id/mapa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="0ss-5q6s3FKYkkp3atMUH..."
android:clickable="true" />

</LinearLayout>

Como vemos, adems de la propiedad apiKey, tambin hemos establecido la propiedad


clickable con valor true,de forma que podamos interactuar con el control mediante gestos con
el dedo (por ejemplo, para desplazarnos por el mapa).

Este tipo de controles tienen la particularidad de que slo pueden ser aadidos a una actividad de
tipo MapActivity, por lo que pantalla de la aplicacin en la que queramos incluir el mapa debe
heredar de esta clase. As, para nuestro caso de ejemplo, vamos a hacer que la clase principal herede
de MapActivity, como vemos en el siguiente cdigo:

package net.sgoliver.android;

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;

import android.os.Bundle;

public class AndroidMapas extends MapActivity {

private MapView mapa = null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//Obtenemos una referencia al control MapView


mapa = (MapView)findViewById(R.id.mapa);

//Mostramos los controles de zoom sobre el mapa


mapa.setBuiltInZoomControls(true);
}

@Override
protected boolean isRouteDisplayed() {
return false;
}
}

Como vemos, si nuestra clase hereda de MapActivity debemos implementar obligatoriamente el


mtodo isRouteDisplayed(), cuyo valor de retorno debe ser true slo en caso de que
vayamos a representar algn tipo de informacin de ruta sobre el mapa (esto no se trata de ningn
tema tcnico, sino tan slo legal, para cumplir los trminos de uso de la API de Google Maps). En
este primer artculo nos limitaremos a mostrar el mapa en la pantalla principal de la aplicacin, por
lo que por el momento devolveremos false.

Adems de esto, en el mtodo onCreate() llamaremos al mtodo


setBuiltInZoomControls() sobre la referencia al control MapView para mostrar los
controles de zoom estandar sobre el mapa, de forma que podamos acercar y alejar la vista del mapa.
Con esto, ya tendramos un mapa completamente funcional funcionando en nuestra aplicacin.

Para ejecutar la aplicacin de ejemplo sobre el emulador de Android an nos queda un paso ms,
modificar el fichero AndroidManifest.xml. Por un lado, tendremos que especificar que vamos a
hacer uso de la API de Google Maps (mediante la clusula <uses-library>), y en segundo
lugar necesitaremos habilitar los permisos necesarios para acceder a Internet (mediante la clusula
<uses-permission>). En el siguiente fragmento de cdigo vemos cmo quedara nuestro
AndroidManifest tras aadir estas dos lineas:

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


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sgoliver.android"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />

<application
android:icon="@drawable/icon"
android:label="@string/app_name">

<uses-library android:name="com.google.android.maps" />

<activity android:name=".AndroidMapas"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:
:name="android.inten
nt.category
y.LAUNCHER
R" />
</i
intent-filter>
</activ
vity>
<
</applicati
ion>

<
<uses-permi
ission and
droid:name=
="android.permission
n.INTERNET"
" />

</man
nifest>

Tras estos dos cambios yaa podemos ejecutar ell proyecto de Eclipsee sobre el emulador.
Comprobaremos cmo podeemos moveernos por el e mapa y hacer zoom con los controles
correspondientes:

En loss prximos artculos


a aprrenderemos a manipular las caracterrsticas de esste mapa dessde nuestro
cdigoo a travs dee sus mtodoos y propieddades, verem
mos cmo aadir marcass visuales paara resaltar
lugarees especficos en el mapaa, y comentaaremos algunnas otras opcciones ms avanzadas.
a

Podiss descargar el
e cdigo fueente del artcculo desde esste enlace.

Maapas en And
droid (II):
( C
Contro
ol Map
pView
Por sggoliver on 27
7/05/2011 enn Android, Programacinn

En el artculo annterior del curso,


c ya diimos los prrimeros pasoos necesarioos para connfigurar un
proyeccto de Eclippse de form
ma que pudiramos utiliizar la API de Google Maps desdde nuestras
aplicacciones. Vim
mos ademss cmo crear una apliicacin de ejemplo muy m bsica en la que
mostrbamos un mapa y perrmitamos su manipulaccin mendiaante los gesstos del usuuario o los
controoles de zoom
m por defectoo.

En estte artculo nos


n centrareemos en com
mentar los diferentes
d m
mtodos y prropiedades del
d control
MapVi iew con loss que podrem
mos manipular un mapa desde
d el cddigo de nuesttra aplicacin.
Veamos en primer lugar cmo ajustar algunas propiedades del control para ajustarlo a nuestras
necesidades. Por defecto, cuando aadimos un control MapView a nuestra aplicacin ste muestra
en el modo de mapa tradicional, sin embargo, el control tambin nos permite cambiar por ejemplo a
vista de satlite, marcar las zonas disponibles en StreetView, o mostrar la informacin del trfico.
Esto lo conseguimos mediante los siguientes mtodos:

setSatellite(true)
setStreetView(true)
setTraffic(true)

Por supuesto existen tambin otros tres mtodos para consultar el estado de cada uno de estos
modos: isSatellite(), isStreetView() e isTraffic().

En este artculo voy a continuar ampliando la aplicacin de ejemplo que construimos en el artculo
anterior, al que aadiremos varios botones para realizar diferentes acciones. As, por ejemplo vamos
a incluir un botn para alternar en nuestro mapa entre vista normal y vista de satlite.

1
2 private Button btnSatelite = null;
3 //...
4 btnSatelite = (Button)findViewById(R.id.BtnSatelite);
//...
5 btnSatelite.setOnClickListener(new OnClickListener() {
6 @Override
7 public void onClick(View arg0) {
8 if(mapa.isSatellite())
mapa.setSatellite(false);
9 else
10 mapa.setSatellite(true);
11 }
12});
13

Si ejecutamos la aplicacin en este momento y probamos el nuevo botn veremos cmo se visualiza
sin problemas nuestro mapa en vista de satlite:
Ademms de la elecccin del tipo de vista dee mapa a moostrar en el control,
c tambbin podemoos elegir si
querem
mos mostraar controless de zoom m sobre el mapa. Paara ello llaamaremos al a mtodo
setBuuiltInZoo omControls() indiccando si queeremos que se s muestrenn o no los coontroles de
zoom:

1//Moostramos lo
os controles de zoomm sobre el mapa
2 mapa
a.setBuiltI
InZoomCont rols(true)
);

Al margen
m de las
l propieddades para personalizaar el aspeccto grfico del controol tambin
disponndremos de varios
v mtoddos para connsultar la infformacin geeogrfica vissualizada enn el mismo.
As,poor ejemplo, podremos
p saaber las coorrdenadas geoogrficas en las que est centrado actualmente
el maapa [mtod do getMap pCenter()] y el nivel de zooom que esst aplicadoo [mtodo
getZo oomLevel( ()].

1
//Ob
btenemos el
l centro del
d mapa
2GeoPPoint loc = mapa.getMapCenter(
();
3
4//Laatitud y Lo
ongitud (en microgra
ados)
5int lat
l = loc.getLatitud
deE6();
6int lon
l = loc.getLongitu
udeE6();
7
//Nivel de zoo
om actual
8int zoom
z = mapa.getZoomL
Level();
9

Como vemos en el e cdigo annterior, las coordenadas


c del centro del
d mapa se obtienen enn forma de
objetoo GeoPoint t, que encaapsula los valores
v de laatitud y lonngitud expreesados en microgrados
m
(gradoos * 1E6). Estos
E valorees se puedenn obtener mediante
m los mtodos ge etLatitud deE6() y
getLo ongitudeE E6() respectivamente.
Por su parte, el nivel de zoom se obtiene como un valor entero entre 1 y 21, siendo 21 el que ofrece
mayor nivel de detalle en el mapa.

Sin embargo, si miramos la documentacin de la API de la clase MapView veremos que no existen
mtodos para modificar estos valores de centro y zoom del mapa. Cmo podemos entonces
modificar la localizacin mostrada en el mapa? Pues bien, para esto habr que acceder en primer
lugar al controlador del mapa, algo que conseguimos mediante el mtodo getController().
Este mtodo nos devolver un objeto MapController con el que s podremos modificar los
datos indicados. As, contaremos con los mtodos setCenter() y setZoom() al que podremos
indicar las coordenadas centrales del mapa y el nivel de zoom deseado.

Vamos a incluir en la aplicacin de ejemplo un nuevo botn para centrar el mapa sobre un punto
determinado (Sevilla) y aplicaremos un nivel de zoom (10) que nos permita distinguir algo de
detalle.

1
2 private Button btnCentrar = null;
3 private MapController controlMapa = null;
4 //...
5 btnCentrar = (Button)findViewById(R.id.BtnCentrar);
6 //...
controlMapa = mapa.getController();
7 //...
8 btnCentrar.setOnClickListener(new OnClickListener() {
9 @Override
10 public void onClick(View arg0) {
Double latitud = 37.40*1E6;
11
Double longitud = -5.99*1E6;
12
13 GeoPoint loc =
14 new GeoPoint(latitud.intValue(), longitud.intValue());
15
16 controlMapa.setCenter(loc);
17 controlMapa.setZoom(10);
}
18});
19
20

Como vemos, para establecer el punto central del mapa construiremos un objeto GeoPoint a
partir de los valores de latitud y longitud y lo pasaremos como parmetro al mtodo
setCenter().
Si ejeccutis la apliicacin en ell emulador podris
p compprobar que funciona
fu perffectamente de
d la forma
esperaada, sin emb bargo, el despplazamientoo y zoom a la
l posicin y nivel indiccados se hace de forma
instanttanea, sin ningn tipo dee animacin..

Para resolver
r estoo, la API noss ofrece otraa serie de mtodos
m que nos
n permitirrn desplazaarnos a una
posicin especficca de forma progresiva
p y a subir o bajar
b el nivell de zoom dee forma aniimada, tal
como se realizaa al pulsar los botonnes de zooom del proopio mapa. Estos mtodos son
anima ateTo(Geo oPoint), que despplazar el mapa hassta un puunto determ minado, y
zoomI In()/zoom mOut() quee aumentar o disminuir en 1 el nivvel de zoom de d forma proogresiva.

Tenienndo esto en cuenta, aaadamos un nuevo


n botnn para hacerr algo anlogo al anterior pero de
forma progresiva:

1 private Button btnAnima ar = null;


//...
2 btnnAnimar = (Button)fi
( ndViewByIdd(R.id.BtnAnimar);
3 //...
4 btnnAnimar.set
tOnClickListener(new w OnClickLi
istener() {
5 @Override
e
6 public void onClick
k(View arg00) {
Doubl
le latitudd = 37.40*11E6;
7 Doubl
le longitud = -5.99* *1E6;
8
9 GeoPo
oint loc =
10 new
n GeoPoin nt(latitud
d.intValue((), longitu
ud.intValu
ue());
11
12 contr
rolMapa.animateTo(lo oc);
13
int zoomActual = mapa.getZoomLevel l();
14
15 int i=zoomA
for(i Actual; i<10; i++)
16 controlMap
c a.zoomIn());
17 }
18});
19
20
21

Por ltimo, disponemos de otro mtodo que en ocasiones puede ser interesante y que nos permitir
desplazar el mapa, no a una posicin especfica, sino un determinado nmero de pixeles en una u
otra direccin, al igual que conseguiramos mediante gestos con el dedo sobre el mapa. Este mtodo
se llama scrollBy() y recibe como parmetros el nmero de pixeles que queremos desplazarnos
en horizontal y en vertical.

As, por ejemplo, podemos aadir un nuevo botn a la aplicacin de ejemplo, que desplace el mapa
40 pxeles hacia la derecha y hacia abajo, de la siguiente forma:

1
2 private Button btnMover = null;
//...
3 btnMover = (Button)findViewById(R.id.BtnMover);
4 //...
5 btnMover.setOnClickListener(new OnClickListener() {
6 @Override
7 public void onClick(View arg0) {
controlMapa.scrollBy(40, 40);
8 }
9 });
10

En este artculo hemos aprendido a manipular desde nuestro cdigo un control MapView, tanto a
travs de sus propiedades como de las acciones disponibles desde su controlador. En el prximo
artculo veremos cmo podemos aadir capas a nuestro mapa de forma que podamos dibujar sobre
l por ejemplo marcas de posicin, o responder eventos de pulsacin sobre el control.

El cdigo fuente de este artculo podis descargarlo desde este enlace.

Mapas en Android (III): Overlays (Capas)


Por sgoliver on 27/06/2011 en Android, Programacin

Seguimos con nuestro Curso de Programacin Android. En los dos artculos anteriores dedicados a
la visualizacin y manipulacin de mapas en nuestras aplicaciones Android (parte I, parte II) ya
vimos cmo mostrar de forma bsica un mapa y cmo manipularlo desde nuestro cdigo para
realizar las acciones ms frecuentes, como por ejemplo centrarlo o desplazarlo a una posicin
determinada o establecer el nivel de zoom. Nos qued pendiente comentar cmo podemos aadir
nuestra propia informacin a un mapa y cmo podemos responder a eventos de pulsacin sobre el
control. En este artculo nos ocuparemos de ambas cosas.

Empecemos por el principio. Para incluir informacin personalizada sobre un control MapView
necesitaremos aadir sobre ste nuevas capas (overlays), donde dibujaremos toda la informacin
adicional. Se puede incluir cualquier tipo de informacin en estas nuevas capas, por ejemplo
indicaciones de ruta, marcadores, notas de texto

El primer paso para definir una nueva capa de informacin ser crear una clase java que derive de la
clase Overlay. En esta nueva clase sobrescribiremos el mtodo draw(), que es el lugar donde
deberemos dibujar toda la informacin a incluir sobre el mapa en esta capa (por supuesto podemos
aadir varias capas al mapa).

Por mostrar un ejemplo vamos a seguir trabajando con la misma apliacin de ejemplo del artculo
anterior, al que aadiremos algn marcador sobre el mapa. Para no complicar mucho el ejemplo,
aadiremos tan slo un marcador sobre unas coordenadas fijas (las mismas coordenadas sobre las
que aparece centrado el mapa cuando se inicia la aplicacin).

El mtodo draw() recibe como parmetro un objeto Canvas sobre el que podemos dibujar
directamente utilizando los mtodos de dibujo que ya hemos utilizado en otras ocasiones
(drawLine(), drawCircle(), drawText(), drawBitmap()). Sin embargo, a todos
estos mtodos de dibujo hay que indicarles las coordenadas en pixels relativos a los bordes del
control sobre el que se va a dibujar. Sin embargo, nosotros lo que tenemos en principio son unas
coordenadas de latitud y longitud expresadas en grados. Cmo podemos traducir entre unas
unidades y otras para saber dnde dibujar nuestros marcadores? Pues bien, para solucionar esto
Android nos proporciona la clase Projection, con la cual podremos hacer conversiones precisas
entre ambos sistemas de referencia.

Veamos lo sencillo que es utilizar esta clase Projection. Partiremos de nuestros valores de
latitud y longitud expresados en grados y a partir de ellos crearemos un objeto GeoPoint que los
encapsule. Por otro lado, crearemos un nuevo objeto Projection mediante el mtod
getProjection() de la clase MapView (objeto recibido tambin como parmetro del mtodo
draw()). Este nuevo objeto Projection creado tendr en cuenta la posicin sobre la que est
centrada el mapa en este momento y el nivel de zoom aplicado para convertir convenientemente
entre latitud-longitud en grados y coordenadas x-y en pixeles. Para hacer la conversin, llamaremos
al mtodo toPixels() que devolver el resultado sobre un objeto Point de salida.

1
Double latitud = 37.40*1E6;
2Double longitud = -5.99*1E6;
3
4Projection projection = mapView.getProjection();
5GeoPoint geoPoint =
6 new GeoPoint(latitud.intValue(), longitud.intValue());
7
Point centro = new Point();
8projection.toPixels(geoPoint, centro);
9

Una vez que tenemos las coordenadas convertidas a pixeles, ya podemos dibujar sobre ellas
utilizando cualquier mtodo de dibujo. Veamos en primer lugar cmo podramos por ejemplo aadir
un crculo y una etiqueta de texto sobre las coordenadas calculadas:

1//Definimos el pincel de dibujo


2Paint p = new Paint();
3p.setColor(Color.BLUE);
4
//Marca Ejemplo 1: Crculo y Texto
5canvas.drawCircle(centro.x, centro.y, 5, p);
6canvas.drawText("Sevilla", centro.x+10, centro.y+5, p);
7

Para que nadie se pierda, veamos el cdigo completo de nuestra clase derivada de Overlay (en un
alarde de creatividad la he llamado OverlayMapa) hasta el momento:

1
2
3 public class OverlayMapa extends Overlay {
private Double latitud = 37.40*1E6;
4 private Double longitud = -5.99*1E6;
5
6 @Override
7 public void draw(Canvas canvas, MapView mapView, boolean shadow)
8 {
9 Projection projection = mapView.getProjection();
GeoPoint geoPoint =
10 new GeoPoint(latitud.intValue(), longitud.intValue());
11
12 if (shadow == false)
13 {
14 Point centro = new Point();
projection.toPixels(geoPoint, centro);
15
16
//Definimos el pincel de dibujo
17 Paint p = new Paint();
18 p.setColor(Color.BLUE);
19
20 //Marca Ejemplo 1: Crculo y Texto
21 canvas.drawCircle(centro.x, centro.y, 5, p);
canvas.drawText("Sevilla", centro.x+10, centro.y+5, p);
22 }
23 }
24}
25
26

Una vez definida nuestra capa de informacin personalizada debemos aadirla al mapa que se va a
mostrar en la aplicacin de ejemplo. En nuestro caso esto lo haremos desde el mtodo
onCreate() de la actividad principal, tras obtener la referencia al control MapView. Para ello,
obtendremos la lista de capas del mapa mediante el mtodo getOverlays(), crearemos una
nueva instancia de nuestra capa personalizada OverlayMapa, y la aadiremos a la lista mediante
el mtodo add(). Finalmente llamaremos al mtodo postInvalidate() para redibujar el
mapa y todas sus capas.

//Obtenemos una referencia a los controles


1 mapa = (MapView)findViewById(R.id.mapa);
2
3 //...
4
5 //AAadimos la
a capa de marcadoress
List<Overlay>
> capas = mapa.getOvverlays();
6 OveerlayMapa om
o = new OvverlayMapa();
7 cappas.add(om)
);
8 mappa.postInva
alidate();
9
10

Si ejeecutamos la aplicacin en este momento podrremos compprobar cmoo se muestraa un mapa


centraado en nuestrras coordenaadas y se diibuja correcttamente la innformacin de nuestra nueva
n capa
sobre l (lo resalto
o en rojo).

Una posible
p variaante podra ser
s incluir allgn tipo de marcador grfico
g sobree el mapa, al
a estilo del
propioo Google Maps.
M Para conseguir
c essto, deberemmos colocarr la imagenn del marcaddor en las
distinttas carpetas /res/drawabble y dibujaarlo sobre laa capa utilizaando el mtoodo drawBi itmap().
Para cargar
c el bitm
map a partir del fichero de
d imagen colocado
c en lal carpeta dee recursos uttilizaremos
la classe BitmapF Factory y su mtodo decodeRes
d source(),, al que tenddremos que pasarp como
parmmetro el ID del d recurso. Veamos cmo quedara esto en el e cdigo deel mtodo draw() de
nuestrra capa perssonalizada [ppara mayor claridad, ms m abajo podis descaargar como siempre el
cdigoo fuente com mpleto]:

1//Ma
arca Ejempl
lo 2: Bitm
map
2Bitm
map bm = Bi
itmapFactory.decodeR
Resource(
3 mapVie
ew.getResources(),
4 R.draw
wable.marcador_googl
le_maps);
5
6canv
vas.drawBit
tmap(bm, centro.x - bm.getWidt
th(),
o.y - bm.getHeight()
centro ), p);
7
En mii caso, como milar al que se muestra en Google
o imagen paara el marcaddor he utilizzado una sim
Maps. Si ejecutam
mos ahora la aplicacin, veremos cmo hemos sustituido
s la marca textuual anterior
por el nuevo marccador grficoo:

Ya hemmos visto loo sencillo quue resulta moostrar inform


macin persoonalizada sobre un mapaa. Tan slo
nos falta saber cm
mo podemoss tambin reaacionar ante pulsacioness del usuario sobre nuesttro control.

Para conseguir
c essto, tendrem
mos que sobbrescribir tam mbin el mtodo onTa ap() de nuuestra capa
personnalizada. Esste mtodo nos proporcciona directaamente com mo parmetrro las coorddenadas de
latitudd y longitud sobre las quue el usuarioo ha pulsadoo (en forma de
d objeto Ge
eoPoint), con lo que
podrem mos actuar en
e consecuenncia desde nuuestra aplicaacin.

Como ejemplo, nosotros


n nos vamos a liimitar a mostrar en unaa notificacin de tipo Toast,
T las
enadas
coorde sobre
e las que se h
ha pulsado.

1
2 @Ovverride
pub
blic boolean onTap(GeoPoint poiint, MapView mapVieww)
3 {
4 Context contexto
c = mapView.ggetContext();
5 sg = "Lat: " + point.getLatitu
String ms udeE6()/1E66 + " - "+
6 "Lon:
: " + point
t.getLongi tudeE6()/1
1E6;
7
ast = Toast.makeText
Toast toa t(contexto, msg, Toaast.LENGTH_
_SHORT);
8
toast.sho
ow();
9
10 return true;
11}
12

El mttodo debe devolver el valor


v true siempre
s que haya trataddo la pulsacin y NO seaa necesario
notificcarla al resto
o de capas o al propio conntrol MapVi
iew, y fals se en caso contrario.
c
Si ejeccutamos de nuevo la applicacin dee ejemplo y probamos a pulsar sobbre cualquierr lugar del
mapa mostrado veeremos comoo se muestraan las coordeenadas que se s han selecccionado. Estoo se podra
utilizaar por ejempllo para detecctar si se pullsa sobre algguno de nuesstros marcaddores con el objetivo
o de
mostraar informacin adicionall.

Podiss descargar el
e cdigo fueente completto de este arttculo pulsanndo este enlaace.

Con esto
e terminaamos por el momento la l serie de artculos
a deddicados a laa geolocalizzacin y la
visualiizacin de mapas
m en nueestras aplicaaciones Andrroid, aunquee no descartoo dedicar algguno ms a
este teema un poco o ms adelannte. Como siempre
s estooy abierto a propuestas sobre el conntenido del
curso, podis utilizar los comeentarios del blog o mi diireccin de correo
c electrrnico para hacrmelas
h
llegar..

Por cieerto, an esttoy decidienddo los prxim


mos temas a tratar en el curso. Sugerencias?

Content Proviiders en
e And
droid (I):
Construcccin
Por sggoliver on 28
8/08/2011 enn Android, Programacinn

En estte nuevo arttculo del Curso de Proogramacin en e Android que estamoos publicanddo vamos a
tratar el
e temido [o a veces incoomprendido]] tema de loss Content Prroviders.

Un Coontent Proviider no es ms
m que el mecanismo
m p
proporcionad do por la plaataforma Anndroid para
permittir compartirr informacin entre apliccaciones. Unna aplicacin que desee que todo o parte de la
inform
macin que almacena
a estt disponiblee de una form
ma controladda para el reesto de aplicaaciones del
sistem
ma deber prooporcionar un
u content prrovider a travvs del cul se pueda reaalizar el acceeso a dicha
inform
macin. Estee mecanism mo es utilizado por muuchas de laas aplicacioones estandaard 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.

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


3 Antonio 900123456 email1@correo.com
7 Jose 900123123 email2@correo.com
9 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 a
SQLiteOpenHelper, 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:

public class ClientesSqliteHelper extends SQLiteOpenHelper {


1
2 //Sentencia SQL para crear la tabla de Clientes
3 String sqlCreate = "CREATE TABLE Clientes " +
4 "(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
5 " nombre TEXT, " +
6 " telefono TEXT, " +
" email TEXT )";
7
8 public ClientesSqliteHelper(Context contexto, String nombre,
9 CursorFactory factory, int version) {
10
11 super(contexto, nombre, factory, version);
12 }
13
@Override
14 public void onCreate(SQLiteDatabase db) {
15 //Se ejecuta la sentencia SQL de creacin de la tabla
16 db.execSQL(sqlCreate);
17
18 //Insertamos 15 clientes de ejemplo
19 for(int i=1; i<=15; i++)
{
20 //Generamos los datos de muestra
21 String nombre = "Cliente" + i;
22 String telefono = "900-123-00" + i;
23 String email = "email" + i + "@mail.com";
24
//Insertamos los datos en la tabla Clientes
25 db.execSQL("INSERT INTO Clientes (nombre, telefono, email) " +
26 "VALUES ('" + nombre + "', '" + telefono +"', '" + email
27+ "')");
28 }
29 }
30
@Override
31 public void onUpgrade(SQLiteDatabase db, int versionAnterior, int
32versionNueva) {
33 //NOTA: Por simplicidad del ejemplo aqu utilizamos directamente la
34 opcin de
// eliminar la tabla anterior y crearla de nuevo vaca con el
35
nuevo formato.
36 // Sin embargo lo normal ser que haya que migrar datos de la
37tabla antigua
38 // a la nueva, por lo que este mtodo debera ser ms elaborado.
39
//Se elimina la versin anterior de la tabla
40 db.execSQL("DROP TABLE IF EXISTS Clientes");
41
42 //Se crea la nueva versin de la tabla
43 db.execSQL(sqlCreate);
44 }
}
45
46
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.

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.ejemplo/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.ejemplo. 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.ejemplo/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.

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.ejemplo/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


2private static final String uri =
3 "content://net.sgoliver.android.ejemplo/clientes";
4
5public 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
2 //Clase interna para declarar las constantes de columna
public static final class Clientes implements BaseColumns
3 {
4 private Clientes() {}
5
6 //Nombres de columnas
7 public static final String COL_NOMBRE = "nombre";
public static final String COL_TELEFONO = "telefono";
8 public static final String COL_EMAIL = "email";
9 }
10

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//Base de datos
2private ClientesSqliteHelper clidbh;
3private static final String BD_NOMBRE = "DBClientes";
4private static final int BD_VERSION = 1;
private static final String TABLA_CLIENTES = "Clientes";
5

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.ejemplo/clientes > Acceso genrico a tabla de clientes


content://net.sgoliver.android.ejemplo/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 llamar CLIENTES) o acceso a cliente por ID (lo llamar CLIENTES_ID). A
continuacin inicializaremos el objeto UriMatcher 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;
private static final int CLIENTES_ID = 2;
3 private static final UriMatcher uriMatcher;
4
5 //Inicializamos el UriMatcher
6 static {
7 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
8 uriMatcher.addURI("net.sgoliver.android.ejemplo", "clientes", CLIENTES);
uriMatcher.addURI("net.sgoliver.android.ejemplo", "clientes/#",
9 CLIENTES_ID);
10}
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 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
2public 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 objeto
uriMatcher, 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
mtodo getLastPathSegment() 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()
de SQLiteDatabase. Esto es sencillo ya que los parmetros son anlogos a los recibidos en el
mtodo query() del content provider.
1
2 @Override
3 public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
4
5
//Si es una consulta a un ID concreto construimos el WHERE
6 String where = selection;
7 if(uriMatcher.match(uri) == CLIENTES_ID){
8 where = "_id=" + uri.getLastPathSegment();
9 }
10
SQLiteDatabase db = clidbh.getWritableDatabase();
11
12 Cursor c = db.query(TABLA_CLIENTES, projection, where,
13 selectionArgs, null, null, sortOrder);
14
15 return c;
16 }
17

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:

@Override
1 public int update(Uri uri, ContentValues values,
2 String selection, String[] selectionArgs) {
3
4 int cont;
5
6 //Si es una consulta a un ID concreto construimos el WHERE
String where = selection;
7 if(uriMatcher.match(uri) == CLIENTES_ID){
8 where = "_id=" + uri.getLastPathSegment();
9 }
10
11 SQLiteDatabase db = clidbh.getWritableDatabase();
12
13 cont = db.update(TABLA_CLIENTES, values, where, selectionArgs);
14
return cont;
15}
16
17@Override
18public int delete(Uri uri, String selection, String[] selectionArgs) {
19
20 int cont;
21
//Si es una consulta a un ID concreto construimos el WHERE
22 String where = selection;
23 if(uriMatcher.match(uri) == CLIENTES_ID){
24 where = "_id=" + uri.getLastPathSegment();
}
25
26 SQLiteDatabase db = clidbh.getWritableDatabase();
27
28 cont = db.delete(TABLA_CLIENTES, where, selectionArgs);
29
30 return cont;
31}
32
33
34
35
36

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
return newUri;
12}
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

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
2 @Override
3 public String getType(Uri uri) {
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:
return "vnd.android.cursor.item/vnd.sgoliver.cliente";
11 default:
12 return null;
13 }
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.ejemplo"/>
8
</application>
9
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.

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.

El cdigo fuente completo de la aplicacin lo publicar junto al siguiente artculo, pero por el
momento podis descargar desde este enlace los fuentes de las dos clases implementadas en este
artculo y el fichero AndroidManifest.xml modificado.

Content Providers en Android (II): Utilizacin


Por sgoliver on 31/08/2011 en Android, Programacin

En el artculo anterior del Curso de Programacin en Android vimos como construir un content
provider personalizado 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 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
2 //Columnas de la tabla a recuperar
3 String[] projection = new String[] {
4 Clientes._ID,
Clientes.COL_NOMBRE,
5 Clientes.COL_TELEFONO,
6 Clientes.COL_EMAIL };
7
8 Uri clientesUri = ClientesProvider.CONTENT_URI;
9
10ContentResolver cr = getContentResolver();
11
12//Hacemos la consulta
Cursor cur = cr.query(clientesUri,
13 projection, //Columnas a devolver
14 null, //Condicin de la query
15 null, //Argumentos variables de la query
16 null); //Orden de los resultados
17

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 String nombre;
3 String telefono;
4 String email;
5
6 int colNombre = cur.getColumnIndex(Clientes.COL_NOMBRE);
int colTelefono = cur.getColumnIndex(Clientes.COL_TELEFONO);
7 int colEmail = cur.getColumnIndex(Clientes.COL_EMAIL);
8
9 txtResultados.setText("");
10
11 do
12 {
nombre = cur.getString(colNombre);
13 telefono = cur.getString(colTelefono);
14 email = cur.getString(colEmail);
15
16 txtResultados.append(nombre + " - " + telefono + " - " + email + "\n");
17
18 } while (cur.moveToNext());
}
19
20
21
22

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.

1ContentValues values = new ContentValues();


2
3values.put(Clientes.COL_NOMBRE, "ClienteN");
4values.put(Clientes.COL_TELEFONO, "999111222");
5values.put(Clientes.COL_EMAIL, "nuevo@email.com");
6
7ContentResolver cr = getContentResolver();
8
cr.insert(ClientesProvider.CONTENT_URI, values);
9

Por ltimo, y ms sencillo todava, la eliminacin de registros la haremos directamente utilizando el


mtodo delete() 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).

1ContentResolver cr = getContentResolver();
2
3cr.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.
Con esto,
e hemos visto lo senncillo que reesulta accedeer a los datoos proporcioonados por un content
providder. Pues bieen, ste es el mismo meccanismo que podemos uttilizar para acceder
a a muuchos datos
de la propia
p platafforma Androoid. En la documentaci
d n oficial deel paquete android.p
a provider
podemmos consultaar los datos que tenem mos disponibbles a travss de este mecanismo,
m entre ellos
enconttramos por ejemplo: el e historial de llamadaas, la agendda de contaactos y telfonos, las
media (audio y video), o el historial y la lista de favoritos
bibliottecas multim f dell navegador.

Por veer un ejempplo de acceso a este tipoo de datos, vamos a realizar una consulta
c al historial
h de
llamaddas del dispositivoo, para lo que accedereemos al content provider
andro oid.provi ider.CallLog.

En priimer lugar vamos


v a reggistrar variass llamadas en
e el emuladdor de Andrroid, de form ma que los
resultaados de la co
onsulta al hiistorial de llamadas conttenga algunoos registros. Haremos por ejemplo
varias llamadas salientes
s dessde el emullador y sim mularemos vaarias llamaddas entrantes desde el
DDMS S. Las primmeras son sencillas, sim mplemente vev al emulaador, accedee al telfonno,marca y
descueelga igual quue lo haras en un dispoositivo fsicoo. Y para em
mular llamaddas entrantess podremos
hacerlo una vez ms
m desde Ecclipse, acceddiendo a la vista
v del DDDMS. En estaa vista, si acccedemos a
la seccin Emulator Controol veremos un apartaddo llamado Telephony Actions. Desde
D ste,
podemmos introduccir un nm mero de telfono origenn cualquieraa y pulsar el botn C Call para
consegguir que nueestro emuladdor reciba unna llamada entrante.
e Sinn aceptar la llamada
l en elemulador
e
pulsarremos Hang g Up para teeminar la llaamada simulaando as unaa llamada peerdida.
Hechoo esto, proceedemos a reaalizar la conssulta al histoorial de llamadas utilizanndo el contennt provider
mos un botnn ms a la aplicacin de ejemplo.
indicaado, y para elllo aadirem e

Consuultando la doocumentacin del contennt provider veremos


v quee podemos extraer
e difereentes datos
relacioonados con la lista de llamadas. Nosotros
N noss quedaremoos slo conn dos signifiicativos, el
nmerro origen o destino de la llamada,, y el tipo de llamada (entrante, saliente,
s perrdida). Los
nombrres de estas columnas se almacenaan en las coonstantes Ca alls.NUMB BER y Cal lls.TYPE
respecctivamente.

Decidiido esto, acttuaremos igual que antees. Definiremmos el arrayy con las coolumnas quee queremos
recupeerar, obtendrremos la referencia al content resoolver y ejeccutaremos laa consulta lllamando al
mtoddo query() ). Por ltim
mo, recorrem
mos el cursorr obtenido y procesamoos los resultaados. Igual
que anntes, lo nicco que haremos ser esscribir los reesultados al cuadro de texto situaddo bajo los
botonees. Veamos ele cdigo:

1 Strring[] projjection = new String[] {


Calls.TYP
PE,
2 Calls.NUM
MBER };
3
4 Uri llamadasU Uri = Calls.CONTENT T_URI;
5
6 ConntentResolvver cr = getContentR Resolver();
7
8 Currsor cur = cr.query(llamadasUr ri,
9 proje
ection, // Columnas a devolver
null,
, //Condicin de la query
10 null,
, //Argumentoss variables de la qu
uery
11 null)
); //Orden de los
l resultados
12
13if (cur.moveT
( oFirst())
14 {
int tipo;
15 String ti
ipoLlamada = "";
16 String te
elefono;
17
18 int colTipo = cur.ggetColumnIndex(Callss.TYPE);
19 int colTelefono = cur.getColumnIndex(Calls.NUMBER);
20
21 txtResultados.setText("");
22
do
23 {
24 tipo = cur.getInt(colTipo);
25 telefono = cur.getString(colTelefono);
26
27 if(tipo == Calls.INCOMING_TYPE)
tipoLlamada = "ENTRADA";
28 else if(tipo == Calls.OUTGOING_TYPE)
29 tipoLlamada = "SALIDA";
30 else if(tipo == Calls.MISSED_TYPE)
31 tipoLlamada = "PERDIDA";
32
33 txtResultados.append(tipoLlamada + " - " + telefono + "\n");
34
} while (cur.moveToNext());
35}
36
37
38
39
40
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
utilizando la clusula <uses-permission> correspondiente.

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

Si ejecutamos la aplicacin y realizamos la consulta podremos ver un resultado similar al siguiente:


Y conn esto terminnamos con el tema dediccado a los coontent proviiders. Esperoo que os hayya sido til
para aprender
a a in
ncluir esta fuuncionalidadd a vuestras aplicacioness y a utilizar este mecannismo para
accedeer a datos propios del sisstema.

Podiss descargar ele cdigo fueente compleeto de la apliicacin de ejjemplo construida a lo laargo de los
dos artticulos anterriores pulsanndo este enlaace.

Notificacciones en An
ndroid
d (I): Toast
T
Por sggoliver on 09
9/06/2011 enn Android, Programacinn

Un temma rpido anntes de seguuir con el Cuurso de Progrramacin Anndroid que estamos
e reallizando. En
Androoid existen varias
v formaas de notificaar mensajes al usuario, como por ejemplo
e los cuadros
c de
diloggo modales o las notificcaciones de la bandeja del sistema (o barra dee estado). Peero en este
artcullo nos vamo
os a centrar en
e primer lugar en la forrma ms senncilla de notificacin: loos llamados
Toast.

Un toaast es un mensaje
m que se muestra en pantalla durante unoos segundos al usuario para luego
volverr a desapareecer automtticamente siin requerir ningnn tipo de actuacin por su parte,
p y sin
recibirr el foco en ningn mom mento (o dicho de otra forma, sin interferir enn las accionees que est
realizaando el usuaario en ese momento). Aunque sonn personalizzables, apareecen por deffecto en la
parte inferior de la pantalla, sobre un rectngulo
r g ligeram
gris mente translcido. Por sus
s propias
caracteersticas, estte tipo de nootificacioness son idealess para mostrrar mensajess rpidos y sencillos
s al
usuario, pero por el contrario,, al no requeerir confirmacin por suu parte, no deberan
d utilizarse para
hacer notificacion
n es demasiaddo importantees.

Su utilizacin es muy
m sencillaa, concentrnndose toda la funcionaliidad en la clase Toast. Esta clase
disponne de un mtodo estticoo makeTexxt() al que deberemos pasar como parmetro el e contexto
de la actividad, el
e texto a mostrar,
m y la duracinn del mensaaje, que pueede tomar losl valores
LENGT TH_LONG o LENGTH_ _SHORT, dependiendo del d tiempo que
q queram mos que la notificacin
n
aparezzca en pantalla. Tras obttener una refferencia al objeto
o Toast a travs de
d este mtoddo, ya slo
nos quuedara mosttrarlo en panntalla mediannte el mtodoo show().

Vamos a construiir una aplicaacin de ejeemplo para demostrar el


e funcionam miento de esste tipo de
notificcaciones. Y para empezaar vamos a incluir un botn
b que muuestre un tooast bsico de
d la forma
que accabamos de describir:
d

1
2 btn
nDefecto.se
etOnClickListener(ne ew OnClickL
Listener() {
@Override
e
3 public void onClick
k(View arg00) {
4 Toast
t toast1 =
5 Toast.make
T Text(getAp
pplicationContext(),,
6 "T oast por d
defecto", Toast.LENG
GTH_SHORT);
;
7
t1.show();
toast
8 }
9 });
10

Si ejeccutamos estaa sencilla applicacin en el emuladorr y pulsamoss el botn quue acabamoss de aadir
verem
mos como en n la parte infferior de la pantalla
p apaarece el mennsaje Toast por defectoo, que tras
varios segundos desaparecer automticam mente.

Como hemos com mentado, stte es el commportamientoo por defecto de las nootificacioness toast, sin
embarrgo tambin podemos peersonalizarloo un poco caambiando suu posicin enn la pantallaa. Para esto
utilizaaremos el mtodo
m setGGravity() ), al que podremos
p indicar en quu zona deseeamos que
aparezzca la notificcacin. La zona
z deberem
mos indicarla con alguna de las connstantes definnidas en la
clase Gravity:
G CENTER,
C LE
EFT, BOTTO OM, o conn alguna commbinacin de stas.
Para nuestro
n ejem
mplo vamos a colocar laa notificacin en la zonna central izqquierda de la
l pantalla.
Para ello,
e aadam mos un segunndo botn a la aplicacin de ejempplo que muestre un toastt con estas
caracteersticas:

1
2 btn
nGravity.se
etOnClickListener(neew OnClickL
Listener() {
@Override
e
3 public void onClick
k(View arg0
0) {
4 t toast2 =
Toast
5 Toast.make
T Text(getAp
pplicationContext(),,
6 "Toast con gravity",
g Toast.LENG
GTH_SHORT);
;
7
8 t2.setGrav
toast vity(Gravit
ty.CENTER|Gravity.LEEFT,0,0);
9
toast
t2.show();
10 }
11});
12

Si volvvemos a ejeccutar la apliccacin y pulsamos el nuevo botn veeremos com


mo el toast apparece en la
zona inndicada de la
l pantalla:

Si estoo no es sufficiente y neecesitamos personalizar


p r por complleto el aspeccto de la nootificacin,
Androoid nos ofrecce la posibilidad de deffinir un layoout XML prropio para toast,
t donde podremos
incluirr todos los elementos necesarios
n p
para adaptarr la notificaccin a nuesstras necesiddades. para
nuestrro ejemplo vamos a definnir un layoutt sencillo, coon una imageen y una etiqqueta de textto sobre un
rectnngulo gris:

1 <?x
xml version="1.0" encoding="utf
f-8"?>
<LinearLayout
t
2 x
xmlns:andro
oid="http://schemas..android.com/apk/res
s/android"
3 a
android:id=
="@+id/lytLayout"
4 a
android:lay
yout_width="fill_parrent"
5 android:layout_height="fill_parent"
6 android:orientation="horizontal"
android:background="#555555"
7 android:padding="5dip" >
8
9 <ImageView android:id="@+id/imgIcono"
10 android:layout_height="wrap_content"
11 android:layout_width="wrap_content"
android:src="@drawable/marcador" />
12
13 <TextView android:id="@+id/txtMensaje"
14 android:layout_width="wrap_content"
15 android:layout_height="wrap_content"
16 android:layout_gravity="center_vertical"
17 android:textColor="#FFFFFF"
android:paddingLeft="10dip" />
18
19</LinearLayout>
20
21
22
23

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 qeu 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
@Override
OnClickListener() {
2 public void onClick(View arg0) {
3 Toast toast3 = new Toast(getApplicationContext());
4
5 LayoutInflater inflater = getLayoutInflater();
6 View layout = inflater.inflate(R.layout.toast_layout,
(ViewGroup) findViewById(R.id.lytLayout));
7
8 TextView txtMsg = (TextView)layout.findViewById(R.id.txtMensaje);
9 txtMsg.setText("Toast Personalizado");
10
11 toast3.setDuration(Toast.LENGTH_SHORT);
12 toast3.setView(layout);
toast3.show();
13 }
14});
15
16
17

Si ejeccutamos aho
ora la aplicaacin de ejem mplo y pulssamos el nueevo botn, veremos
v com
mo nuestro
toast aparece
a con la
l estructuraa definida enn nuestro layoout personallizado.

Como podis commprobar, mosstrar notificaaciones de tiipo Toast enn nuestras applicaciones Android
A es
algo de
d lo ms senncillo, y a veeces resultann un elemennto de lo mss interesantee para avisarr al usuario
de determinados eventos.
e

Podiss descargar el
e cdigo de ejemplo de este artculoo pulsando esste enlace.

Notificacciones en An
ndroid
d (II): Barraa de
Esttado
Por sggoliver on 20
0/10/2011 enn Android, Programacinn

Hace algn tiemp po, ya trataamos dentro de este cuurso un prim mer mecanissmo de nottificaciones
disponnibles en la plataforma Android: los llamadoss Toast. Como ya com mentamos, esste tipo de
notificcaciones, aun
nque resultaan tiles y prrcticas en muchas
m ocasiiones, no deberamos utilizarlas en
situaciiones en lass que necessitemos aseggurarnos la atencin deel usuario ya y que no requiere
r de
ningunna intervencin por su parte, se muestran y desapparecen autoomticamentte de la pantalla.

En esste nuevo artculo


a vam
mos a tratar otro tipo de notificacciones algo ms persisstentes, las
notificcaciones de la barra de estado de Android.
A Esttas notificacciones son laas que se muestran
m en
nuestrro dispositivvo cuando recibimos un mensaaje SMS, cuando tennemos actuaalizaciones
disponnibles, cuanndo tenemoss el reprodductor de msica
m abierrto en seguundo plano, Estas
notificcaciones connstan de unn icono y un texto mostrado
m enn la barra de
d estado superior,
s y
adicionalmente un n mensaje algo ms desccriptivo y unna marca de fecha/hora que
q podemos consultar
despleegando la baandeja del siistema. A modo
m de ejem
mplo, cuandoo tenemos unau llamada perdida en
nuestrro terminal, se
s nos muesttra por un lado un icono en la barra de
d estado

y un u mensaje con ms innformacin al desplegarr la bandejaa del sistem


ma, donde enn este caso
nforma del evento que se ha produucido (Misssed call), el
concreeto se nos in e nmero de
d telfono
asociaado, y la feecha/hora del evento. Adems,
A all pulsar sobbre la notifficacin se nos dirige
autom
mticamente ala historial de
d llamadas.

Pues bien,
b aprend
damos a utilizar este tiipo de notifficaciones enn nuestras aplicaciones
a . Vamos a
constrruir para ello
o una aplicaccin de ejemmplo, como siempre
s lo ms
m sencilla posible paraa centrar la
atencin en lo reaalmente impoortante. En este
e caso, el ejemplo vaa a consistir en un nico botn que
generee una notificacin de ejeemplo en la barra
b de estaado, con todoos los elemenntos comenttados y con
la posiibilidad de dirigirnos
d a la propia apliicacin de ejjemplo cuanndo se pulse sobre ella.

Para generar
g notifficaciones en
e la barra de
d estado deel sistema, lo
l primero que
q debemoos hacer es
obteneer una refe
ferencia al servicio ded notificaciones de Android,
A a travs dee la clase
Notif ficationM Manager. Utilizarem mos para ello
e el mtodo gettSystemSe ervice()
indicaando como parmetro el identificador dell servicio corresponddiente, en este caso
Conte ext.NOTIF FICATION_SERVICE.

1//Ob
btenemos un
na referencia al serrvicio de notificaci
iones
2String ns = Co
ontext.NOTIFICATION__SERVICE;
3NotificationMa
anager notManager =
tionManager) getSyst
(Notificat temService(ns);
4

Seguiddamente co onfiguraremoos las caraactersticas de nuestraa notificacin. En primer lugar


estableeceremos el icono y el texto a mosstrar en la barra
b de estaado, y registtraremos la fecha/hora
asociaada a nuestrra notificacin. Con esttos datos coonstruiremoss un objeto Notifica ation. En
nuestrro caso de ejeemplo, vamoos a utilizar un icono preedefinido dee Android (podis utilizaar cualquier
otro), el mensaje de
d ejemplo Alerta!,
y registraremoos la fecha/hhora actual, devuelta
d porr el mtodo
Syste em.curren ntTimeMillis():
1//Configuramos la notificacin
2int icono = android.R.drawable.stat_sys_warning;
3CharSequence textoEstado = "Alerta!";
4long hora = System.currentTimeMillis();
5
6Notification notif =
new Notification(icono, textoEstado, hora);
7

El segundo paso ser utilizar el mtodo setLatestEventInfo() para asociar a nuestra


notificacin la informacin a mostrar al desplegar la bandeja del sistema (ttulo y descripcin) e
indicar la actividad a la cual debemos dirigir al usuario automticamente si ste pulsa sobre la
notificacin. Los dos primeros datos son simples cadenas de texto, pero cmo indicamos la
aplicacin a ejecutar si se pulsa sobre la notificacin?

Para esto ltimo 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
(AndroidNotificacionesActivity.class). Este intent lo utilizaremos para construir el
PendingIntent final mediante el mtodo PendingIntent.getActivity().

Veamos cmo quedara esta ltima parte comentada:

1
2 //Configuramos el Intent
Context contexto = getApplicationContext();
3 CharSequence titulo = "Mensaje de Alerta";
4 CharSequence descripcion = "Ejemplo de notificacin.";
5
6 Intent notIntent = new Intent(contexto,
7 AndroidNotificacionesActivity.class);
8
9 PendingIntent contIntent = PendingIntent.getActivity(
contexto, 0, notIntent, 0);
10
11notif.setLatestEventInfo(
12 contexto, titulo, descripcion, contIntent);
13

Como opciones adicionales, tambin podemos indicar por ejemplo que nuestra notificacin
desaparezca automticamente de la bandeja del sistema cuando se pulsa sobre ella. Esto lo hacemos
aadiendo al atributo flags de nuestra notificacin el valor
Notification.FLAG_AUTO_CANCEL.

Tambin podramos indicar que al generarse la notificacin el dispositivo debe emitir un sonido,
vibrar o encender el LED de estado presente en muchos terminales. Esto lo conseguiramos
aadiendo al atributo defaults de la notificacin los valores DEFAULT_SOUND,
DEFAULT_VIBRATE o DEFAULT_LIGHTS.
1//AuutoCancel: cuando se pulsa la notificain sta de
esaparece
2notif.flags |== Notification.FLAG__AUTO_CANCEL;
3
4//Aadir sonid
do, vibracin y lucees
5 //no
otif.defaul
lts |= Not ification.
.DEFAULT_SOUND;
6 //no
otif.defaul
lts |= Not ification.
.DEFAULT_VIBRATE;
//no
otif.defaul
lts |= Notification..DEFAULT_LIGHTS;
7

Existeen otras mucchas opcionees y personaalizaciones ded stas ltim mas, que se pueden
p conssultar en la
docummentacin official de la cllase Notificaation de Anddroid (atributtos flags y defaults s).

Por lltimo, una vez


v tenemoss completam mente configguradas las opciones de
d nuestra notificacin
n
podem
mos generarlla llamando al mtodo notify() ), pasando como parm metro un iddentificador
nico definido porr nosotros y el objeto No
otificati ion construiido.

1//En
nviar notif
ficacin
2notM
Manager.not
tify(NOTIF_ALERTA_ID
D, notif);

Ya esttamos en dissposicin de probar nuesstra aplicacin de ejempplo. Para elloo vamos a ejecutarla en
el emuulador de An ndroid y pulsamos el bootn que hemmos implemeentado con todo el cdiggo anterior.
Si todoo va bien, deebera apareecer en ese mismo
m momeento nuestra notificacinn en la barraa de estado,
con el icono y textto definidos..

Si ahoora salimos de
d la aplicaccin y despleegamos la bandeja del sistema
s podrremos verificcar el resto
de infoormacin dee la notificacin tal comoo muestra la siguiente im
magen:
Por ltimo,
si pu
ulsamos sobbre la notifficacin se debera abrrir de nuevo automticcamente la
aplicaccin de ejem
mplo. Adem ms, la notificacin tammbin deberra desaparecer de la bandeja
b del
sistem mo lo habamos configuraado en el cddigo con la opcin
ma, tal y com o FLAG
G_AUTO_CA ANCEL.

En definitiva, com
mo podis comprobar
c e bastante sencillo
es s gennerar notificaaciones en la
l barra de
estadoo de Android
d desde nuestras aplicacciones. Os animo
a a utiliizar este mecanismo parra notificar
determ
minados evenntos al usuarrio de forma bastante vissual e intuitivva.

Como siempre, po
odis descarggar el cdigoo fuente com
mpleto de estte artculo puulsando este enlace.

Notificacciones en An
ndroid
d (III): Dilogos
Por sggoliver on 05
5/11/2011 enn Android, Programacinn

Durannte este curso ya hemoos visto doos de las prrincipales alternativas


a a la hora de
d mostrar
notificcaciones a loos usuarios de nuestra aplicaciones
a s: los toast y las notificaciones de la
l barra de
estadoo. En este lttimo artculoo sobre notifi
ficaciones vaamos a comeentar otro meecanismo quue podemos
utilizaar para mostrrar o solicitaar informacin puntual al
a usuario. Ell mecanismoo del que habblamos son
los cuaadros de di
logo.

En priincipio, los dilogos


d de Android
A los podremos
p uttilizar con diistintos finess, en generall:

Mostrar un n mensaje.
Pedir una confirmacin
c n rpida.
Solicitar all usuario unaa eleccin (ssimple o mlltiple) entre varias
v alternnativas.

De cualquier form ma, veremos tambin cm


mo personallizar compleetamente un dilogo paraa adaptarlo
a cualqquier otra neecesidad.
El uso de dilogos en Android guarda muchas semejanzas con el funcionamiento ya comentado de
los mens, ya que se basa en la implementacin de dos mtodos de la propia actividad que los
muestra, uno de ellos para crear el dilogo por primera vez, onCreateDialog(), y el segundo
para poder modificarlos de forma dinmica cada vez que se muestran, onPrepareDialog().

El primero de estos mtodos recibe como parmetro el ID del dilogo que queremos crear. Podemos
asignar estos ID a nuestro antojo pero deben identificar de forma nica a cada dilogo de nuestra
aplicacin. La forma habitual de proceder ser definir una constante en la actividad con algn
nombre identificativo y utilizar sta en el resto del cdigo. Como en el caso de los mens, dentro de
este mtodo construiremos cada dilogo segn el ID recibido y lo devolveremos como valor de
retorno. Seguiramos el siguiente esquema:

1
2
private static final int DIALOGO_TIPO_1 = 1;
3 private static final int DIALOGO_TIPO_2 = 2;
4 //...
5 protected Dialog onCreateDialog(int id) {
6 Dialog dialogo = null;
7
8 switch(id)
{
9 case DIALOGO_TIPO_1:
10 dialogo = crearDialogo1();
11 break;
12 case DIALOGO_TIPO_2:
dialogo = crearDialogo2();
13
break;
14 //...
15 default:
16 dialogo = null;
17 break;
}
18
19 return dialogo;
20}
21
22

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
confirma su lectura. Este tipo de dilogos los construiremos mediante la clase AlertDialog, y
ms concretamente su subclase AlertDialog.Builder. Su utilizacin es muy sencilla, bastar
con crear un objeto de tipo AlertDialog.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
2 private Dialog crearDiaalogoAlerta()
3 {
log.Builder builder = new Aler
AlertDial rtDialog.Buuilder(thi
is);
4
5 builder.s
setTitle("Informacio on");
6 builder.s
setMessage("Esto es un mensaje de alert ta.");
7 builder.s
setPositivveButton("OOK", new On
nClickListeener() {
8 publi
ic void onClick(DialoogInterface dialog, int which) {
9 dialog.can
d cel();
}
10 });
11
12 return builder.creaate();
13}
14

Como vemos, al mtodo se etPositiv veButton( () le pasam mos como argumentos


a el texto a
mostraar en el botn, y la implem mentacin del d evento onClick en e forma de objeto
OnCli ickListen ner. Dentroo de este eveento, nos lim
mitamos a cerrrar el diloggo mediante su mtodo
cance el(), aunq
que podramoos realizar cuualquier otraa accin.

El asppecto de nuesstro dilogo de alerta serra el siguiennte:

Diloggo de Confirmacin

Un dilogo de con nfirmacin es muy similaar al anteriorr, con la difeerencia de quue lo utilizarremos para
solicittar al usuario que nos confirme
c unaa determinaada accin, por p lo que lasl posibles respuestas
sern del
d tipo S/NNo.

La im
mplementaci
n de estos dilogos
d mente igual a la ya comeentada para las alertas,
serr prcticam
salvo que en estaa ocasin aadiremos
a dos botoness, uno de ellos
e para laa respuesta afirmativa
(setPPositiveB Button())), y el segunndo paraa la respuesta negativa
(setNNegativeB Button())). Veamos unn ejemplo:

1 private Dialog crearDia


alogoConfirmacion()
{
2 log.Builder builder = new Aler
AlertDial rtDialog.Bu
uilder(thi
is);
3
4 builder.s
setTitle("Confirmaciion");
5 builder.s
setMessage("Confirmma la accion selecciionada?");
6 builder.s
setPositivveButton("A
Aceptar", new OnClickkListener(
() {
public void onClick
k(DialogInt
terface dialog, int which)
w {
7 Log.i
i("Dialogos", "Confiirmacion Aceptada.");
8 dialo
og.cancel();
9 }
10 });
builder.s
setNegativveButton("C
Cancelar", new OnClicckListener
r() {
11 public void onClick
k(DialogInt
terface dialog, int which)
w {
12 Log.i
i("Dialogos", "Confiirmacion Ca
ancelada."");
13 dialo
og.cancel();
14 }
15 });
16
return builder.crea
ate();
17}
18
19
20
21

En estte caso, gen


neramos a modo
m de ejemmplo dos meensajes de loog para podder verificar qu botn
pulsam
mos en el dilogo. El asppecto visual de nuestro dilogo
d de coonfirmacin sera el siguuiente:

Diloggo de Selecccin

Cuanddo las opcio ones a selecccionar por el usuario no son sllo dos, com
mo en los dilogos
d de
confirm
macin, sin no que el coonjunto es mayor podeemos utilizaar los diloggos de seleccin para
mostraar una lista de
d opciones entre
e las quee el usuario pueda
p elegirr.

Para ello
e tambin n utilizaremos la clase AlertDia alog, pero esta vez noo asignarem mos ningn
mensaaje ni definiremos las acciones
a a reealizar por cada
c botn individual, sino que dirrectamente
indicaaremos la listta de opciones a mostrarr (mediante el
e mtodo se etItems() )) y proporccionaremos
la impplementaci n del evennto onClic ck() sobree dicha lista (mediantee un listeneer de tipo
Dialo ogInterfa ace.OnClickListener), evennto en el que realizzaremos lass acciones
oportuunas segn lal opcin eleegida. La lissta de opcionnes la definiremos commo un array tradicional.
t
Veamoos cmo:

private Dialog crearDia


alogoSeleccion()
1 {
2 final String[] item
ms = {"Espaol", "In
ngls", "Fr
rancs"};
3
4 AlertDial
log.Builder builder = new Aler
rtDialog.Bu
uilder(thi
is);
5
builder.s
setTitle("Seleccin"");
6 builder.s
setItems(items, new DialogInteerface.OnCllickListen
ner() {
7 ic void onClick(Dialo
publi ogInterfacee dialog, int item) {
8 Log.i("Dia
L Opcin elegida: " + items[item
logos", "O i m]);
9 }
});
10
11 return builder.crea
ate();
12}
13
14
15

En esste caso el dilogo tenndr un asppecto similaar a la interrfaz mostrada para loss controles
Spinn ner.

Este dilogo
d perm
mite al usuarrio elegir enntre las opciiones disponnibles cada vez
v que se muestra
m en
pantalla. Pero, y si quisirammos recordarr cul es la opcin
o u opcciones selecccionadas porr el usuario
para que
q aparezcaan marcadas al visualizzar de nuevvo el cuadroo de dilogoo? Para elloo podemos
utilizaar los mtodo
os setSing gleChoice eItems() o setMult tiChiceItems(), enn vez de el
setIt tems() uttilizado anteeriormente. La L diferencia entre ammbos mtodoos, tal comoo se puede
suponeer por su noombre, es quue el primeroo de ellos peermitir una seleccin sim mple y el seegundo una
seleccin mltiplee, es decir, dee varias opciiones al mism
mo tiempo, mediante
m coontroles Che
eckBox.

La forma
f de utilizarlos es muy similar a la ya comentada. En el caso de
setSi ingleChoi iceItems(), el mtoodo tan slo se diferenciia de setIt tems() en que recibe
un seggundo parm
metro adicioonal que inddica el ndicce de la oppcin marcada por defeecto. Si no
querem
mos tener nin
nguna de elllas marcadass inicialmentte pasaremoss el valor -1..

builder.setSin
ngleChoiceItems(itemms, -1, new
w DialogInt
terface.On
nClickListe
ener() {
1 public void onClick(DialogInte
erface dialog, int it
tem) {
2 Log.i(
("Dialogos", "Opcin
n elegida: " + items[
[item]);
3 }
});
4
5

De estta forma con


nseguiramoss un dilogo como el de la siguiente imagen:

Si porr el contrario
o optamos poor la opcin de seleccinn mltiple, laa diferencia principal esttar en que
tendreemos que implemenntar un listener del tipo
Dialo ogInterfa ace.OnMultiChoic ceClickLi istener. En este caso, c en el evento
onCli ick recibireemos tanto la
l opcin seeleccionada (item) com mo el estado en el que ha h quedado
(isCh hecked). Adems,
A en esta
e ocasin, el segundoo parmetro adicional quue indica el estado por
defectto de las opcciones ya noo ser un simmple nmerro entero, sinno que tendrr que ser un u array de
booleaanos. En caaso de no querer
q ninguuna opcin seleccionada
s a por defectto pasaremoos el valor
null.

1builder.setMul ltiChoiceItems(itemss, null, new


2 Dial ogInterfac
ce.OnMulti ChoiceClic
ckListener () {
public void onClick(DialogInte
erface dialog, int it
tem, boole
ean isCheck
ked) {
3 Log.i(
("Dialogos", "Opcinn elegida: " + items[
[item]);
4 }
5});

Y el dilogo
d nos quedara
q de la
l siguiente forma:
f
Tanto si utilizamo os la opcinn de selecciin simple como
c la de seleccin mltiple,
m parra salir del
diloggo tendremoss que pulsar la tecla Attrs de nuestro disposittivo, pero noo perderemoos el estado
de las opciones. Si
S volvemos a abrir el dilogo
d stas debern continuar en ele mismo esttado que la
ltimaa vez que se cerr. Esto por
p supuestoo se cumple mientras
m no se cierre la actividad
a asoociada o se
salga de
d la aplicaccin.

Y conn esto terminnamos con ele apartado dedicado


d a notificacione
n es en Androoid. Hemos comentado
los trees principalees mecanism
mos de Anddroid a la hora
h de mosstrar mensajes y notificcaciones al
usuario (Toast, Barra
B de Esstado, y Dilogos), toddos ellos muy
m tiles para
p cualquier tipo de
aplicaccin y que es importannte conocer bien para poder
p deciddir entre elloos dependienndo de las
necesiidades que teengamos.

Como siempre, po
odis descarggar el cdigoo fuente de este
e artculo a travs de este
e enlace.

Accceso a Serviicios Web


W SOAP en
e Android (1/2)
Por sggoliver on 27
7/02/2012 enn Android, Programacinn

En estte primer arttculo que vaamos a dediccar a los serrvicios web dentro del Curso
C de Proogramacin
Androoid nos vamo os a centrar en los serviccios web que utilizan el estndar SO
OAP como mecanismo
m
de com
municacin.

A difeerencia de ottros tutorialees, no slo vaamos describbir cmo accceder a este tipo de serviicios desde
una applicacin An ndroid, sino que tambin veremos como crear un servicioo web SOAP P mediante
ASP.NNET para acceder
a a unna base de datos SQL L Server. Dee esta form ma pretendo ilustrar la
arquiitectura com
mpleta de unna aplicacinn Android quue acceda a datos almaccenados en un u servidor
de base de datoss externo. Nota:N Aunquue intentar aportar el mximo nmero de detalles,
d es
imposible abarcarrlo todo en un solo arttculo, por lo que el texxto supondrr unos conocimientos
mnim
mos de Visuaal Studio y deel lenguaje C#. C

Como caso de ejemplo, vam mos a crear una


u aplicaciin sencilla capaz de gestionar
g un listado de
clienttes que con
ntendr el noombre y telfono de cadda uno de elllos. Nuestraa aplicacin ser capaz
de connsultar el lisstado actual de clientes almacenadoos en el servvidor externoo y de inserrtar nuevos
clientees en la basee de datos. Como
C siemppre, se trata de un ejempplo muy senncillo pero creo
c que lo
suficieentemente co ompleto commo para que sirva de basee para crear otras aplicacciones ms complejas.
c

Como software necesario, enn este caso utilizar


u Visual Studio 2010
2 y SQL L Server 20008 R2 para
crear el w y la base de datos respectivam
e servicio web mente. Podis descargarr de forma gratuita
g las
versiones Expresss de ambos productos
p (m
ms que sufiicientes paraa crear una aplicacin
a coomo la que
describbiremos en este artculo) desde laa web oficiial de Micrrosoft. Tambbin es recomendable
instalaar SQL Serveer 2008 Mannagement Stuudio Expresss, descargable tambin de
d forma graatuita desde
esta web.
w Esta apllicacin no es
e ms que un
u gestor grrfico para acceder
a y maanipular nueestras bases
de datos SQL Serrver con totaal comodidadd.

Vamos comenzar por la basee de todo el sistema, y esto es la base b de datos a la que acceder
a el
serviciio web y, a travs de sste, tambinn la aplicacin Android que crearem
mos ms adeelante. Para
ello abbrimos SQL Server Mannagement Stuudio, nos connectamos a nuestro
n servidor SQL Seerver local,
y pulsamos sobre la seccin D Databases del rbol dee objetos quee aparece a la
l izquierda. Sobre esta
carpetta podemos acceder
a a la opcin Neew Databasee del menn contextuaal para crear una nueva
base de
d datos.

En el cuadro de dilogo
d que aparece
a tan slo
s indicareemos el nom mbre de la nuueva base ded datos, en
mi casso la llamar DBCLIENTTES, y dejareemos el restoo de opcionees con sus vaalores por deefecto.
Despleegamos el rbol de carppetas de nuestra recin creada
c base de datos DB
BCLIENTESS y sobre la
carpetta Tables ejecutamos
e la opcin N
New table para crear una
u nueva tabbla.

Vamos a aadir s
lo 3 camposs a la tabla:

IdClientte, de tipo int,


i que ser un cdigoo nico identtificativo dell cliente.
Nombre, de
d tipo nvar rchar(50) ), que conteendr el nommbre del cliennte.
Telefonoo, de tipo in
nt, que contendr el telfono del cliiente.

Marcaaremos adem ms el camppo IdCliente como clave princcipal de la taabla, y tambbin como
campoo de identid dad autoincrremental, de
d modo que se calculee automticcamente cadda vez que
inserteemos un nueevo cliente.
Con esto ya tenemmos nuestra tabla finalizzada, por lo que slo noos queda guuardarla con el nombre
que deeseemos, quee para este ejjemplo ser Clientes.

Hechoo, ya tenemo os nuestra baase de datos SQL Serverr creada y unau tabla preeparada para almacenar
los dattos asociadoos a nuestross clientes. Ell siguiente paso
p ser creear el serviciio web que manipular
m
los dattos de esta taabla.

Para crear
c el serv
vicio abrirem
mos Visual Studio
S 20100 y crearemoos un nuevoo proyecto web
w en C#
utilizaando la planntilla ASP.NET Emptyty Web Appplication. En
E un alardde de origiinalidad lo
llamarremos ServiicioWebSoapp.

Una vez
v creado ell proyecto, aadiremos
a a ste un nuuevo servicioo web mediaante el men Project /
Add neew item. Lo llamarem
mos ServiciioClientes.assmx.
Una vez
v aadido aparecer en pantalla el e cdigo fueente por deffecto del nuuevo servicioo web, que
contiene un nico mtodo de ejemplo llam mado Hello oWorld(). Este mtoddo podemos eliminarlo
ya quee no nos servvir de nadaa, y adems modificarem mos el atribuuto WebSer rvice de laa clase para
indicaar que el nam
mespace ser http://sgolliver.net/ (een vuestro caaso podis inndicar otro valor).
v Con
esto, nos
n quedara un cdigo base
b como sste:

1
2 nammespace ServicioWebSooap
3 {
4 /// <summ
mary>
/// Summa
ary description for ServicioClientes
5 /// </sum
mmary>
6 [WebServi
ice(Namespace = "htttp://sgoliv
ver.net/")]
7 [WebServi
iceBinding(ConformsTTo = WsiPro
ofiles.Bas
sicProfile1
1_1)]
8 [System.C
ComponentMModel.Toolb
boxItem(false)]
9
10 public class ServicioClientes
s : System.Web.Servi
ices.WebSer
rvice
{
11 //Mt
todos del servicio web
w
12 //...
.
13 }
14 }
15

Pues bien,
b dentroo de esta claase Servic cioClient tes es dondde aadirem mos todos loos mtodos
pbliccos que queramos tener accesibles
a a travs de nuuestro serviccio web, siem
mpre preceddidos por el
atributto [WebMet thod] como veremos en e breve. Parra nuestro ejemplo vamoos a crear trees mtodos,
el prim
mero para ob btener el listtado completto de clientees almacenaddos en la basse de datos, y los otros
dos paara insertar nuevos
n clienntes (ms addelante expliicar por quu dos, aunquue adelanto que es tan
slo por motivos didcticos).
d

Antes de crear esttos mtodos,, vamos a crrear una nuevva clase sencilla que nos sirva para encapsular
los dattos de un cliiente. La aaadiremos meediante la oppcin Projeect / Add class de Vissual Studio
y la llaamaremos Cliente.cs. Esta clase contendr
c nnicamente loos 3 campos que ya com mentamos al
crear lal base de datos
d y dos constructorees, uno de ellos
e por defecto que taan solo iniciializar los
campoos y otro con n parmetross para crear clientes a partir de sus datos identificativos. Ell cdigo de
la classe es muy sencillo, y tan solo caabe mencionnar que deffiniremos suus tres atribuutos como
propieedades autommticas de C# utilizando para ello la notacin abreviada {ge et; set;} }
1
2
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
using System.Web;
6
7 namespace ServicioWebSoap
8 {
9 public class Cliente
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;
this.Nombre = "";
17 this.Telefono = 0;
18 }
19
20 public Cliente(int id, string nombre, int telefono)
21 {
22 this.Id = id;
this.Nombre = nombre;
23 this.Telefono = telefono;
24 }
25 }
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 de ADO.NET, aunque podramos utilizar cualquier otro
mcanismo de acceso a datos, como por ejemplo Entity 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 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 propiedad Parameters. 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(
@"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
5
Catalog=DBCLIENTES;Integrated Security=True");
6
7 con.Open();
8
9 string sql = "INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre,
10@telefono)";
11
12 SqlCommand cmd = new SqlCommand(sql, con);
13
cmd.Parameters.Add("@nombre", System.Data.SqlDbType.NVarChar).Value =
14nombre;
15 cmd.Parameters.Add("@telefono", System.Data.SqlDbType.Int).Value =
16telefono;
17
18 int res = cmd.ExecuteNonQuery();
19
20 con.Close();
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.

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:
1
2 [WebMethod]
3 public Cliente[] ListadoClientes()
4 {
5 SqlConnection con =
new SqlConnection(
6 @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
7 Catalog=DBCLIENTES;Integrated Security=True");
8
9 con.Open();
10
11 string sql = "SELECT IdCliente, Nombre, Telefono FROM Clientes";
12
13 SqlCommand cmd = new SqlCommand(sql, con);
14
SqlDataReader reader = cmd.ExecuteReader();
15
16 List<Cliente> lista = new List<Cliente>();
17
18 while (reader.Read())
19 {
20 lista.Add(
new Cliente(reader.GetInt32(0),
21 reader.GetString(1),
22 reader.GetInt32(2)));
23 }
24
25 con.Close();
26
27 return lista.ToArray();
}
28
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 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 objeto Cliente.

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.

1 [WebMethod]
public int NuevoClienteObjeto(Cliente cliente)
2 {
3 SqlConnection con = new SqlConnection(@"Data
4 Souurce=SGOLIV
VERPC\SQLEXPRESS;Ini itial Ca
atalog=DBC
CLIENTES;Inntegrated
5 Sec
curity=True
e");
6
con.Open(
();
7
8 string sqql = "INSSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre,
(
9 @teelefono)";
10
11 nd cmd = new SqlComm
SqlComman mand(sql, con);
c
12
13 cmd.Param
meters.Addd("@nombre"", Systeem.Data.Sq
qlDbType.NVVarChar).V
Value =
cliente.Nombr
re;
14 cmd.Param
meters.Addd("@telefonno", S
System.Dataa.SqlDbTyp
pe.Int).Vallue =
15cliente.Teleffono;
16
17 int res = cmd.ExecuuteNonQuery();
18
19 con.Close
e();
20
return res;
}

Y conn esto hemo os finalizadoo nuestro seervicio web. Podemos probar su funcionamien
f nto con la
pginaa de prueba que
q proporciiona ASP.NET al ejecuttar el proyeccto en Visuall Studio. Si ejecutamos
e
el proyecto se abrrir automtticamente unn exploradorr web que mostrar
m unaa pgina conn todas las
operacciones que hemos
h definiddo en el servvicio web.

Si pulssamos sobree cualquiera de ellas pasaaremos a una nueva pgina que nos permitir daar valores a
sus paarmetros y ejecutar
e el mtodo
m correespondiente para
p visualizzar sus resultados. Si pulsamos por
ejemplo en la operracin Nuev voCliente e(string, , int) lleggaremos a essta pgina:
Aqu podemos
p dar valores a los
l dos parmetros y ejeecutar el mtodo (botnn Invoke), lo que nos
devolvver la respu
uesta codificaada en un XM
ML segn el estandar SOOAP.

Como podis com mprobar, en principio


p el XML
X devueelto no es fccil de interprretar, pero esto
e es algo
que no debe preo ocuparnos demasiado
d y que en principio
ya p serr transparente para noosotros, las
libreras que utilizzaremos mss adelante enn Android paara la llamadda a servicioos SOAP se encargarn
de parrsear conven nientemente estas respueestas y de daarnos tan sloo aquella parrte que necesitamos.

En el siguiente
s d la construcccin de unaa aplicacin Android quee sea capaz
arttculo nos occuparemos de
de connectarse a este servicio web y de llamar
l a los mtodos quue hemos deefinido paraa insertar y
recupeerar clientes de nuestra base de datoos. Veremoss adems cmo podemoos ejecutar y probar en
local todo
t el sistem
ma de formaa que podamoos comprobaar que todo funciona
f com
mo esperamoos.

Podiss descargar el
e cdigo fueente completto del servicio web desdde este enlacee.

Accceso a Serviicios Web


W SOAP en
e Android (2/2)
Por sggoliver on 27
7/02/2012 enn Android, Programacinn

En el artculo antterior del cuurso vimos cmo constrruir un servvicio web SO OAP hacienndo uso de
ASP.N NET y una base de daatos externaa SQL Server. En este segundo arrtculo verem mos cmo
podemmos acceder a este serviccio web desdde una aplicaacin Androiid y probaremos todo el sistema en
local para
p verificaar su correctoo funcionam
miento.

En priimer lugar hay que em mpezar dicieendo que Anndroid no inncluye de serie ningn tipo de
soportte para el accceso a serviicios web dee tipo SOAP P. Es por estto por lo quue vamos a utilizar
u una
librera externa paara hacernoss ms fcil esta
e tarea. Entre
E la ofertta actual, la opcin mss popular y
ms utilizada
u es la
l librera kssoap2-androoid. Esta librrera es un fork,
f especiaalmente adaaptado para
Androoid, de la anttigua libreraa kSOAP2. Este
E framew work nos perm mitir de forrma relativammente fcil
y cmmoda utilizar servicios weeb que utiliccen el estnddar SOAP. La ltima verrsin de estaa librera en
el mommento de esccribir este arrtculo es la 2.6.0,
2 que puuede descarggarse desde este
e enlace.

Agreggar esta libreera a nuesttro proyectoo Android ese muy senccillo. Una vez v tenemoss creado el
proyeccto en Andro oid, accederremos al meen Projectt / Propertiees y en la ventana
v de propiedades
accedeeremos a la seccin Javva Build Patth. En esta seccin acceederemos a la l solapa Libraries y
pulsarremos el bootn Add External
E JARRs. Aqu seleccionaamos el fichhero jar de la librera
ksoap22-android (een este caso ksoap2-anndroid-assem mbly-2.6.0-jaar-with-depeendencies.jarr) y listo,
ya tenemos nuestrro proyecto preparado
p paara hacer usoo de la funcioonalidad apoortada por laa librera.
Como aplicacin de ejemplo,, vamos a crrear una apllicacin senccilla que perrmita aadirr un nuevo
usuario a la base de datos. ParaP ello aadiremos a la vista priincipal dos cuadros de texto para
introduucir el nommbre y telfono del nuuevo clientee (en mi caso c se llam
marn txtN Nombre y
txtTe elefono respectivame
r ente) y un botn
b (en mii caso btnE
Enviar) quue realice la llamada al
mtoddo NuevoCl w pasndolle como parrmetros los datos introdducidos en
liente dell servicio web
los cuaadros de texto anterioress.

No vooy a mostrarr todo el cdigo necesaario para creear esta vistaa y obtener las referenccias a cada
controol porque no o tiene ningguna particuularidad sobbre lo ya viisto en mulltitud de ocasiones en
artcullos anteriorees del curso (en
( cualquieer caso al finnal del artcuulo podis deescargar todoo el cdigo
fuentee para su co onsulta). Loo que nos interesa
i en este caso es e la implementacin del d evento
onCli ick del botn btnEnv viar, que ser el encaargado de coomunicarse con el serviicio web y
processar el resultaado.

Lo priimero que vaamos a haceer en este evvento es definnir, por com


modidad, cuaatro constanttes que nos
servirn en varias ocasiones duurante el cddigo:

NAMESPAC CE. Espacioo de nombress utilizado enn nuestro serrvicio web.


URL. Direcccin URL para
p realizar la conexinn con el serviicio web.
METHOD_N NAME. Nommbre del mtoodo web conncreto que vaamos a ejecuutar.
SOAP_ACT TION. Equivalente al annterior, pero en la notaciin definida por SOAP.

Aunquue los valorres se podran ms o menos


m intuirr, para conoocer exactam
mente los valores
v que
debem
mos asignar a estas consttantes vamoss a ejecutar una vez mss el proyectoo de Visual Studio que
constrruimos en el
e artculo anterior y vamos a accedera a laa pgina dee prueba del
d mtodo
Nuevo oCliente. Veremos algo
a parecidoo a lo siguiennte:

En la imagen anteerior se mueestran resaltaados en rojo los valores de las cuatrro constantes a definir,
que enn nuestro casso concreto quedaran
q dee la siguientee forma:
1String NAMESPA
ACE = "http://sgoliv
ver.net/";
2String URL="ht
ttp://10.0.2.2:1473/
/ServicioClientes.as
smx";
3String METHOD_
_NAME = "NuevoClient
teSimple";
4String SOAP_AC
CTION = "http://sgol
liver.net/NuevoClien
nteSimple";
;

Como podis com mprobar, y essto es algo im


mportante, en
e la URL hee sustituido el nombre de
d mquina
localhhost por su direccin
d IP equivalente, que en el caso
c de aplicaciones Anndroid ejecuttadas en el
emulaador se corresponde con lal direccin 10.0.2.2, enn vez de la cllsica 127.0..0.1.

Los siiguientes passos del proceeso sern crrear la peticin SOAP all servicio weeb, enviarla al servidor
y recibir la respuuesta. Aunquue ya dijimoos que todoo este proceeso sera cassi transparennte para el
prograamador, por ser sta la primera vez que
q hablamoos del tema mem voy a dettener un poco ms para
intentaar que enten
ndamos lo quue estamos haciendo
h y no
n solo nos limitemos
l a copiar/pegaar trozos de
cdigoo que no sabbemos lo quee hacen.

Volvaamos a la pgina de pruueba del mtodo web NuevoClie


N ente. Justoo debajo de la seccin
donde se solicitan
n los parmeetros a pasaar al mtodoo se incluye tambin unn XML de muestra
m de
cmo tendra que ser nuestra peticin al servidor
s si tuuviramos que
q construirrla a mano. Echmosle
E
un visttazo:

Una vez ms he marcado


m variaas zonas sobbre la imagenn, corresponndientes a lass tres partes principales
p
de una peticin ded tipo SOA AP. Empezaando por laa parte inteerna del XML, X en primer lugar
enconttramos los datos
d de la peticin en s (Requestt) que contieene el nombbre del mtoodo al que
queremmos llamar,, y los nom mbres y vaalores de loos parmetroos en entraada. Rodeanndo a esta
inform
macin se a aden otra serie
s de etiqquetas y datoos a modo de conteneddor estndarr que suele
recibirr el nombre de Enveloppe. La inforrmacin indiicada en estte contenedoor no es esppecfica de
nuestrra llamada all servicio, pero s contieene informaccin sobre formatos
fo y esquemas de validacin
del estndar SOA AP. Por ltim mo, durante el envo dee esta peticin SOAP al servidor mediante m el
protoccolo HTTP ses aaden deeterminados encabezadoos como los que veis enn la imagen. Todo esto
junto har
h que el servidor
s sea capaz de intterpretar corrrectamente nuestra
n peticcin SOAP, se llame al
mtoddo web correecto, y se deevuelva el reesultado en un u formato similar
s al annterior que ya
y veremos
ms adelante.
a Acllarada un poco la estruuctura y funncionamientoo general de d una peticcin SOAP
veamoos lo sencillo
o que resultaa realizarla desde
d nuestraa aplicacin Android.

En primer lugar crearemos la l peticin (request)


( a nuestro mtodo Nuevo oCliente.. Para ello
crearemos un nuev vo objeto So
oapObject t pasndole el namespacce y el nombbre del mtoodo web. A
esta peticin teendremos que q asociarr los parmetros de entrada mediante m e mtodo
el
addPr roperty() ) al que passaremos los nombres y valores
v de loos parmetroos (que en nuuestro caso
se obteendrn de lo
os cuadros dee texto de la vista princippal).
1SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
2
3request.addProperty("nombre", txtNombre.getText().toString());
4request.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 propiedad dotNet. Por ltimo,
asociaremos la peticin antes creada a nuestro contenedor llamando al mtodo
setOutputSoapObject().

1SoapSerializationEnvelope envelope =
2 new SoapSerializationEnvelope(SoapEnvelope.VER11);
3
4envelope.dotNet = true;
5
6envelope.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
2 HttpTransportSE transporte = new HttpTransportSE(URL);
3
4 try
{
5 transporte.call(SOAP_ACTION, envelope);
6
7 //Se procesa el resultado devuelto
8 //...
9 }
catch (Exception e)
10{
11 txtResultado.setText("Error!");
12}
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 objeto SoapPrimitive, que directamente podremos convertir a una cadena de caracteres
llamado a toString(). Ms adelante veremos cmo tratar valores de retorno ms complejos.

1SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();


2String res = resultado_xml.toString();
3
4if(r
res.equals(
("1"))
5 txtResulta
ado.setText("Inserta
ado OK");

Y listoo, con esto ya tenemos preparada la


l llamada a nuestro seervicio web y el tratamiiento de la
respueesta recibida.

Un detalle ms an ntes de poderr probar todoo el sistema.. Debemos acordarnos


a d conceder permiso
de p de
accesoo a internet a nuestra aplicacin, aaadiendo la linnea correspoondiente al Android
A Mannifest:

1<use
es-permissi
ion android
d:name="android.perm
mission.INT
TERNET"/>

Pues bien, parra probar lo que llevamos hasta ahoora podem mos ejecutaar ambos
proyecctos simultn neamente, ene primer luugar el de Visual Studdio para iniciar la ejeccucin del
serviddor local quee alberga nueestro servicioo web (hay que dejar abbierto el explorador una vez que se
abra), y posteriorm mente el de Eclipse paraa iniciar nueestra aplicaccin Androidd en el Emuulador. Una
vez esstn los doss proyectos en ejecucin, podemoss rellenar loos datos de nuestro cliiente en la
aplicaccin Androiid y pulsar el
e botn Enviar para realizar la llaamada al serrvicio web e insertar el
clientee en la base de datos (quue por supueesto tambin deber estar iniciada). Si S todo va bien y no se
producce ningn errror, deberaamos poder consultar la tabla de Cl lientes a travs del SQL
S Server
Managgement Stud dio para verifficar que el cliente
c se ha insertado coorrectamentee.

En la imagen vem mos cmo hemos


h inserttado un nueevo cliente llamada
l cliiente7 con nmero
n de
telfonno 7777. Si
S consultammos ahora nuuestra base ded datos Sql Server podrremos compprobar si el
registrro efectivam
mente se ha innsertado corrrectamente.
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 tipo Cliente.

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 tipo SoapObject, 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 mtodo getPropertyCount(). 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 a getProperty(), 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
2 SoapObject resSoap =(SoapObject)envelope.getResponse();
3
4 Cliente[] listaClientes = new Cliente[resSoap.getPropertyCount()];
5
for (int i = 0; i < listaClientes.length; i++)
6 {
7 SoapObject ic = (SoapObject)resSoap.getProperty(i);
8
9 Cliente cli = new Cliente();
10 cli.id = Integer.parseInt(ic.getProperty(0).toString());
cli.nombre = ic.getProperty(1).toString();
11 cli.telefono = Integer.parseInt(ic.getProperty(2).toString());
12
13 listaClientes[i] = cli;
14}
15

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,
android.R.layout.simple_list_item_1, datos);
9
10lstClientes.setAdapter(adaptador);
11

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 clase
Cliente, 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
interfaz KvmSerializable en nuestra clase Cliente. Para ello, adems de aadir la clusula
implements correspondiente tendremos que implementar los siguientes mtodos:

getProperty(int indice)
getPropertyCount()
getPropertyInfo(int indice, HashTable ht, PropertyInfo info)
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
switch(arg0)
4 {
5 case 0:
6 return id;
7 case 1:
return nombre;
8 case 2:
9 return telefono;
10 }
11
12 return null;
13}
14
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
2public 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
2
@Override
3 public void getPropertyInfo(int ind, Hashtable ht, PropertyInfo info) {
4 switch(ind)
5 {
6 case 0:
info.type = PropertyInfo.INTEGER_CLASS;
7
info.name = "Id";
8 break;
9 case 1:
10 info.type = PropertyInfo.STRING_CLASS;
11 info.name = "Nombre";
break;
12 case 2:
13 info.type = PropertyInfo.INTEGER_CLASS;
14 info.name = "Telefono";
15 break;
16 default:break;
}
17}
18
19

Por ltimo, el mtodo setProperty() ser el encargado de asignar el valor de cada atributo
segn su ndice y el valor recibido como parmetro.

@Override
1 public void setProperty(int ind, Object val) {
2 switch(ind)
3 {
4 case 0:
5 id = Integer.parseInt(val.toString());
break;
6 case 1:
7 nombre = val.toString();
8 break;
case 2:
9 telefono = Integer.parseInt(val.toString());
10 break;
11 default:
12 break;
}
13}
14
15
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
Cliente que 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
2 Cliente cli = new Cliente();
3 cli.nombre = txtNombre.getText().toString();
4 cli.telefono = Integer.parseInt(txtTelefono.getText().toString());
5
PropertyInfo pi = new PropertyInfo();
6 pi.setName("cliente");
7 pi.setValue(cli);
8 pi.setType(cli.getClass());
9
10request.addProperty(pi);
11
SoapSerializationEnvelope envelope =
12 new SoapSerializationEnvelope(SoapEnvelope.VER11);
13envelope.dotNet = true;
14
15envelope.setOutputSoapObject(request);
16
17envelope.addMapping(NAMESPACE, "Cliente", cli.getClass());
18
Todo esto
e lo harem mos en un nuevo
n botn aadido a laa aplicacin de ejemplo (Enviar2), cuyo
c efecto
tendr que serr idntico al que ya cream mos para la llamada al mttodo web
Nuevo oClienteS Simple(), aunque com mo acabamoos de ver suu implementaacin es algo diferente
debidoo a los distin
ntos parmetrros de entradda utilizadoss.

Como imagen finaal veamos unna captura de


d la pantallaa final de nueestra aplicaccin de ejem
mplo, donde
vemoss los tres botones implem
mentados, juunto al resultado de la ejecucin
e dee cada uno, el mensaje
Inserrtado OK de
d los mtoddos de inserrcin, y la lista
l de clienntes recuperrada por el mtodo de
consullta.

Esperoo que estos dos


d ltimos artculos sobbre servicioss web SOAP P y Android os sirvan paara tener un
ejemplo completo, tanto de la parte serviddor como de la parte cliennte, que os sirva
s de basee para crear
nuevos sistemas adaptados a vuestras necesidades.
n Como siem mpre, podis descargar el cdigo
fuentee completo de
d este artcuulo a travs de
d este enlace.

Accceso a Serviicios Web


W REST
R e Android (1/2)
en
Por sggoliver on 04
4/03/2012 enn Android, Programacinn

En loss dos artculoos anterioress (ste y stee) del Curso de Programaacin Androoid nos hemoos ocupado
de desscribir la forrma de consttruir un sisteema formadoo por un servvicio web SOOAP que acccede a una
base de
d datos exteerna y una applicacin Anndroid que, a travs de esste servicio, es capaz dee manipular
dichoss datos.

En estte nuevo arttculo vamoos a crear unn sistema siimilar, pero esta vez haaciendo uso de la otra
alternaativa por exccelencia a la hora de creaar servicios web, y no ess otra de utillizar servicioos web tipo
REST T. Las famosas APIs quue publican muchos de los sitios web w actualm mente no sonn ms que
serviciios web de este
e tipo, aunnque en la mayora
m de loos casos conn medidas dee seguridad adicionales
a
tales como
c autentiicacin OAuuth o similarees.
REST tambin se asienta sobbre el protoccolo HTTP como
c mecannismo de transporte entrre cliente y
serviddor, ya verem
mos despuss en qu meddida. Y en cuanto
c al forrmato de los datos transsmitidos, a
diferenncia de SOAAP, no se im mpone ninguuno en conccreto, aunquue lo ms haabitual actualmente es
intercaambiar la in
nformacin ene formato XML o JSO ON. Ya quee en el casoo de SOAP utilizamos
XML, en este nuev vo artculo utilizaremos
u JSON para construir nuuestro ejempllo.

Tambiin vamos a utilizar unn frameworkk distinto para construiir el servicio, aunque seguiremos
s
hacinndolo en Vissual Studio y en lenguajje C#. En esste caso, en vez de utilizzar ASP.NEET a secas,
vamoss a utilizar el
e frameworkk especfico ASP.NET MVC M d direccionamiento se
3, cuyyo sistema de
ajusta mejor a loss principios de REST, donde
d cada recurso
r [en nuestro casoo cada cliente] debera
ser accesible med diante su proopia URL nnica. Podiss descargar MVC3
M desdde su pginaa oficial de
Microsoft.

En estte primer arttculo sobre servicios REEST vamos a describir laa construccin del servicio web en
s, y dedicaremos
d un segundoo artculo a explicar
e mo podemos acceder a este
cm e servicio desde una
aplicaccin Android.

Empezzamos. Lo primero quue vamos a hacer ser crear un nuevo


n proyeecto en Visual Studio
utilizaando esta veez la plantiilla llamadaa ASP.NET W Applicaation, lo llamaremos
T MVC 3 Web
ServiicioWebRestt.

En la ventana de opciones del


d proyectoo dejaremos todos los datos
d que apparecen porr defecto y
seleccionaremos como
c plantillla Empty para
p crear unna aplicacinn vaca.
Esto debera
d crearrnos el nuevoo proyecto con
c la estrucctura de carppetas necesarria, que com
mo veris es
bastannte elaboradaa. En nuestrro caso vamos a crear ell servicio weeb de formaa aislada del resto de la
aplicaccin web, y para ello lo primero quee vamos a haacer es aaddir una nuevaa Area al prooyecto, a la
que llaamaremos por
p ejemplo Api, lo quue nos crear una estrucctura de carppetas similarr a la de la
aplicaccin princip
pal pero denntro de unaa carpeta inddependiente.. Esto nos permite
p aisllar todo el
cdigoo y recursoss de nuestro servicio weeb del resto de la aplicaacin web (qque en nuesttro caso no
existirr porque noo es el objeetivo de estee artculo, pero
p que poddramos creaar sin probllemas si lo
necesiitramos).

Con essto, la estrucctura de nuesstro proyectoo ser la siguuiente:

Una vez
v que ya teenemos prepparada toda lal estructuraa de nuestro proyecto em
mpecemos a aadir los
elemenntos necesarrios. Lo primmero que vaamos a creaar ser una nueva clasee Cliente,, igual que
hicimoos en el ejem
mplo anterioor con SOAP
P. La colocarremos en la carpeta Appi/Models y el cdigo
es el mismo
m que ya
y vimos:

1
2name
espace Serv
vicioWebRes st.Areas.A
Api.Models
{
3 public class Cliente
4 {
5 publicc int Id { get; set; }
6 publicc string Nombre { gett; set; }
publicc int Telefono { get;; set; }
7 }
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 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 mtodos ObtenerClientes() e InsertarCliente().

1 public bool InsertarCliente(Cliente cli)


2 {
3 SqlConnection con = new SqlConnection(cadenaConexion);
4
con.Open();
5
6 string sql = "INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre,
7 @telefono)";
8
9 SqlCommand cmd = new SqlCommand(sql,con);
10
11 cmd.Parameters.Add("@nombre", System.Data.SqlDbType.NVarChar).Value =
cli.Nombre;
12 cmd.Parameters.Add("@telefono", System.Data.SqlDbType.Int).Value =
13cli.Telefono;
14
15 int res = cmd.ExecuteNonQuery();
16
17 con.Close();
18
19 return (res == 1);
}
20
21public List<Cliente> ObtenerClientes()
22{
23 List<Cliente> lista = new List<Cliente>();
24
25 SqlConnection con = new SqlConnection(cadenaConexion);
26
27 con.Open();
28
29 string sql = "SELECT IdCliente, Nombre, Telefono FROM Clientes";
30
SqlCommand cmd = new SqlCommand(sql,con);
31
32 SqlDataReader reader =
33 cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
34
35 while (reader.Read())
36 {
Cliente cli = new Cliente();
37
38 cli = new Cliente();
39 cli.Id = reader.GetInt32(0);
40 cli.Nombre = reader.GetString(1);
41 cli.Telefono = reader.GetInt32(2);
42
43 lista.Add(cli);
}
44
45 reader.Close();
46
47 return lista;
48}
49
50
51

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 (clase
ClientesController) 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
JsonResult llamado al mtodo Json() pasndole como parmetro de entrada el objeto a
formatear. Todo esto se reduce a una sola linea de cdigo:

[HttpGet]
1public JsonResult Clientes()
2{
3 return Json(this.clientesManager.ObtenerClientes(),
JsonRequestBehavior.AllowGet);
4}
5
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. 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":
return Json(clientesManager.InsertarCliente(item));
6 case "PUT":
7 return Json(clientesManager.ActualizarCliente(item));
8 case "GET":
9 return
10 Json(clientesManager.ObtenerCliente(id.GetValueOrDefault()),
JsonRequestBehavior.AllowGet);
11 case "DELETE":
12 return
13Json(clientesManager.EliminarCliente(id.GetValueOrDefault()));
14 }
15
return Json(new { Error = true, Message = "Operacin HTTP desconocida" });
16}
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.

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
2context.MapRoute(
"AccesoClientes",
3 "Api/Clientes",
4 new
5 {
6 controller = "Clientes",
action = "Clientes"
7 }
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
2 context.MapRoute(
"AccesoCliente",
3 "Api/Clientes/Cliente/{id}",
4 new
5 {
6 controller = "Clientes",
7 action = "Cliente",
id = UrlParameter.Optional
8 }
9 );
10

Como todo esto en cuenta, y por recapitular un poco, las posibles llamadas a nuestro servicio sern
las siguientes:
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. Podis descargar el cdigo fuente completo de este
artculo desde este enlace.

Acceso a Servicios Web REST en Android (2/2)


Por sgoliver on 04/03/2012 en Android, Programacin
En el artculo anterior dedicaado a los serrvicios web REST hemoos visto cm mo crear fcilmente un
serviciio de este tiipo utilizanddo el framew
work ASP.NNET MVC 3. 3 En esta segunda partte vamos a
describbir cmo podemos consttruir una apllicacin Anddroid que accceda a este servicio
s web
b REST.

Y tal como
c hicimoos en el casoo de SOAP, vamos a crrear una apliicacin de ejjemplo que llame l a las
distinttas funcioness de nuestroo servicio weeb. En este casoc mpondr de 5 botones,
la apliccacin se com
uno poor cada una de las accioones que hem mos implemeentado en ell servicio weeb (insertar, actualizar,
eliminnar, recuperaar un cliente,, y listar todoos los clientees).

A difeerencia del caso


c de SOA AP, en esta ocasin no vamosv a utilizar ningunna librera exxterna para
accedeer al serviciio web, ya que Android incluye toodo lo neceesario para realizarr la conexin
c y
llamadda a los mto
odos del servvicio, y trataamiento de reesultados enn formato JSON.

Como ya hemos comentado,


c a trabajar coon servicios web de tipo REST, las llamadas
al l al servicio
s no
se harrn a travs de una nicca URL, sinno que se deeterminar la l accin a realizar
r segn la URL
accediida y la acci
n HTTP uttilizada para realizar la peticin
p (GET UT o DELET
T, POST, PU TE). En los
siguienntes apartados veremos uno
u a uno laa implementaacin de estoos botones.

Inserttar un nuevo cliente

Como ya comentaamos en el artculo antterior, la inssercin de un


u nuevo cliente la realiizaremos a
travs de la siguiente URL:

http:
://10.0.2
2.2:2731/Api/Clientes/Cl
liente

Utilizaaremos la acccin http PO


OST y tendreemos que inccluir en la peeticin un obbjeto en form
mato JSON
que coontenga los datos del nuuevo clientee (tan slo Nombre
N y Teelfono, ya que el ID see calcular
autommticamente). El formato de este objeeto de entradda ser anloogo al siguiennte:

{Nomb
bre:cccc
c, Telefono:12345678}

Pues bien,
b onseguir estoo comenzaremos por crear un nuevo objeto Htt
para co tpClient, que ser el
encarggado de reallizar la comuunicacin HTTP
H con el servidor a partir
p de loss datos que nosotros
n le
proporrcionemos. Tras esto crrearemos la peticin POOST creanddo un nuevoo objeto Htt tpPost 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 MIME-Type correspondiente es application/json).

1HttpClient httpClient = new DefaultHttpClient();


2
3HttpPost post =
4 new HttpPost("http://10.0.2.2:2731/Api/Clientes/Cliente");
5
6post.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 mtodo put() 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 tipo
StringEntity e incluyndolo finalmente en la peticin mediante el mtodo setEntity().

1//Construimos el objeto cliente en formato JSON


2JSONObject dato = new JSONObject();
3
4dato.put("Nombre", txtNombre.getText().toString());
5dato.put("Telefono", Integer.parseInt(txtTelefono.getText().toString()));
6
7StringEntity entity = new StringEntity(dato.toString());
post.setEntity(entity);
8

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().

1HttpResponse resp = httpClient.execute(post);


2String respStr = EntityUtils.toString(resp.getEntity());
3
4if(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 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.

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
2
3 HttpClient httpClient = new DefaultHttpClient();
4
HttpPut put = new HttpPut("http://10.0.2.2:2731/Api/Clientes/Cliente");
5 put.setHeader("content-type", "application/json");
6
7 try
8 {
9 //Construimos el objeto cliente en formato JSON
JSONObject dato = new JSONObject();
10
11
dato.put("Id", Integer.parseInt(txtId.getText().toString()));
12 dato.put("Nombre", txtNombre.getText().toString());
13 dato.put("Telefono", Integer.parseInt(txtTelefono.getText().toString()));
14
15 StringEntity entity = new StringEntity(dato.toString());
16 put.setEntity(entity);
17
HttpResponse resp = httpClient.execute(put);
18 String respStr = EntityUtils.toString(resp.getEntity());
19
20 if(respStr.equals("true"))
21 lblResultado.setText("Actualizado OK.");
22}
23catch(Exception ex)
{
24 Log.e("ServicioRest","Error!", ex);
25}
26
27

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 (objeto HttpDelete) 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
2 HttpClient httpClient = new DefaultHttpClient();
3
4 String id = txtId.getText().toString();
5
6 HttpDelete del =
7 new HttpDelete("http://10.0.2.2:2731/Api/Clientes/Cliente/" + id);
8
del.setHeader("content-type", "application/json");
9
10try
11{
12 HttpResponse resp = httpClient.execute(del);
13 String respStr = EntityUtils.toString(resp.getEntity());
14
15 if(respStr.equals("true"))
lblResultado.setText("Eliminado OK.");
16}
17catch(Exception ex)
18{
19 Log.e("ServicioRest","Error!", ex);
}
20
21

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 objeto JSONObject 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
2
HttpClient httpClient = new DefaultHttpClient();
3
4 String id = txtId.getText().toString();
5
6 HttpGet del =
7 new HttpGet("http://10.0.2.2:2731/Api/Clientes/Cliente/" + id);
8
9 del.setHeader("content-type", "application/json");
10
11try
{
12 HttpResponse resp = httpClient.execute(del);
13 String respStr = EntityUtils.toString(resp.getEntity());
14
15 JSONObject respJSON = new JSONObject(respStr);
16
17 int idCli = respJSON.getInt("Id");
String nombCli = respJSON.getString("Nombre");
18 int telefCli = respJSON.getInt("Telefono");
19
20 lblResultado.setText("" + idCli + "-" + nombCli + "-" + telefCli);
21}
22catch(Exception ex)
23{
Log.e("ServicioRest","Error!", ex);
24}
25
26

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
2
3 HttpClient httpClient = new DefaultHttpClient();
4
5 HttpGet del =
6 new HttpGet("http://10.0.2.2:2731/Api/Clientes");
7
8 del.setHeader("content-type", "application/json");
9
10try
{
11 HttpResponse resp = httpClient.execute(del);
12 String respStr = EntityUtils.toString(resp.getEntity());
13
14 JSONArray respJSON = new JSONArray(respStr);
15
16 String[] clientes = new String[respJSON.length()];
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
clientes[i] = "" + idCli + "-" + nombCli + "-" + telefCli;
25 }
26
27 //Rellenamos la lista con los resultados
28 ArrayAdapter<String> adaptador =
29 new ArrayAdapter<String>(ServicioWebRest.this,
android.R.layout.simple_list_item_1, clientes);
30
31 lstClientes.setAdapter(adaptador);
32}
33catch(Exception ex)
34{
35 Log.e("ServicioRest","Error!", ex);
}
36
37
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:
Y conn esto hemoss terminado. Espero habber ilustradoo con clariddad en los doos ltimos artculos
a la
forma de construiir servicios web tipo REST
R mediannte ASP.NE ET y aplicacciones clientte Android
capacees de accedeer a dichos seervicios.

Como siempre, po
odis descarggar el cdigoo fuente com
mpleto de estte artculo mediante
m este enlace.

Notificacciones Push Andrroid: Google


G e Clou
ud
Meessaginng (GCCM). Introd
duccin
Por sggoliver on 04
4/07/2012 enn Android, Programacinn

Aprovvechando qu ue el serviicio de mennsajera puush de Gooogle ha sallido de su fase beta


recienntemente, tall como anunnciaron en el ltimo evvento Googgle I/O, y quue ha sufriddo algunos
cambios con resp pecto a su anterior
a verssin, voy a dedicar los prximos artculos
a dell Curso de
Prograamacin An ndroid a desscribir qu ese y cmo utilizar
u este servicio. En
E este primmer artculo
haremmos una introoduccin al servicio
s y coomentaremoss la forma de registrarnoos para podeer utilizarlo
(no prreocuparos, es
e gratuito),, y en los doos siguientess veremos cmo implem mentar las applicaciones
serviddor (una vez ms en ASPP.NET) y clieente (en Anddroid). Empeecemos.

En alggunas ocasioones, nuestraas aplicacionnes mviles necesitan coontar con la capacidad de


d notificar
al usuario determiinados eventtos que ocurrren fuera deel dispositivvo, normalmente en una aplicacin
web o servicio on nline alojadoo en un serviidor externoo. Como ejem mplo de estoo podramoss pensar en
las notificaciones que nos muuestra nuestrro mvil cuaando se recibbe un nuevoo correo elecctrnico en
nuestrro servidor de
d correo habbitual.

Para conseguir
c essto se nos podran
p ocurrrir varias soluciones, por
p ejemplo mantener abierta
a una
conexiin permaneente con el servidor dee forma quee ste le puudiera comuunicar inmediatamente
cualquuier nuevo evento
e a nueestra aplicaciin. Esta tccnica, aunquue es viable y efectiva, requiere
r 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 mensajera push 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 recientemente y coincidiendo con su salida de fase beta ha modificado su nombre a GCM
(Google Cloud Messaging). Y 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.
Googlle API Conso
ole Create Project

Una vez creado el proyecto el navegador te


t redirige a una direccin similar a sta:

Googlle API Conso


ole URL Proyecto
P

Al nm mero que apparece tras laa etiqueta ##project: loo llamaremoss Sender ID
D y debemoos anotarlo
ya que nos har falta ms adelante
a commo identificaador nico de la aplicaacin web emisora
e de
nuestrros mensajess.

Una vez creado ell nuevo proyyecto vamos a activar el servicio GC


CM accedienndo al men Services
y pulsando el bot
n ON/OFF asociado
a al servicio
s llam
mado Googlle Cloud Meessaging.

Googlle API Conso


ole Servicees

Se noss presentar entonces unna ventana donde tendrem mos que aceeptar las conndiciones dell servicio y
tras ssto el serviciio quedar activado,
a aunnque an noos faltar un ltimo pasoo para poderr utilizarlo.
Como en el caso de la utilizzacin de la api de Gooogle Maps, para p hacer uso
u del servvicio GCM
tendreemos que geenerar una ApiKey
A que nos sirva posteriormentte como ideentificacin de acceso.
Para ello
e accedem
mos al menn Api Acccess y pulsaremos sobbre el botnn Create new
n Server
Key.

Googlle API Conso


ole New Server
S Key

Nos apparecer un dilogo llammado Conffigure Serveer Key for API A Project, que aceptaaremos sin
ms puulsando el botn Createe, sin necessidad de relleenar nada.

Googlle API Conso


ole Configgure Server Key
K

Y ya tendramos
t creada
c nuesttra API Key,, que aparecer en la secccin Simpple API Acceess con el
ttulo Key
for serrver apps (with IP lockinng).

Googlle API Conso


ole API Keey

Con esto
e ya nos habramos registrado
r coorrectamentee en el servvicio GCM y habramoss generado
nuestrra API Key para identifficarnos, conn lo que esttaramos en disposicinn de construuir nuestras
aplicacciones clien
nte y serviddor, algo quue veremos en los dos prximos artculos.
a Enn ste nos
parareemos un pocco ms para hacer una descripcin
d a grandes raasgos de la arquitectura
a que tendr
nuestrro sistema y de cmo fluir
f la infoormacin enntre cada unno de los seervicios y applicaciones
impliccados.
Como hemos indicado antes, para p aseguraar la correctaa comunicaccin entre loos tres sistem
mas se hace
necesaario un protoocolo de reggistros y autoorizaciones que proporccione seguriddad y calidad a todo el
processo. Este procceso se resum
me en el siguuiente grficco (click paraa ampliar), que
q intentar explicar a
continnuacin.

Processo General GCM


G

Comenntemos brev
vemente cadaa uno de los pasos indicaados en el diiagrama.

El prim
mer paso, au unque no apaarece en el grfico,
g seraa la autenticaacin de nueestra aplicaciin web en
el servvicio GCM. En la anterior versinn del servicio GCM (llaamada C2DM M) esto debba hacerse
mediaante la utilizaacin de otraa API de Gooogle llamadda ClientLoggin, o a travs del protoccolo OAuth
2.0, ammbas dirigiddas a obteneer un token de autorizaacin que deeba enviarsee posteriorm mente en el
resto de
d llamadas al servicio. Sin embarggo, con la lleegada de Gooogle Cloud Messaging, esto se ha
simpliificado mediiante la obttencin y usso de una API A Key, quue es precissamente lo que q hemos
comenntado unos prrafos
p ms arriba. Commo pasaba coon el token de d autorizaciin, nuestra nueva API
Key deber
d acommpaar a cadda una de laas llamadas que hagamoos al serviciio GCM dessde nuestra
aplicaccin web.

Los siguientes passado, ya s mostrados


m en el diagramaa, seran los siguientes:
s

1. El siguientte paso es ell equivalentee al ya comeentado para el servidor pero esta veez desde el
punto de vista de la aplicacin cliente. La aplicacin Android deebe registraarse en los
servidores GCM comoo cliente cappaz de recibiir mensajes desde dichoo servicio. Para P esto es
necesario que
q el dispossitivo/emulaador cumplann una serie de d requisitos:
o Dissponer de An ndroid 2.2 o superior.
o Ten ner configurrada una cueenta de Googgle en el disppositivo o em mulador. Coonfigurable
dessde Ajustes / Cuentas y sincronizacin.
o Si se trata de un u dispositivvo real debee estar instaalada la Gooogle Play Store. Por el
conntrario si esttamos ejecuttando la apliicacin desdde el emuladdor bastar con
c usar un
targget que incluuya las APIss de Google.
2. Si el registro se finaliiza correctammente se reccibir un cdigo de regiistro (Registtration ID)
que la apllicacin clieente deber conservar. Adems, laa aplicacin Android deeber estar
preparada para recibir peridicameente refrescoos de este cdigo de regiistro, ya quee es posible
que el serrvidor GCM M invalide peeridicamennte este ID, genere unoo nuevo y loo vuelva a
notificar a la aplicacinn cliente.
3. Este nuevo o paso consiiste en enviaar, desde la aplicacin cliente
c a la aplicacin servidor,
s el
cdigo de registro GCM M recibido, el cual har las veces dee identificaddor nico dell 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.
4. 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: Google Cloud


Messaging (GCM). Implementacin Servidor
Por sgoliver on 04/07/2012 en 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 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 y CodigoC2DM, 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 SqlConnection con =
4 new SqlConnection(
5 @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
6 Catalog=DBUSUARIOS;Integrated Security=True");
7
con.Open();
8
9 string cod = CodigoCliente(usuario);
10
11 int res = 0;
12 string sql = "";
13
14 if (cod == null)
15 sql = "INSERT INTO Usuarios (NombreUsuario, CodigoC2DM) VALUES
(@usuario, @codigo)";
16 else
17 sql = "UPDATE Usuarios SET CodigoC2DM = @codigo WHERE NombreUsuario
18= @usuario";
19
20 SqlCommand cmd = new SqlCommand(sql, con);
21
cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value =
22usuario;
23 cmd.Parameters.Add("@codigo", System.Data.SqlDbType.NVarChar).Value =
24regGCM;
25
26 res = cmd.ExecuteNonQuery();
27
28 con.Close();
29
return res;
30}

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 { SqlConnection con =
3 new SqlConnection(
4 @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
5 Catalog=DBUSUARIOS;Integrated Security=True");
6
7 con.Open();
8
string sql = "SELECT CodigoC2DM FROM Usuarios WHERE NombreUsuario =
9 @usuario";
10
11 SqlCommand cmd = new SqlCommand(sql, con);
12
13 cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value =
14 usuario;
15
string cod = (String)cmd.ExecuteScalar();
16
17 con.Close();
18
19 return cod;
20}

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
E GCM

El bottn de envo
o, realizar una
u bsquedda en la basee de datos deld nombre de d usuario inntroducido,
recupeerar su Registration ID y enviar unn mensaje dee prueba conn la fecha-hoora actual.

1
2 prootected void Button3_
_Click(objeect sender,
, EventArgs
s e)
{
3 oRegistroG
Servicio GCM svc = new
n Servici ioRegistroG
GCM();
4
5 string codUsuario = svc.CoddigoClientee(TxtUsuari
io.Text);
6
7 bool res = enviarMMensajePrueba(codUsuuario);
8
9 if (res == true)
LblR
ResultadoMMensaje.Texxt = "Envoo OK";
10
else
11 LblR
ResultadoMMensaje.Texxt = "Envo NOK";
12}
13

Como vemos en el e cdigo, tooda la lgicca de envo de mensajess la he encaapsulado en el mtodo


auxiliaar enviarM MensajePr rueba() para p poder centrarme
c a
ahora en ella. En este mtodo es
donde vamos a haacer realmennte uso de laa API del serrvicio de Gooogle Cloud Messaging,, y por ello
antes de
d ver la imp
plementacinn vamos a hablar
h primerro de las disttintas opcionnes de esta API.
A

Todas las llamadaas a la API de


d GCM parra enviar meensajes se reealizan mediiante peticioones HTTP
POST a la siguien
nte direccin:

https:///android.goo
ogleapis.com
m/gcm/send

La cabbecera de essta peticin debe contenner dos datoos esencialess. Por un laddo debemoss indicar la
API Key
K que gen neramos en el primer artculo
a (atriibuto Autho orization n), y por ottro lado el
formatto del contennido (en este caso, los parmetros
p d la API) que
de q vamos a incluir con la peticin
(atribuuto Conten nt-Type). GCM
G permitte formatearr los datos coomo JSON (para
( lo que habra que
indicaar el valor applicat
tion/json n) o como texto plano (para lo quue debemos utilizar el
valor applicat
tion/x-ww ww-form-u urlencode ed). En nuestro caso de ejemplo uttilizaremos
la seguunda opcinn.

Dado que hemos elegido


e la oppcin de texxto plano, loos distintos datos
d del conntenido se foormatearn
como parmetros HTTP con el
e formato tradicional, ess decir, tendrremos que construir
c unaa cadena de
la form
ma param1=
=valor1&paaram2=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 parmetro
delay_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
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.

private static bool enviarMensajePrueba(String registration_id)


1 {
2 String GCM_URL = @"https://android.googleapis.com/gcm/send";
3
4 string collapseKey = DateTime.Now.ToString();
5
Dictionary data = new Dictionary();
6 data.Add("data.msg",
7 HttpUtility.UrlEncode("Prueba. Timestamp: " +
8 DateTime.Now.ToString()));
9
10 bool flag = false;
StringBuilder sb = new StringBuilder();
11
12 sb.AppendFormat("registration_id={0}&collapse_key={1}",
13 registration_id, collapseKey);
14
15 foreach (string item in data.Keys)
16 {
17 if (item.Contains("data."))
sb.AppendFormat("&{0}={1}", item, data[item]);
18 }
19
20 string msg = sb.ToString();
21 HttpWebRequest req = (HttpWebRequest)WebRequest.Create(GCM_URL);
22 req.Method = "POST";
req.ContentLength = msg.Length;
23 req.ContentType = "application/x-www-form-urlencoded";
24
25 string apiKey = "AIzaSyCJ7QSQAznAmhDzNTLSUE6uX9aUfr9-9RI";
26 req.Headers.Add("Authorization:key=" + apiKey);
27
28 using (StreamWriter oWriter = new StreamWriter(req.GetRequestStream()))
29 {
oWriter.Write(msg);
30 }
31
32 using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
33 {
34 using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
35
string respData = sr.ReadToEnd();
36
37 if (resp.StatusCode == HttpStatusCode.OK) // OK = 200
38 {
39 if (respData.StartsWith("id="))
40 flag = true;
}
41 else if (resp.StatusCode == HttpStatusCode.InternalServerError)
42// 500
43 Console.WriteLine("Error interno del servidor, prueba ms
44 tarde.");
45 else if (resp.StatusCode == HttpStatusCode.ServiceUnavailable)
// 503
46 Console.WriteLine("Servidor no disponible temporalmente,
47prueba ms tarde.");
48 else if (resp.StatusCode == HttpStatusCode.Unauthorized)
49// 401
50 Console.WriteLine("La API Key utilizada no es vlida.");
51 else
Console.WriteLine("Error: " + resp.StatusCode);
52 }
53 }
54
55 return flag;
56}
57
58
59
60

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 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 objeto Dictionary 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 mediante GetRequestStream() 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


HttpWebResponse mediante 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.
Podis descargar el cdigo fuente completo de este artculo desde este enlace.

Actualizacin: Acabo de descubrir 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). Habr que echarle un vistazo.

Notificaciones Push Android: Google Cloud


Messaging (GCM). Implementacin Cliente
Por sgoliver on 04/07/2012 en Android, Programacin

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:
Aplicaacin Ejemplo Android GCM
G

En estta aplicacin
n, el usuario podr introdducir un nom mbre de usuaario identificcativo y pulssar el botn
Acepptar para quue quede guaardado en las preferenciaas de la apliicacin. Trass esto podr registrarse
como cliente capaaz de recibirr mensajes desde
d GCM pulsando el botn Reggistrar GCM M. En caso
de reaalizarse de fo
orma correctta este regisstro la aplicaacin enviarr automticamente el Registration
R
ID reccibido y el nombre de usuario alm macenado a la aplicacin web a traavs del serrvicio web.
Igualmmente el usu uario podr des-registraarse en el servicio
s GCM para no recibir mss mensajes
pulsanndo el botnn Des-registrar GCM. Obviamentte todo este proceso de registro y des-registro
d
debera hacerse de
d forma trannsparente paara el usuarrio de una aplicacin
a reeal, en esta ocasin he
colocaado botones para ello sllo por motivvos didcticoos.

Antes de nada vamos a preparar nuestro n proyecto de Eclipse y vamos a configurar


Manifest parra poder hacer uso del seervicio GCM
convennientementee el AndroidM M y su librera auxiliar.

Para ello
e vamos a crear un nuuevo proyectto Android sobre s un tarrget de Andrroid 2.2 o suuperior que
incluyya las libreraas de Googlee, y vamos a incluir en su
s carpeta /libs las librerras de ksoapp2 (esto ya
vimos como haceerlo en el artculo
a sobrre servicios web) y la librera clieente de GCM M llamada
gcm.jjar. Cmo o podemos obtener estta librera? Para conseguirla debem mos instalaar desde el
Androoid SDK Man nager el paqquete extra lllamado Gooogle Cloud Messaging
M fo Android Library.
for L
Librerra Google Cloud
C Messagging for Anddroid

Una vez
v instalado
o podemos irr a la ruta RAIZ_SDKK_ANDROID D/extras/ /google/g gcm/gcm-
clien nt/dist donde debber apareceer la librera gcm.jar que debem
mos aadir a nuestro
proyeccto.

Lo sigguiente ser configurarr nuestro AndroidMani


A ifest. Lo prrimero que aadiremoss ser una
clusuula <uses-sdkk> para indiicar como veersin mnim
ma del SDK soportada laa 8 (Androidd 2.2). Con
esto nos
n asegurareemos de que la aplicaciin no se innstala en disspositivos coon versin de
d Android
anterioor, no soporttadas por el servicio GCM.

1<use
es-sdk
2 android:m
minSdkVersion="8"
3 android:t
targetSdkV
Version="16
6" />

A conttinuacin a
adiremos loos permisos necesarios
n paara ejecutar la aplicacinn y utilizar GCM:
G

1<per
rmission an
ndroid:name
e="net.sgo
oliver.andr
roid.permi
ission.C2D__MESSAGE"
2andr
roid:protec
ctionLevel="signaturre" />
<use
es-permissi
ion android
d:name="net.sgoliver
r.android.p
permissionn.C2D_MESSA
AGE" />
3<use
es-permissi
ion android
d:name="com.google.a
android.c2d
dm.permisssion.RECEIV
VE" />
4<use
es-permissi
ion android
d:name="android.perm
mission.INT
TERNET" />
5<use
es-permissi
ion android
d:name="android.perm
mission.WAK
KE_LOCK" />>

Los doos primeros aseguran quue slo esta aplicacin podr


p recibirr los mensajes, el segundo permite
la recepcin
r en s de d mensajjes desde GCM (sustituir mi paquuete java
net..sgoliver r.android d por el vuuestro propioo en estas linneas), el terccero es el peermiso para
poder conectarnoss a internet y el ltimo es
e necesario para tareas realizadas durante
d la recepcin de
mensaajes que vereemos ms addelante.

Por lltimo, como componenttes de la apllicacin, adeems de la actividad


a priincipal ya aadida
a por
defectto, deberemoos declarar un
u broadcasst receiver llamado
l GCMMBroadcas stReceive er, que no
tendreemos que creearlo porquee ya viene implementad
i do dentro dee la librera gcm.jar (solo tenis
que modificar
m el elemento
e <ccategory> > para indicaar vuestro paaquete java),, y un serviccio 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
2 <application
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" >
<intent-filter>
10 <action android:name="com.google.android.c2dm.intent.RECEIVE" />
11 <action android:name="com.google.android.c2dm.intent.REGISTRATION"
12/>
13 <category android:name="net.sgoliver.android" />
</intent-filter>
14 </receiver>
15
16 <service android:name=".GCMIntentService" />
17
18</application>
19

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
visto en el curso no me detendr en el cdigo ya que es bastante directo.

1
2 btnGuardarUsuario.setOnClickListener(new OnClickListener() {
@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());
editor.commit();
9 }
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 receiver GCMBroadcastReceiver 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 clase GCMRegistrar. 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
//Si no estamos registrados --> Nos registramos en GCM
5 final String regId =
6 GCMRegistrar.getRegistrationId(GcmActivity.this);
7 if (regId.equals("")) {
8 GCMRegistrar.register(GcmActivity.this, "224338875065");
9 //Sender ID
} 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);
9 } else {
10 Log.v("GCMTest", "Ya des-registrado");
11 }
}
12});
13

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 des-registro.
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.

1 protected void onRegistered(Context context, String regId) {


2 Log.d("GCMTest", "REGISTRATION: Registrado OK.");
3
4 SharedPreferences prefs =
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
2
3 private void registroServidor(String usuario, String regId)
4 {
final String NAMESPACE = "http://sgoliver.net/";
5 final String URL="http://10.0.2.2:1634/ServicioRegistroGCM.asmx";
6 final String METHOD_NAME = "RegistroCliente";
7 final String SOAP_ACTION = "http://sgoliver.net/RegistroCliente";
8
9 SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
10
request.addProperty("usuario", usuario);
11 request.addProperty("regGCM", regId);
12
13 SoapSerializationEnvelope envelope =
14 new SoapSerializationEnvelope(SoapEnvelope.VER11);
15
16 envelope.dotNet = true;
17
18 envelope.setOutputSoapObject(request);
19
HttpTransportSE transporte = new HttpTransportSE(URL);
20
21 try
22 {
23 transporte.call(SOAP_ACTION, envelope);
24
25 SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();
String res = resultado_xml.toString();
26
27 if(res.equals("1"))
28 Log.d("GCMTest", "Registro WS: OK.");
29 }
30 catch (Exception e)
31 {
Log.d("GCMTest", "Registro WS: NOK. " + e.getCause() + " || " +
32e.getMessage());
33 }
34}
35
36

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@Override
2protected void onUnregistered(Context context, String regId) {
3 Log.d("GCMTest", "REGISTRATION: Desregistrado OK.");
4 }
5
@Override
6protected void onError(Context context, String errorId) {
7 Log.d("GCMTest", "REGISTRATION: Error -> " + errorId);
8}
9

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
2protected 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:

private void mostrarNotificacion(Context context, String msg)


1 {
2 //Obtenemos una referencia al servicio de notificaciones
3 String ns = Context.NOTIFICATION_SERVICE;
4 NotificationManager notManager =
(NotificationManager) context.getSystemService(ns);
5
6 //Configuramos la notificacin
7 int icono = android.R.drawable.stat_sys_warning;
8 CharSequence textoEstado = "Alerta!";
9 long hora = System.currentTimeMillis();
10
11 Notification notif =
new Notification(icono, textoEstado, hora);
12
13 //Configuramos el Intent
14 Context contexto = context.getApplicationContext();
15 CharSequence titulo = "Nuevo Mensaje";
16 CharSequence descripcion = msg;
17
Intent notIntent = new Intent(contexto,
18 GCMIntentService.class);
19
20 PendingIntent contIntent = PendingIntent.getActivity(
21 contexto, 0, notIntent, 0);
22
notif.setLatestEventInfo(
23 contexto, titulo, descripcion, contIntent);
24
25 //AutoCancel: cuando se pulsa la notificain sta desaparece
26 notif.flags |= Notification.FLAG_AUTO_CANCEL;
27
28 //Enviar notificacin
notManager.notify(1, notif);
29}
30
31
32
33
34
35

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:

1public GCMIntentService() {
2 super("224338875065");
3}

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:
Aplicaacin Ejemplo Android GCM
G

Y si deesplegamos la barra de estado


e verem
mos el mensaaje de pruebaa recibido:

Notificcacin GCM
M Barra de Estado
E

Como habis pod dido comproobar en estoos tres ltim


mos artculoss, la utilizaccin de mennsajes push
requieere de un prooceso algo laborioso
l peero para nadaa complicaddo. Os animoo a que lo inntentis en
vuestrras aplicacion
nes ya que puede
p represeentar una caaracterstica interesante
i y til.

Podiss descargar el
e cdigo fueente del ejem
mplo construuido en este artculo
a desdde este enlacce.

Tarreas en
n segu
undo plano
p en An
ndroid (I):
Thread y Asyn ncTask
k
Por sggoliver on 29
9/07/2012 enn Android, Programacinn

Todoss los compon nentes de unna aplicacinn Android, tanto


t las actiividades, loss servicios [ss, tambin
los serrvicios], o lo
os broadcastt receivers se
s ejecutan en
e el mismo hilo de ejeccucin, el llaamado hilo
princippal, main thread o GUI thread, que como ste ltimo
nombbre indica tam mbin 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.

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 a Thread.sleep().

1private void tareaLarga()


2{ try {
3 Thread.sleep(1000);
4 } catch(InterruptedException e) {}
5}
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 btnSinHilos.setOnClickListener(new OnClickListener() {
3 @Override
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 Toast.makeText(MainHilos.this, "Tarea finalizada!",
12 Toast.LENGTH_SHORT).show();
13 }
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 mtodo setMax(100), posteriormente la hemos inicializado al valor mnimo
mediante una llamada a setProgress(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 objeto Threaddefinido para comenzar la
ejecucin de la tarea en segundo plano.

1new Thread(new Runnable() {


2 public void run() {
3 //Aqu ejecutamos nuestras tareas costosas
4 }
}).start();
5

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 mtodo runOnUiThread() 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
mtodo runOnUiThread()para mostrar el mensaje toast.

1 new Thread(new Runnable() {


public void run() {
2 pbarProgreso.post(new Runnable() {
3 public void run() {
4 pbarProgreso.setProgress(0);
5 }
6 });
7
for(int i=1; i<=10; i++) {
8 tareaLarga();
9 pbarProgreso.post(new Runnable() {
10 public void run() {
11 pbarProgreso.incrementProgressBy(10);
}
12 });
13 }
14
15 runOnUiThread(new Runnable() {
16 public void run() {
17 Toast.makeText(MainHilos.this, "Tarea finalizada!",
Toast.LENGTH_SHORT).show();
18 }
19 });
20 }
21}).start();
22
23
24
25

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 de doInBackground() 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 mtodo publishProgress().
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 y
Booleanrespectivamente, 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 a publishProgress() 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 en onPostExecute() mostraremos el mensaje Toast de
finalizacin de la tarea. Veamos el cdigo completo:

1 private class MiTareaAsincrona extends AsyncTask {


2 @Override
3 protected Boolean doInBackground(Void... params) {
4
5 for(int i=1; i<=10; i++) {
6 tareaLarga();
7
8 publishProgress(i*10);
9
if(isCancelled())
10 break;
11 }
12
13 return true;
14 }
15
@Override
16 protected void onProgressUpdate(Integer... values) {
17 int progreso = values[0].intValue();
18
19 pbarProgreso.setProgress(progreso);
20 }
21
@Override
22 protected void onPreExecute() {
23 pbarProgreso.setMax(100);
24 pbarProgreso.setProgress(0);
25 }
26
@Override
27 protected void onPostExecute(Boolean result) {
28 if(result)
29 Toast.makeText(MainHilos.this, "Tarea finalizada!",
30 Toast.LENGTH_SHORT).show();
31 }
32
@Override
33 protected void onCancelled() {
34 Toast.makeText(MainHilos.this, "Tarea cancelada!",
35 Toast.LENGTH_SHORT).show();
36 }
37}
38
39
40
41
42
43

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 nuestra AsyncTask (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 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 la AsyncTask del ejemplo anterior, que tendremos
que modificar ligeramente para adaptarla al nuevo dilogo. Veamos el cdigo por ahora:

1
2 btnAsyncDialog.setOnClickListener(new OnClickListener() {
3
4 @Override
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
tarea2 = new MiTareaAsincronaDialog();
12 tarea2.execute();
13 }
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


mtodo setProgress() 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 {


2
3 @Override
protected Boolean doInBackground(Void... params) {
4
5 for(int i=1; i<=10; i++) {
6 tareaLarga();
7
8 publishProgress(i*10);
9
10 if(isCancelled())
11 break;
}
12
13 return true;
14 }
15
16 @Override
17 protected void onProgressUpdate(Integer... values) {
18 int progreso = values[0].intValue();
19
pDialog.setProgress(progreso);
20 }
21
22 @Override
23 protected void onPreExecute() {
24
25 pDialog.setOnCancelListener(new OnCancelListener() {
@Override
26 public void onCancel(DialogInterface dialog) {
27 MiTareaAsincronaDialog.this.cancel(true);
28 }
29 });
30
31 pDialog.setProgress(0);
32 pDialog.show();
}
33
34 @Override
35 protected void onPostExecute(Boolean result) {
36 if(result)
37 {
pDialog.dismiss();
38 Toast.makeText(MainHilos.this, "Tarea finalizada!",
39 Toast.LENGTH_SHORT).show();
40 }
41 }
42
43 @Override
protected void onCancelled() {
44 Toast.makeText(MainHilos.this, "Tarea cancelada!",
45 Toast.LENGTH_SHORT).show();
46 }
47 }
48
49
50
51
52
53
54

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, y
como siempre el cdigo fuente completo del ejemplo.

Demo Hilos y AsyncTask en Android

Tareas en segundo plano en Android (II):


IntentService
Por admin on 05/08/2012 en 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 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 utilizando
AsyncTask.

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, 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 mtodo
sendBroadcast() pasndole como parmetro el intent recin creado. Veamos cmo quedara el
cdigo completo.

1
2
3
4 public class MiIntentService extends IntentService {
5 public static final String ACTION_PROGRESO =
6 "net.sgoliver.intent.action.PROGRESO";
7 public static final String ACTION_FIN =
8 "net.sgoliver.intent.action.FIN";
9
10 public MiIntentService() {
super("MiIntentService");
11 }
12
13 @Override
14 protected void onHandleIntent(Intent intent)
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);
bcIntent.putExtra("progreso", i*10);
23 sendBroadcast(bcIntent);
24 }
25
26 Intent bcIntent = new Intent();
27 bcIntent.setAction(ACTION_FIN);
28 sendBroadcast(bcIntent);
}
29
30 private void tareaLarga()
31 {
32 try {
33 Thread.sleep(1000);
} catch(InterruptedException e) {}
34 }
35}
36
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 el
AndroidManifest.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
clase MiIntentService, 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.

1btnEjecutar.setOnClickListener(new OnClickListener() {
2
3 @Override
4 public void onClick(View v) {
Intent msgIntent = new Intent(MainActivity.this,
5MiIntentService.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
a BroadcastReceiver y que implemente su mtodo onReceive() para gestionar los
mensajes ACTION_PROGRESO y ACTION_FIN que definimos en nuestro IntentService. En
el caso de recibirse ACTION_PROGRESO extraeremos el nivel de progreso del intent recibido y
actualizaremos consecuentemente la barra de progreso mediante setProgress(). En caso de
recibirse ACTION_FIN mostraremos un mensaje Toast informando de la finalizacin de la tarea.

1 public class ProgressReceiver extends BroadcastReceiver {


2
3 @Override
4 public void onReceive(Context context, Intent intent) {
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 nuestro


BroadcastReceiver, 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.

1IntentFilter filter = new IntentFilter();


2filter.addAction(MiIntentService.ACTION_PROGRESO);
3filter.addAction(MiIntentService.ACTION_FIN);
4ProgressReceiver rcv = new ProgressReceiver();
registerReceiver(rcv, filter);
5

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.

Android IntentService

Como siempre, podis descargar el cdigo completo de la aplicacin de ejemplo construida en este
artculo.
Depuracin en Android: Logging
Por sgoliver on 28/04/2011 en 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. Error
2. Warning
3. Info
4. Debug
5. 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 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:
1
2 pub
blic class LogsAndroi
L d extends Activity {
3
4 private static final String LOGTAG = "LLogsAndroidd";
5
@Overridee
6
public void onCreate(Bundle savedInsta
s nceState) {
7 superr.onCreate(savedInsttanceState);
8 setCoontentView
w(R.layout..main);
9
10 Log.ee(LOGTAG, "Mensaje de
d error");
11 Log.ww(LOGTAG, "Mensaje de
d warning");
Log.ii(LOGTAG, "Mensaje de
d informacin");
12 Log.dd(LOGTAG, "Mensaje de
d depuracin");
13 Log.vv(LOGTAG, "Mensaje de
d verbose" ");
14 }
15}
16

Si ejeccutamos la aplicacin
a annterior en el emulador veremos
v cmmo se abre laa pantalla priincipal que
crea Eclipse
E por defecto
d y apparentementee no ocurre nada ms. Dnde
podeemos ver los mensajes
que heemos aadid do al log? Puues para ver los
l mensajess de log nos tenemos quue ir a la persspectiva de
Eclipsse llamada DDMS.
D Una vez en esta perspectiva,, podemos acceder
a a loss mensajes de
d log en la
parte inferior
i de la
l pantalla, en
e una vistaa llamada LoogCat. En estae ventana se muestrann todos los
mensaajes de log que
q genera Android
A duraante la ejecucin de la applicacin, quue son muchhos, pero si
buscammos un pocco en la listta encontrareemos los geenerados poor nuestra applicacin, taal como se
muestrra en la sigu
uiente imagenn (click paraa ampliar):

Como se puede observar,


o paara cada meensaje se muuestra toda la informaccin que inddicamos al
princippio del artcu
ulo, adems de estar difeerenciados por
p un color distinto segn su criticiddad.

En estte caso de ejemplo,


e los mensajes mostrados
m soon pocos y fciles
f de loocalizar en el
e log, pero
para una
u aplicaciin real, ell nmero de
d estos meensajes puedde ser muchho mayor y aparecer
intercaalados caticamente enttre los dems mensajes de Androidd. Para estos casos, la ventada
v de
LogCaat ofrece unau serie de
d funcionallidades paraa facilitar la visualizaacin y bsqueda de
determ
minados men nsajes.

Por ejjemplo, pod demos restrinngir la listaa para que slo muestrre mensajes con una deeterminada
criticiddad mnima.. Esto se connsigue pulsaando alguno de los 5 primeros botonnes que se observan en
la partte superior derecha
d de la
l ventana ded log. As, si por ejempplo pulsamoos sobre el botn
b de la
categoora Info (en verde), en la s mostrarnn los mensajes con criticcidad Error, Warning e
l lista slo se
Info.
Otro mtodo
m de filtrado ms innteresante es la definicin de filtros personalizaados (botn +
verde),
donde podemos filtrar la lista para mostrarr nicamentee los mensajjes con un PID o Tag deeterminado.
Si hemmos utilizado como etiqqueta de los mensajes el e nombre dee nuestra applicacin o de d nuestras
actividdades esto noos proporcioonar una forrma sencillaa de visualizaar slo los mensajes
m gennerados por
nuestrra aplicacin
n.

As, para nuestro ejemplo,


e poddramos creaar un filtro inndicando como Tag la cadena
c LogssAndroid,
tal com
mo se muestra en la siguuiente imagenn:

Esto crear
c ueva ventanna de log conn el nombre que hayam
una nu mos especificado en el filltro, donde
slo apparecern nu
uestros 5 meensajes de log de ejemploo (click paraa ampliar):

Por ltimo, cabe mencionar


m quue existe una variante dee cada uno de
d los mtoddos de la classe Log que
recibe un parmeetro ms enn el que podemos passar un objeeto de tipo excepcin. Con esto
consegguimos que, adems deel mensaje de log indiicado, se muestre
m tambbin la trazza de error
generaada con la ex
xcepcin.

Veamoos esto con un ejemploo, y para elloo vamos a forzar


f un errror de divisin por ceroo, vamos a
capturrar la excepccin y vamoss a generar un
u mensaje de
d log con la variante inddicada:

1
try
2{
3 int a = 1/0;
4}
5catc ch(Exceptio
on ex)
6 {
Log.e(LOGT
TAG, "Divisin por cero!",
c ex);
7}
8

Si vollvemos a ejjecutar la applicacin y vemos el log l generadoo, podermoss comprobaar cmo se


muestrra la traza dee error coresspondiente generada
g conn la excepcin (click paraa ampliar).
En deefinitiva, po
odemos com mprobar com mo la geneeracin de mensajes
m de log puedde ser una
herram
mienta sencillla pero muyy efectiva a la
l hora de deepurar aplicaaciones que no se ajustaan mucho a
la depuracin paso
o a paso, o simplemente
s e para generaar trazas de ejecucin dee nuestras applicaciones
para comprobar dee una forma sencilla cm mo se compoortan.

e cdigo fueente utilizadoo en este artculo desde este enlace.


Podiss descargar el

Vous aimerez peut-être aussi