martes, 3 de agosto de 2010

TUTORIAL: Desarrollo de aplicaciones para Android (XVI)


Seguimos con este mini-curso de desarrollo de aplicaciones para Android. En el anterior ejercicio vimos la segunda parte del tutorial de creación de un bloc de notas, que ya permitía crear, editar y eliminar notas, pero que aún tiene algunos errores que hay que solucionar.


Ejercicio 3

En este ejercicio utilizaremos los valores devueltos por los eventos del ciclo de vida de las actividades para almacenar y recuperar datos. En concreto veremos:

- Los eventos del ciclo de vida y cómo nuestra aplicación los puede utilizar
- Técnicas para mantener el estado de la aplicación


Paso 1

Importa el proyecto Notepadv3 a Eclipse. Empezamos justo dónde dejamos el anterior ejercicio.

La aplicación, tal y como está ahora, tiene algunos problemas. Por ejemplo, si presionamos el botón de retroceso cuando estamos editando una nota, ocurre un error y se cierra el programa, y todo lo que habíamos escrito en la nota se pierde.

Para solucionar esto, vamos a mover la mayoría de las funciones de creación y edición de notas a la clase NoteEdit. Además añadiremos un ciclo de vida completo para la edición de notas.


  1. Primero eliminaremos el código de NoteEdit que parsea el título y el texto del Bundle extras. Vamos a utilizar la clase DBHelper para acceder a las notas directamente desde la base de datos. Todo lo que necesitamos pasar a la actividad de NoteEdit es un mRowId (pero sólo si estamos editando, si creamos no pasamos nada). Elimina las siguientes líneas:
    String title = extras.getString(NotesDbAdapter.KEY_TITLE);
    String body = extras.getString(NotesDbAdapter.KEY_BODY);
  2. Nos vamos a librar también de las propiedades que se están pasando en el Bundle extras, que utilizábamos para establecer el título y el texto en la interfaz de usuario. Por tanto, elimina esto:
    if (title != null) {
    mTitleText
    .setText(title);
    }
    if (body != null) {
    mBodyText
    .setText(body);
    }


Paso 2


Crea un atributo NotesDbAdapter de clase en la parte superior de la clase NoteEdit:
    private NotesDbAdapter mDbHelper;

Luego añade una instancia del NotesDbAdapter en el método onCreate() (justo debajo de super.onCreate()):

mDbHelper = new NotesDbAdapter(this);
mDbHelper
.open();


Paso 3

En NoteEdit, necesitamos comprobar el savedInstanceState para mRowId, para que si la nota que se está editando contiene un estado guardado en el Bundle, la podamos recuperar (esto podría ocurrir si nuestra actividad pierde el "focus" y luego se reinicia).
  1. Reemplaza el código que ahora inicializa mRowId ... :
           mRowId = null;

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
    mRowId
    = extras.getLong(NotesDbAdapter.KEY_ROWID);
    }
    ... por este:
           mRowId = (savedInstanceState == null) ? null :
    (Long) savedInstanceState.getSerializable(NotesDbAdapter.KEY_ROWID);
    if (mRowId == null) {
    Bundle extras = getIntent().getExtras();
    mRowId
    = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
    : null;
    }
  2. Fíjate en la comprobación de valor null de savedInstanceState. Necesitamos cargar mRowId del Bundle extras si no lo proporciona savedInstanceState. Se usa un operador ternario para utilizar de manera segura el valor, o null si no está presente.
  3. Fíjate en el uso de Bundle.getSerializable() en vez de Bundle.getLong(). Como devuelve un long no se puede usar para representar el caso cuando mRowId es null.


Paso 4

Ahora necesitamos "poblar" (rellenar) los atributos basados en mRowId en caso de tenerlos:

populateFields();

Esto va antes de la línea confirmButton.setOnClickListener(). Definiremos el método más tarde.



Paso 5

Vamos ahora a librarnos de la creación del Bundle. La actividad ya no necesita devolver ninguna información extra al invocador. Y como que no tendremos ningún Intent que devolver, usaremos la versión más corta del método setResult(), por lo que el método onClick() debe quedar así:

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

Nos preocuparemos de almacenar las modificaciones y nuevas notas en la base de datos nosotros mismos, mediante el uso de métodos de ciclo de vida.

El método onCreate() ahora debería ser así (clic en la imagen para ampliar):



Paso 6

Ahora define el método populateFields():
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor
(note);
mTitleText
.setText(note.getString(
note
.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText
.setText(note.getString(
note
.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}

Este método utiliza el método NotesDbAdapter.fetchNote() para encontrar la nota a editar, luego llama a startManagingCursor() desde la clase Activity (se trata de un método de conveniencia proporcionado para ocuparse del ciclo de vida del Cursor). Esto liberará y volverá a crear recursos siguiendo las pautas del ciclo de vida de una actividad, por lo que no tendremos que preocuparnos de hacerlo nosotros. Después de eso, simplemente se buscan los valores del título y el texto del Cursor y se rellenan los elementos de la vista con ellos.



Paso 7

Siguiendo con la clase NoteEdit, vamos a sobrecargar ahora los métodos onSaveInstanceState(), onPause() y onResume(). Estos son nuestros métodos de ciclo de vida (junto a onCreate()).

onSaveInstanceState() es invocado por Android si la actividad de está deteniendo (puede finalizar antes de ser recuperada). Esto significa que se debería almacenar el estado necesario para dejar la actividad en el mismo punto en que estaba cuando se reinicie. Es la contrapartida del método onCreate(), y de hecho el Bundle savedInstanceState pasado a onCreate() es el mismo Bundle que creas como outState en el método onSaveInstanceState().

onPause() y onResume() son métodos complementarios. onPause() es invocado cuando la actividad finaliza, aunque nosotros lo provoquemos (con una llamada a finish() por ejemplo). Lo utilizaremos para guardar el estado actual de la nota en la base de datos. Es recomendable liberar los recursos que se puedan, para no llevarse demasiados al estado pasivo. onResume() invocará el método populateFields() para consultar la nota en la base de datos y así rellenar los atributos.

Añade pues los siguientes métodos de ciclo de vida después del método populateFields():

  1. onSaveInstanceState():
        @Override
    protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    saveState
    ();
    outState
    .putSerializable(NotesDbAdapter.KEY_ROWID, mRowId);
    }

    En el próximo paso definiremos saveState().

  2. onPause():
        @Override
    protected void onPause() {
    super.onPause();
    saveState
    ();
    }
  3. onResume():
        @Override
    protected void onResume() {
    super.onResume();
    populateFields
    ();
    }

Fíjate que saveState() se debe invocar tanto en onSaveInstanceState() como en onPause() para asegurarnos de que los datos se guardan. Esto es así porque no tenemos ninguna garantía de que se vaya a invocar onSaveInstanceState() y porque cuando se invoca, se invoca antes de onPause().



Paso 8

Define el método saveState() para guardar los datos en la base de datos:
     private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();

if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId
= id;
}
} else {
mDbHelper
.updateNote(mRowId, title, body);
}
}

Fíjate que capturamos el valor de retorno de createNote(), y que si contiene un ID de fila válido (mayor de cero), lo almacenamos en el atributo mRowId para que en el futuro podamos actualizar la nota en vez de tener que crear otra nueva.



Paso 9

Ahora abre la clase Notepadv3.

Todo lo relacionado con las notas ocurre ahora dentro del ciclo de vida de la clase NoteEdit, por lo que lo único que necesita el método onActivityResult() es actualizar su vista de los datos. El método resultante debería ser así:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData
();
}

Como ahora es la otra clase la que hace el trabajo, lo único que hay que hacer es actualizar los datos.


Paso 10

En la misma clase, elimina las líneas que establecen el título y texto en el método onListItemClick():

    Cursor c = mNotesCursor;
c
.moveToPosition(position);

Elimina también:
    i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c
.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i
.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c
.getColumnIndex(NotesDbAdapter.KEY_BODY)));

Lo único que debería quedar en este método es lo siguiente:
    super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i
.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult
(i, ACTIVITY_EDIT);

También puedes eliminar el atributo mNotesCursor de la clase, y declararlo de nuevo como variable local en el método fillData():

    Cursor notesCursor = mDbHelper.fetchAllNotes();

Fíjate que la m de mNotesCursor denota a un atributo "member", por lo que cuando declaremos notesCursor como variable local, eliminaremos la m. Acuérdate de renombrar las demás ocurrencias de mNotesCursor que haya en el método fillData().


Paso 11 (opcional)

Si quieres tener los textos en español, sustituye el texto de res/layout/values/strings.xml por este otro (clic para ampliar la imagen):


Ejecuta la aplicación y prueba que todo funcione correctamente.

¡Enhorabuena! Ya has creado tu primera aplicación seria para Android.



Solución

Puedes ver la solución a este ejercicio en el proyecto Notepadv3Solution para compararla con la tuya.

En el siguiente post, seguiremos con un ejercicio extra opcional, dónde veremos como ocurren los eventos del ciclo de vida gracias al debugger de Eclipse.

1 comentario:

Alejandra dijo...

muy bueno tu tutorial! pero tengo una pregunta, las notas que haces con ésta aplicación ¿las puedes visualizar en una computadora? Gracias!

Publicar un comentario