martes, 11 de diciembre de 2007

Manos a la obra - Tutorial - Ejercicio #2

Documento original: Tutorial: Notepad Exercise 2

En este ejercicio agregaremos una segunda "Activity" a nuestra aplicación de anotaciones y permitiremos que el usuario cree, edite y borre anotaciones. La nueva "Activity" asumirá la responsabilidad de crear nuevas anotaciones al colectar los datos de entrada del usuario y empaquetarlos y devolverlos a la "Activity" que la invocó.

Este ejercicio demostrará:

  • Desarrollo de una nueva "Activity" y cómo agregarla al archivo "Android manifest".
  • Invocación asincrónica de otra "Activity" usando "startSubActivity()".
  • Paso de datos entre "Activity" ("bundles").
  • Cómo utilizar características más avanzadas de diseño de pantalla.
Paso #1

Importa el proyecto "Notepadv2" de la misma forma en que lo describí en el tutoríal anterior. Los archivos de este proyecto están localizados en el directorio "NotepadCodeLab". Si se te presenta un error con el archivo "AndroidManifest.xml" o con el archivo "android.zip", abre el menú de contexto del proyecto y selecciona la opción "Android Tools->Fix Project Properties".

Expande el proyecto "Notepadv2" y dale un vistazo general:

  • Abre y examina el archivo "strings.xml" (directorio "res/values"). Verás la declaración de nuevas cadenas de texto, las cuales las usaremos en las nuevas funcionalidades.
  • Abre y examina el encabezado de la clase "Notepadv2". Notarás que también hay algunos cambios: declaraciones de nuevas constantes y propiedades.
  • También notarás que el método "fillData()" ha sido modificado para usar la propiedad "rows" en vez de una variable local.
  • Adicionalmente, hemos sobreescrito nuevos métodos: "onListItemClick()" y "onActivityResult()".
Paso #2

Revisa el método "onCreate()", verás que no ha cambiado desde la versión anterior.

Paso #3

Debemos agregar la opción "Delete Note" al menú de la "Activity". Para esto:

  • En el método "onCreateOptionsMenu()" agrega la siguiente línea:
menu.add(0, DELETE_ID, R.string.menu_delete);

  • Ahora, el método debería verse así:
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, R.string.menu_insert);
menu.add(0, DELETE_ID, R.string.menu_delete);
return true;
}

Paso 4
  • En el método "onMenuItemSelected()" agrega el siguiente fragmento de código:

case DELETE_ID:
dbHelper.deleteRow(rows.get(getSelection()).rowId);
fillData();
break;

Este código usa el método "getSelection()" perteneciente a "ListActivity", el cual nos indica cual es el actual elemento seleccionado de la lista. Luego, referenciamos el registro de esa anotación y finalmente tomamos el valor de la propiedad "rowId". Es este valor el que finalmente utilizamos con "deleteRow" para borrar el registro.

  • Posteriormente, llamamos al método "fillData()" para mantener todo actualizado.
  • El método debería quedar de la siguiente forma:
@Override
public boolean onMenuItemSelected(int featureId, Item item)
{
super.onMenuItemSelected(featureId, item);
switch(item.getId()) {
case INSERT_ID:
createNote();
fillData();
break;
case DELETE_ID:
dbHelper.deleteRow(rows.get(getSelection()).rowId);
fillData();
break;
}
return true;
}


Paso 5

Ahora, modificaremos el cuerpo del método "createNote()":

  • Definiremos un "Intent" para crear una anotación ("ACTIVITY_CREATE"). Los argumentos que utilizaremos será "this" y la clase "NoteEdit". A continuación, gatillaremos el "Intent" usando el método "startSubActivity()".
NOTA: La clase "NoteEdit" será definida en unos momentos.

NOTA: En este ejemplo, nuestro "Intent" utiliza el nombre específico de una clase. Mientras esto es adecuado para determinadas circunstancias, lo más común es invocar un "Intent" usando una "Action" y una "URI". Ver "android.content.Intent" para más información.

Paso 6

Modificaremos el cuerpo del método "onListItemClick()".

El método "onListItemClick()" sobreescribe el método que es llamado cuando un usuario selecciona un elemento de la lista. Este método entrega 4 parámetros:

  • Objeto ListView : referencia al objeto desde el cual fue invocado el elemento.
  • Objeto View : referencia al objeto que fue seleccionado dentro de la ListView.
  • Un entero con la posición del elemento seleccionado.
  • Un entero con la identificación del elemento que fue seleccionado.
En nuestro ejemplo, podemos ignorar los dos primeros parámetros (tenemos sólo una "ListView" desde la cual el elemento pudo ser seleccionado). También ignoraremos la identificación del elemento. Todo lo que nos interesa por ahora es la posición del elemento que el usuario seleccionó. Utilizaremos esta posición para recuperar los datos de la anotación desde la lista y los empaquetamos para enviarlo a la "Activity" "NoteEdit".

Este método crea el "Intent" para editar la anotación usando la clase "NoteEdit". Luego, agregamos todos los datos al "Intent" (título, cuerpo de la anotación y la identificación). Finalmente, gatillamos el "Intent" invocando al método "startSubActivity()" con los argumentos "ACTIVITY_EDIT" y la clase "NoteEdit".
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(KEY_ROW_ID, rows.get(position).rowId);
i.putExtra(KEY_BODY, rows.get(position).body);
i.putExtra(KEY_TITLE, rows.get(position).title);
startSubActivity(i, ACTIVITY_EDIT);
El método "putExtra()" permite agregar elementos adicionales al paquete del "Intent".

Paso 7

A continuación modificaremos el método "onActivityResult()".

El método "onActivityResult()" sobreescribe un método que es invocado cuando una sub "Activity" finaliza su tarea. Los parámetros que este método entrega son:

  • requestCode : s el código original utilizado en la invocación del "Intent" (en nuestro caso podrá ser "ACTIVITY_CREATE" o "ACTIVITY_EDIT").
  • resultCode : es el resultado (o código de error) del llamado. Este valor debería ser cero si todo fue correcto y valor distinto para indicar que algo falló. Existen algunos códigos predefinidos que pueden ser utilzados, pero también pueden definirse nuevos códigos para errores más específicos.
  • data : esta es una cadena de texto que es utilizada para recibir algún tipo de información descriptiva (por ejemplo: el texto que el usuario escribió en un cuadro de diálogo). Esto es útil únicamente cuando tenemos sólo una cosa que devolver. Si requerimos retornar más información, debemos usar "extras".
  • extras : este parámetro entrega los "extras bundle" (si es que existen) que retornó el "Intent" invocado.
La combinación de "startSubActivity()" y "onActivityResult()" puede ser vista como un llamado asincrónico a un procedimiento remoto ("asynchronous RPC" o "remote procedure call"). Esta es la forma recomendada para que una "Activity" invoque a otra "Activity" y compartir servicios.

A continuación está el código completo de este método:


@Override
protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras)
{
super.onActivityResult(requestCode, resultCode, data, extras);

switch(requestCode) {
case ACTIVITY_CREATE:
String title = extras.getString(KEY_TITLE);
String body = extras.getString(KEY_BODY);
dbHelper.createRow(title, body);
fillData();
break;
case ACTIVITY_EDIT:
Long rowId = extras.getLong(KEY_ROW_ID);
if (rowId != null) {
String editTitle = extras.getString(KEY_TITLE);
String editBody = extras.getString(KEY_BODY);
dbHelper.updateRow(rowId, editTitle, editBody);
}
fillData();
break;
}
return;
}

  • En este método distinguimos los resultados de dos "Activity": "ACTIVITY_CREATE" y "ACTIVITY_EDIT".
  • En el caso de "ACTIVITY_CREATE", tomamos el título y el cuerpo desde "extras" para usarlos en la creación de la anotación.
  • En el caso de "ACTIVITY_EDIT", tomamos además el "KEY_ROW_ID" para localizar el registro que deseamos actualizar.
  • El método "fillData()" al final nos asegura que todo será actualizado.
Paso 8

Abre el archivo "note_edit.xml" y revísalo. Este archivo contiene la definición de la pantalla que será usada para editar nuestras anotaciones.

Este archivo hace uso de un nuevo parámetro: "android:layout_weight" (en este caso tiene el valor "1").

layout_weight es usado en "LinearLayouts" para asignar a las vistas un nivel de "importancia" dentro del diseño de la pantalla. Todas las vistas tienen un valor por omisión de cero para "layout_weight", lo cual significa que ellas pueden ocupar el espacio que requieran para ser desplegadas. Al asignar un valor mayor que cero hará que el resto del espacio disponible sea dividido entre el padre de acuerdo al valor de "layout_weight" y su proporción con el total de "layout_weight", especificado en el actual trazado y en otras vistas.

Para dar un ejemplo de lo anterior, digamos que tenemos una etiqueta de texto y dos áreas de edición en un vista horizontal. La etiqueta de texto no tiene especificado valor para su "layout_weight", por lo tanto tomará el mínimo espacio requerido para desplegarse. Si el valor de "layout_weight" para las áreas de texto es "1", el restanto espacio dentro del área definida por el padre será dividida entre ambas. Si la primera área tuviera un valor de "1" y la segunda un valor de "2" para "layout_weight", entonces un tercio del espacio disponible será entregado a la primera área y los dos restantes a la segunda.

Este diseño de pantalla también demuestra cómo anidar múltiples esquemas para así lograr complejos trazados de pantalla. En este caso, el esquema horizontal lineal está anidado dentro de uno vertical, lo cual permite que la etiqueta del título y el área de edición estén una al lado de otra.

Paso 9

Crea una clase llamada "NoteEdit" y que extienda "android.app.Activity".

Esta es la primera vez que crearemos una "Activity" sin la ayuda del "Android Eclipse plugin". Al hacerlo de esta manera, el método "onCreate()" no es automáticamente etiquetado con la anotación de sobreescritura. Es difícil imaginar una "Activity" que no sobreescriva el método "onCreate()".

A continuación están las instrucciones en detalle:

  1. Abre el menú de contexto del paquete "com.google.android.demo.notepad2" y selecciona "New->Class".
  2. Escribe en el campo "Name:" el nombre de la clase ("NoteEdit").
  3. En el campo "Superclass:", escribe "android.app.Activity" o puedes simplemente escribir "Activity" y presionar "Ctrl-Space" (Windows y Linux) para invocar el asistente de código y encontrar path completo del paquete en el cual está esta clase.
  4. Presiona el botón "Finish".
  5. En el editor de texto abre el menú de contexto y selecciona la opción "Source", luego selecciona "Override/Implement Methods...".
  6. Busca el método "onCreate(Bundle)" y selecciónalo.
  7. Presiona el botón "OK".
Paso 10

Completaremos el código del método "onCreate()".

Haremos las declaraciones necesarias para definir el título de nuestra "Activity" ("Edit Note"). Este valor está definido en el archivo "strings.xml". Aquí también definiremos que la vista que utilizaremos será aquella definida en el archivo "note_edit.xml layout". Después de esto, podemos asociar a objetos Java las vistas correspondientes al título, el cuerpo de la anotación y el botón de confirmación. De esta forma, nuestro código puede usarlos para asignar y recuperar el texto del título y el cuerpo; y además asociar cñodigo al evento gatillado cuando el usuario presiona el botón de confirmación.

Los valores que fueron enviados por la "Notepadv2" serán extraidos desde el paquete y usados para llenar el título y el cuerpo de la anotación. La identificación de la anotación la almacenaremos para saber que registro estamos editando.

  • Asociación de la "Activity" con un esquema:
setContentView(R.layout.note_edit);

  • Examinar el esquema de pantalla para encontrar el título, el cuerpo y el botón de confirmació. Utilizamos los identificadores generados en la clase "R" y luego los convertimos al tipo apropiado.
titleText = (EditText) findViewById(R.id.title);
bodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);


NOTA: "titleText" y "bodyText" son propiedades de la clase que aún no hemos definido.

  • Declarar una propiedad llamada "rowId", la cual almacenará la identificación del registro editado.
private Long rowId;

  • Agregar código para inicializar el título, cuerpo y la propiedad "rowId" a partir de los datos extraidos del paquete recibido desde "Notepadv2".
rowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(Notepadv2.KEY_TITLE);
String body = extras.getString(Notepadv2.KEY_BODY);
rowId = extras.getLong(Notepadv2.KEY_ROW_ID);

if (title != null) {
titleText.setText(title);
}
if (body != null) {
bodyText.setText(body);
}
}


  • Definir un controlador para el evento clic del botón de confirmación. Esto lo hacemos utilizando el método "onClickListener()".
NOTA: Los controladores de eventos ("listeners") son uno de los aspectos más difíciles de entender en la implementación de una "UI", pero lo que intentamos hacer aquí es bastante simple. Lo que deseamos hacer es simplemente llamar al método "onClick()" cuando el usuario presione el botón de confirmación. Dentro de este método podremos hacer los necesario para devolver los valores ingresados por el usuario al "Intent" que llamo a esta "Activity". La técnica empleada en este código se llama "anonymous inner class". Resulta enredado de leer a menos que hayas visto este patrón anteriormente, pero por ahora sólo nos interesa usar este código a modo de receta de cómo invocar un método como reacción a un evento de la "UI".

Paso 11

Completaremos el cuerpo del método "onClick()".

Este es el código que se ejecutará cuando el usuario haga clic sobre el botón de confirmación. Aquí es donde debemos recuperar el título y cuerpo de la anotación y colocarlos dentro del paquete de datos que devolveremos a la "Activity" que invocó esta "Activity". Si la operación es editar en vez de crear, también deberemos poner la identificación del registro de la anotación.

  • Creación del paquete donde pondremos el título y cuerpo de la anotación.
Bundle bundle = new Bundle();

bundle.putString(Notepadv2.KEY_TITLE, titleText.getText().toString());
bundle.putString(Notepadv2.KEY_BODY, bodyText.getText().toString());
if (rowId != null) {
bundle.putLong(Notepadv2.KEY_ROW_ID, rowId);
}


  • Asignar la información de resultado, incluyendo el paquete y resultado final de la operación.
setResult(RESULT_OK, null, bundle);
finish();


El método "setResult()" es usado para asignar el código de retorno, la cadena de información textual y el paqueta de datos que será entregado al "Intent" que invocó a esta "Activity". En este caso suponemos que todo estará bien, asi es que devolvemos "RESULT_OK" como valor del código de retorno. No usaremos la cadena de información textual , por esta razón estamos pasando el valor "null".

El método "finish()" es usado como una señal que la "Activity" ha realizado su tarea (una especia de "return"). Todos los argumentos entregados a "setResul()" van a ser devueltos junto con el control de ejecución.

  • La siguiente es la declaración de las propiedades utilizadas en el código de esta clase.
private EditText titleText;
private EditText bodyText;
private Long rowId;

  • El código completo del método "onCreate()" es mostrado a continuación.
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.note_edit);

titleText = (EditText) findViewById(R.id.title);
bodyText = (EditText) findViewById(R.id.body);

Button confirmButton = (Button) findViewById(R.id.confirm);

rowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
String title = extras.getString(Notepadv2.KEY_TITLE);
String body = extras.getString(Notepadv2.KEY_BODY);
rowId = extras.getLong(Notepadv2.KEY_ROW_ID);

if (title != null) {
titleText.setText(title);
}
if (body != null) {
bodyText.setText(body);
}
}

confirmButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {
Bundle bundle = new Bundle();

bundle.putString(Notepadv2.KEY_TITLE, titleText.getText().toString());
bundle.putString(Notepadv2.KEY_BODY, bodyText.getText().toString());
if (rowId != null) {
bundle.putLong(Notepadv2.KEY_ROW_ID, rowId);
}

setResult(RESULT_OK, null, bundle);
finish();
}

});
}

Paso 12

Finalmente, la nueva "Activity" debe ser declarada en el archivo "Android Manifest".

Antes de que una actividad pueda ser "vista" por Android, esta actividad debe tener su propia entrada en el archivo"AndroidManifest.xml". Esto le entregará al sistema la información necesaria para localizar la actividad y saber que requiere para funcionar.

A continuación se muestra el archivo "
AndroidManifest.xml" modificado:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.demo.notepad2">
<application android:icon="@drawable/icon">
<activity class=".Notepadv2" android:label="@string/app_name">
<intent-filter>
<action android:value="android.intent.action.MAIN" />
<category android:value="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity class=".NoteEdit"/>
</application>
</manifest>


Paso 13

Ejecución de la aplicación.

1. Haz clic con el botón derecho sobre el proyecto "Notepadv2" para abrir el menú de contexto y seleccionar "Run As > Android Application".

No hay comentarios.: