miércoles, 12 de diciembre de 2007

Manos a la obra - Tutorial - Ejercicio #3

Documento original : Tutorial: Notepad Exercise 3

En este ejercicio, usaremos el ciclo de vida de los métodos "callback" asociados a un evento para almacenar y recuperar los datos del estado de la aplicación.

Este ejercicio demostrará:
  • El ciclo de vida de un evento y cómo una aplicación puede hacer uso de ellos.
  • Técnicas para mantener el estado de la aplicación.

Paso 1

La aplicación que terminamos de desarrollar en el tutorial anterior tiene un grave defecto. Si presionas el botón "Back" del dispositivo cuando estás editando una anotación, esto causará que se lance una "exception" producto que la "Activity" que espera datos no podrá recuperar el texto que estaba siendo editado.

Para remediar lo anterior, moveremos parte de la funcionalidad de creación y edición de una anotación dentro de la clase "NoteEdit" y utilizaremos el completo ciclo de vida de una "Activity" en la edición de una anotación.

  • Importa el proyecto "Notepadv3" dentro de Eclipse. 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". El punto de partida de este ejercicio es el proyecto "Notepadv2".
  • Remove the NoteEdit code to parse out the title and body from the extras bundle.
  • Vamos a usar la clase "DBHelper" para accesar las anotaciones directamente desde la base de datos. Todo lo que debemos entregar a "NoteEdit" es el "rowId" (si es que estamos editando, pero si estamos creado no necesitamos entregarla nada). Con este nuevo enfoque no tendremos que utilizar las propiedades que estaban siendo pasadas como datos del paquete.
  • Remueve las siguientes líneas desde la clase "NoteEdit":
String title = extras.getString(Notepadv3.KEY_TITLE);
String body = extras.getString(Notepadv3.KEY_BODY);

también elimina las siguientes líneas:

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

NOTA: Podrías reemplazar el bloque "if (extras != null) {...}" con el operador ternario:

rowId = xtras != null ? extras.getLong(Notepadv3.KEY_ROW_ID) : null;

Paso 2

Declara la siguiente nueva propiedad dentro de la clase "NoteEdit":

private DBHelper dbHelper;

luego, crea una instancia de "DBHelper" en el método "onCreate()", justo debajo del llamado al método "super.onCreate()":


dbHelper = new DBHelper(this);

Paso 3

Necesitamos verificar el argumento de "onCreate(Bundle ...)" para comprobar el "rowId" y así valor si la anotación que estaba siendo editada ha sido congelada.

Reemplaza al siguiente código que inicializa "rowId":

rowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
rowId = extras.getLong(Notepadv3.KEY_ROW_ID);
}

por este código:

rowId = icicle != null ? icicle.getLong(Notepadv3.KEY_ROW_ID) : null;
if (rowId == null) {
Bundle extras = getIntent().getExtras();
rowId = extras != null ? extras.getLong(Notepadv3.KEY_ROW_ID) : null;
}

NOTA: Si "rowId" no pudo recuperarse a partir de "icicle", entonces debemos recuperarlo desde el paquete de datos.

Paso 4

A continuación, necesitamos llenar las propiedades basándonos en el "rowId" que tenemos. Para ello agrega el siguiente llamado:

populateFields();

antes de la sentencia "confirmButton.setOnClickListener(...)"

Paso 5

Eliminaremos todo el código asociado con la creación del paquete de datos y la asignación de valores en el método controlador de evento "onClick(...)". La "Activity" ya no necesita devolver ninguna información adicional a quien la invocó. También podemos utilizar una versión corta del método "setResult()":


public void onClick(View arg0)
{
setResult(RESULT_OK);
finish();
}

La nueva versión del método "onCreate(...)" debería quedar de la siguiente forma:


@Override
protected void onCreate(Bundle icicle)
{
super.onCreate(icicle);
dbHelper = new DBHelper(this);
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 = icicle != null ? icicle.getLong(Notepadv3.KEY_ROW_ID) : null;
if (rowId == null) {
Bundle extras = getIntent().getExtras();
rowId = extras != null ? extras.getLong(Notepadv3.KEY_ROW_ID) : null;
}

this.populateFields();

confirmButton.setOnClickListener(
new View.OnClickListener()
{
public void onClick(View arg0) {
setResult(RESULT_OK);
finish();
}
}
);
return;
}

Paso 6

Declararemos el método "populateFields()".

private void populateFields()
{
if (rowId != null)
{
DBHelper.Row row = dbHelper.fetchRow(rowId);
if (row.rowId > -1)
{
titleText.setText(row.title);
bodyText.setText(row.body);
}
}
return;
}

Paso 7

¿Por qué es tan importante manipular los eventos del ciclo de vida?

Si tú estás acostumbrado a tener pleno control sobre tus aplicaciones, podrías estarte preguntando por qué es necesario este concepto de ciclo de vida. La razón es porque en Android tú no tienes el control de tus "Activity" sino que es el sistema operativo.

Tal como ya hemos visto, el modelo de Android está basado en la colaboración mutual entre "Activity" llamándose unas a otras. Cuando una "Activity" llama a otra, la actual "Activity" es pausada y podría ser eliminada posteriormente si los recursos disponibles comienzan a escasear.
Si esto llegara a ocurrir, tu "Activity" tendrá la oportunidad de guardar su actual estado para que más tarse cuando sea restaurada puede volver al estado en el cual se encontrada antes de ser eliminada.

Android tiene un ciclo de vida muy bien definido. Los eventos del ciclo de vida ocurren incluso si tú no estás pasando el control a otra "Activity" en forma explícita. Por ejemplo, una llamada podría ser recibida cuando el usuario está operando tu aplicación. Si esto ocurre, tu aplicación quedará en segundo plano mientras otra aplicación se encarga de la llamada.

Volvamos al tutorial y a realizar cambios en la clase "NoteEdit". A continuación, sobreescribiremos los métodos "onFreeze()", "onPause()" y "onResume()". Estos son nuestros métodos del ciclo de vida (incluido "onCreate()").

  • Método onFreeze() : este método es llamado por Android si la "Activity" está siendo detenida y probablemente será eliminada antes de que pueda ser reinicializada. Esto significa que dentro de este método la aplicación debería guardar cualquier estado que sea necesario para reiniciarse en las mismas condiciones que estaba al momento de ser detenida. Este método es la contra parte del método "onCreate(Bundle" y de hecho el argumento que este método recibe es el mismo que tú construyes como "outState" en el método "onFreeze()".
  • Métodos onPause() y onResume() : estos métodos son complementarios. El método "onPause()" es siempre llamado cuando una "Activity" finaliza, incluso si hemos sido nosotros quien lo ha programado. Nosotros usaremos este método para guardar la actual anotación en la base de datos. Una buena práctica es liberar cualquier recursos que sea posible en "onPause()", y de esa manera mantener bajo el nivel de recursos cuando nuestra aplicación esta en un estado pasivo. Por esta razón, vamos a cerrar la conexión con la base de datos y asignaremos el valor null al objeto "DBHelper" (de esa manera facilitamos el trabajo del "Garbage Collector"). En el método "onResume()" recrearemos la instancia de "DBHelper" para poder recuperar la anotación desde la base de datos.
A continuación está el código del método "onFreeze()":

@Override
protected void onFreeze(Bundle outState)
{
super.onFreeze(outState);
outState.putLong(Notepadv3.KEY_ROW_ID, rowId);
}


A continuación está el código del método "onPause()":

@Override
protected void onPause()
{
super.onPause();
saveState();
dbHelper.close();
dbHelper = null;
return;
}


A continuación está el código del método " onResume()":

@Override
protected void onResume()
{
super.onResume();
if (dbHelper == null) {
dbHelper = new DBHelper(this);
}
populateFields();
}


Paso 8

A continuación definiremos el método "saveState()", el cual guarda los datos en la base de datos.

private void saveState()
{
String title = titleText.getText().toString();
String body = bodyText.getText().toString();
if (rowId == null) {
dbHelper.createRow(title, body);
} else {
dbHelper.updateRow(rowId, title, body);
}
}


Paso 9

Ahora saquemos modificaremos el método "onActivityResult()" de la clase "Notepadv3". Toda la lógica de recuperación y actualización está en el ciclo de vida de la clase "NoteEdit", por lo tanto lo único que el método "onActivityResult()" debe realizar es actualizar la vista de los datos. El código del método es mostrado a continuación:

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

Paso 10

En el método "onListItemClick()" de la clase "Notepadv3" también debemos eliminar las líneas que agregar el título y cuerpo de la anotación al paquete de datos.

i.putExtra(KEY_TITLE, rows.get(position).title);
i.putExtra(KEY_BODY, rows.get(position).body);

Paso 11

La aplicación esta lista para ser ejecutada.


No hay comentarios.: