- 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.
- Primero eliminaremos el código de
NoteEdit
que parsea el título y el texto del Bundle extras. Vamos a utilizar la claseDBHelper
para acceder a las notas directamente desde la base de datos. Todo lo que necesitamos pasar a la actividad de NoteEdit es unmRowId
(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); - 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
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). - 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;
} - Fíjate en la comprobación de valor null de
savedInstanceState
. Necesitamos cargarmRowId
del Bundleextras
si no lo proporcionasavedInstanceState
. Se usa un operador ternario para utilizar de manera segura el valor, o null si no está presente. - Fíjate en el uso de
Bundle.getSerializable()
en vez deBundle.getLong()
. Como devuelve unlong
no se puede usar para representar el caso cuandomRowId
esnull
.
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()
:
onSaveInstanceState()
:@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveState();
outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId);
}En el próximo paso definiremos
saveState()
.onPause()
:@Override
protected void onPause() {
super.onPause();
saveState();
}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
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);
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
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:
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