Primeros pasos en Android (II)
0. Índice de contenidos.
- 1. Introducción
- 2. Entorno
- 3. Formas de compartir la información
- 4. Interfaces gráficos en Android
- 5. Usando onClickListener
- 6. Conclusiones
- 6. Información sobre el autor
1. Introducción
En este tutorial vamos a seguir profundizando en el mundo Android. Para poder seguir el tutorial correctamente os recomiendo leer y seguir antes el tutorial previo a este, ya que vamos a seguir añadiendo elementos a la aplicación de ejemplo que ya tenemos creada.
2. Entorno
- Hardware: Portátil MacBook Pro 15′ (2.0 GHz Intel i7, 8GB DDR3 SDRAM, 500GB HDD).
- AMD Radeon HD 6490M 256 MB
- Sistema Operativo: Mac OS X Snow Leopard 10.6.7
- Software: Eclipse Juno
- Hardware: Samsung Galaxy R
- Software: Android 2.3.5
3. Formas de compartir la información
El traspaso de información entre componentes es algo básico en todo lenguaje de programación. En Java, por ejemplo, para pasar información de un objeto a otro podemos utilizar un constructor que recibe parámetros, podemos utilizar los setters para cambiar su estado y darle
información. Se puede acceder en definitiva de un objeto o a otro y cambiar sus atributos siempre que se tenga visibilidad sobre ellos. Para las actividades de Android esto es distinto, y si no lo he dicho antes lo digo ahora. Una actividad nunca tiene que manipular memoria que esté usando otra. Es cierto que a veces necesitamos en una actividad información de otra actividad previa. Esto lo podemos conseguir de varias formas.
3.1 Intent
La clase Intent, que vimos con un poco más de detalle en el tutorial anterior, tiene una función putExtra(String,String), que nos permite insertar pares de Strings, como si fueran «key» y «value». Luego podemos preguntar si existe el «key» desde otra clase para recuperar el «value». Vamos a ver como lo haríamos. Nos vamos a nuestra clase FirstActivity.java y vamos a agregar las siguientes líneas a nuestro método onKeyDown():
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if ( keyCode == KeyEvent.KEYCODE_MENU ) { Intent startIntent = new Intent(this.getApplicationContext(), SecondActivity.class); startActivity(startIntent); return true; } if ( keyCode == KeyEvent.KEYCODE_VOLUME_UP ) { Intent actionStartIntent = new Intent("com.autentia.THERE_WOULD_BE_CAKE"); actionStartIntent.putExtra("message", "Subiendo volumen !!!"); startActivity(actionStartIntent); return true; } return super.onKeyDown(keyCode, event); }
Las líneas que he añadido son desde la 8 hasta la 12 y son todas importantes:
- En la línea 8, de forma homóloga a como hacíamos antes, preguntamos si la tecla que hemos pulsado es la de subir el volumen, que es cuando queremos hacer nuestra acción.
- En la línea 9 estamos declarando un Intent con un nombre extraño. No os preocupéis que al final habrá past… una explicación detallada.
- En la línea 10 estamos usando la función que os comentaba antes putExtra(), donde le indicamos que el key «message» contiene el value «Subiendo Volumen !!!».
- En las líneas 11 y 12 simplemente lanzamos la actividad y devolvemos true, de forma análoga a como se explicaba en el tutorial anterior.
Para que encajen todas las piezas os voy a poner lo que tenemos que añadir en el manifest.xml.
<activity android:name=".SecondActivity"> <intent-filter> <action android:name="com.autentia.THERE_WOULD_BE_CAKE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity>
- En la línea 4 estamos declarando un Intent para SecondActivity. He querido ponerle este nombre, no solo para demostrar que soy un «gamer», sino para demostrar que el único requisito para poner nombres a los Intents, es que una clase no tenga registrados varios Intents con el mismo nombre, pues no sabría cual ejecutar. Ahora cualquier actividad de cualquier aplicación que supiera que nuestra clase tiene registrado un Intent de este tipo «com.autentia.THERE_WOULD_BE_CAKE», podría ejecutar nuestra actividad.
- En la línea 5 simplemente declaro que la categoría del Intent es la de por defecto, lo que hace que la actividad pueda ser encontrada por Context.startActivity().
La última pieza de este puzzle está en la clase SecondActivity.java, y en como va a gestionar la información que le viene en «extra».
@Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.second_activity); final Intent currentIntent = getIntent(); if(currentIntent.hasExtra("message")){ final String newText = currentIntent.getExtras().getString("message"); } }
Esto no necesita mucha explicación, es simplemente la extracción de la información que nos pasó la otra actividad. Luego ya haremos algo con este mensaje, pero no quiero desvelar sorpresas por ahora.
3.2 La clase Application
La clase Application es el pegamento que une todas las actividades, servicios y recibidores en una entidad unificada. Es difícil entender que puede hacer la clase application por tí sin tener en cuenta los siguientes puntos:
- Las actividades son muy volátiles.
- Las actividades no tienen acceso a la zona de memoria de otras actividades.
- Las actividades se paran y arrancan todo el tiempo por muchas razones y no hay manera de saber si una actividad ya ha sido iniciada antes o es esta su primera vez, mientras que el onCreate de la clase Application solo se llama la primera vez que se inicia , por lo que puede ser un buen sitio para iniciar cosas que solo se necesiten poner al principio.
- Todas los recibidores de broadcast, servicios y actividades tienen un método en su superclase getApplication(), que si se castea resulta en la clase del tipo que hayamos definido: SampleApplication myApplication = (SampleApplication) getApplication();
Si necesitamos un sitio donde guardar datos que vayan a usar muchas actividades la clase Application, puede ser un buen sitio. Debemos tener mucho cuidado añadiendo datos a esta clase, ya que se iniciará antes que cualquier componente declarado en nuestro manifest, por lo que debemos hacer el método onCreate lo más ligero posible.
Creamos una clase Application es muy sencillo. Primero la definimos en el manifest, después de la Application que ya tenemos creada …
<application android:icon="@drawable/icon" android:label="@string/app_name" android:name= ".SampleApplication"/>
… y como ya os estáis imaginando creamos también la clase.
package com.autentia.android; import android.app.Application; public class SampleApplication extends Application { public String username; public void onCreate(){ super.onCreate(); } }
Y de forma análoga a como os comentaba antes, para acceder a este atributos, primero hacemos el cast, desde donde queramos, y luego seteamos la información
SampleApplication myApplication = (SampleApplication) getApplication(); myApplication.username = "Alberto";
Debemos estar seguros de que la información que guardemos en Application es relevante en la mayoría de ocasiones, ya que almacenar datos en esta clase puede penalizar el inicio de una aplicación de forma considerable.
4. Interfaces gráficos en Android
Ya hemos visto que los interfaces gráficos en Android se contruyen mediante documentos XML, pero también se puede hacer de forma programática mediante JAVA. Aunque no es recomendable hacerlo, si conviene saber cómo se hace, ya que alguna vez nos va a tocar
modificar la vista en función de eventos. Además el programar los interfaces solamente con JAVA conlleva que una persona que no sepa JAVA no podrá tocar la vista, mientras que si la tenemos en XML es más fácil de entender o incluso de que una persona sin muchos conocimientos haga cambios.
Vamos a hacernos un ejemplo de como hacer cambios en la vista con JAVA. El ejemplo va a ser el siguiente: Cuando hablábamos al principio del tutorial de la clase Intent y de como pasar información de una Actividad a otra, nos pasamos el mensaje «Subiendo el Volumen !!!», cuando se presionaba la tecla de subir volumen. Ahora vamos a hacer que este mensaje que nos pasamos se muestre por pantalla, y vamos a ir paso a paso, para que veais todos los elementos indispensables para hacer esto.
Lo primero vamos a la vista y añadimos lo siguiente:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/second_activity_text" android:id="@+id/second_activity_text_view"> </TextView> </LinearLayout>
No hemos añadido mucho a lo que ya teníamos, pero hay 2 cosas muy importantes que quiero que queden muy claras.
- 1. Cualquier elemento gráfico en Android hereda de la clase View. Todos estos elementos (LinearLayout, TextView, Button …) TIENEN que tener un alto(height) y un ancho(width) seteados. Si no lo tienen es posible que el compilador de Android no os falle, pero si se empieza a pintar una vista que no tenga seteados ambos valores la aplicación fallará. Esto es muy importante.
- 2. Hemos añadido un ID al TextView. TextView hereda de View, por lo que desde cualquier clase java vamos a poder acceder a este elemento, a esta vista, con el método findViewById(R.id.second_activity_text_view), así que es necesario ponerle un ID si luego desde JAVA queremos poder acceder a él.
Ahora nos vamos a la clase SecondActivity para añadir los siguientes cambios.
@Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.second_activity); final Intent currentIntent = getIntent(); if(currentIntent.hasExtra("message")){ final String newText = currentIntent.getExtras().getString("message"); TextView bodyView = (TextView)findViewById(R.id.second_activity_text_view); bodyView.setText(newText); } }
- En la línea 7 estoy accediendo al Intent que ha desencadenado el inicio de esta Actividad.
- En la línea 8 compruebo si tiene el extra con el key «message», que es el que tendrá cuando esta Actividad se inicie por el Intent «com.autentia.THERE_WOULD_BE_CAKE». Pudiera suceder que 2 Intents tuvieran el extra «message», y que quisieramos tratarlo de formas distintas. En este tutorial no va a pasar, pero en una aplicación real puede que queramos comprobar que Intent nos está llamando. Para comprobarlo podemos hacer lo siguiente: if ( «com.autentia.THERE_WOULD_BE_CAKE».equals(currentIntent.getAction()) ) hacemos A, else hacemos B.
- En la línea 9 nos guardamos el valor de «message», que será «Subiendo el Volumen !!!».
- En la línea 10 estamos buscando la vista que queremos modificar. Para eso utilizamos el método findViewById y buscamos nuestro TextView por el ID que le dimos antes. Es importante comentar que el método findViewById debe usarse después de haber seteado la vista con el setContentView, porque sino, no la va a encontrar. También comentar que puede ser una buena idea comprobar que bodyView no es nulo, y que el sistema realmente nos ha devuelto lo que queremos.
- En la línea 11 estamos cambiando programáticamente el valor del TextView que previamente contenía «Bienvenido a la segunda Actividad !» por el nuevo mensaje «Subiendo el Volumen !!!». La vista se pintará directamente con el nuevo valor.
5. Usando onClickListener
Como su propio nombre indica es un «escuchador de clicks», y los vamos a usar para interactuar con los usuarios de nuestras aplicaciones. La idea es que cuando un usuario haga click en un determinado sitio la aplicación hace X acción y cuando haga click en otro hace Z.
Vamos a hacer otro ejemplo, que es como creo que se aprenden mejor las cosas. En la actividad SecondActivity vamos a incorporar 2 botones, uno con el texto «Botón 1» y otro con «Botón 2», y vamos a hacer que ambos botones tengan listeners a la espera de que el usuario los pulse. Cuando se pulse uno de ellos sacaremos un mensaje por pantalla informando al usuario de cual ha pulsado.
Para empezar vamos a ir a la vista de SecondActivity y añadimos los dos botones.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/second_activity_text" android:id="@+id/second_activity_text_view"> </TextView> <Button android:layout_height="wrap_content" android:id="@+id/second_activity_button1" android:layout_width="wrap_content" android:text="@string/button1_text"> </Button> <Button android:layout_height="wrap_content" android:id="@+id/second_activity_button2" android:layout_width="wrap_content" android:text="@string/button2_text"> </Button> </LinearLayout>
Como los botones son elementos gráficos heredan de View y ya sabeis que les tenemos que añadir ancho y alto para que no falle el renderizado de la vista. Le añadimos también IDS, pues luego programáticamente les asignaremos a los botones los listeners, y para encontrarlos necesitamos que tengan ids. También les estamos poniendo un texto a los botones. Ya sabéis, estos textos, al archivo strings.xml. Os lo pongo debajo.
<?xml version="1.0" encoding="utf-8"?> <resources> ... ... <string name="button1_text">Botón 1</string> <string name="button2_text">Botón 2</string> </resources>
Ahora vamos a asignarles los listener programáticamente. La clase SecondActivity os debería quedar de la siguiente forma:
package com.autentia.android; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class SecondActivity extends Activity{ private View.OnClickListener clickListenerForButtonOne = new View.OnClickListener() { public void onClick(View v) { Toast.makeText(v.getContext(), R.string.button1_message, Toast.LENGTH_LONG).show(); } }; private View.OnClickListener clickListenerForButtonTwo = new View.OnClickListener() { public void onClick(View v) { Toast.makeText(v.getContext(), R.string.button2_message, Toast.LENGTH_LONG).show(); } }; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.second_activity); final Intent currentIntent = getIntent(); if(currentIntent.hasExtra("message")){ final String newText = currentIntent.getExtras().getString("message"); TextView bodyView = (TextView)findViewById(R.id.second_activity_text_view); bodyView.setText(newText); } final Button button1 = (Button) findViewById(R.id.second_activity_button1); if ( button1 != null ) { button1.setOnClickListener(clickListenerForButtonOne); } final Button button2 = (Button) findViewById(R.id.second_activity_button2); if ( button2 != null ) { button2.setOnClickListener(clickListenerForButtonTwo); } } }
- En la línea 41 recuperamos el botón de forma análoga a cuando recuperamos antes el TextView. En esta ocasión sí chekeamos que sea nulo, y en el caso de no serlo le asignamos el onClickListener
- En la línea 13 vemos como se crea un View.onClickListener, que no es más que un interfaz con un método de «onClick», donde se implementa la acción que se va a ejecutar.
- En la línea 17 estamos mostrando por pantalla un mensaje informando al usuario de que a pulsado el botón 1.
Para terminar solo nos queda añadir estos mensajes en el fichero strings.xml.
... <string name="button1_message">Has pulsado el botón 1</string> <string name="button2_message">Has pulsado el botón 2</string> ...
6. Conclusiones
Con estos tutoriales nos hemos sumergido un poco más en el mundo Android y ahora tenemos un buen punto de partida para seguir aprendiendo cosas nuevas y mejores prácticas. Espero haber sido lo suficientemente claro explicando las cosas, y si no es así o tenéis alguna duda podéis dejarla en la sección de comentarios. Espero que os haya gustado, un saludo !!
7. Información sobre el autor
Alberto Barranco Ramón es Ingeniero Técnico en Informática de Gestión y Graduado en Ingeniería del Software por la Universidad Politécnica de Madrid
Mail: abarranco@autentia.com.
Twitter: @barrancoalberto
Autentia Real Business Solutions S.L. – «Soporte a Desarrollo».