Nesse exercício, vamos usar callbacks de eventos do ciclo de vida para guardar e recuperar dados de estado da aplicação. Esse exercício demonstrará:
- Eventos do ciclo de vida e como sua aplicação pode usá-los.
- Técnicas para manter o estado da aplicação.
Se você não passou pelo exercício 01 e 02, recomendo que o faça. Lá estarão as instruções iniciais de como chegar nessa etapa do projeto Notepad.
Passo 1
Importe o projeto Notepadv3 dentro do Eclipse. Se você encontrar algum problema com o AndroidManifest.xml ou algum outro problema, clique com o botão direito sobre o projeto e selecione Android Tools > Fix Project Properties. O ponto inicial desse exercício é exatamente o ponto onde paramos no exercício 02.
A aplicação atual tem alguns problemas - se você tocar o botão de voltar quando estiver editando uma nota irá causar travamento da aplicação e qualquer outra coisa que você faça durante a edição será perdida.
Para corrigir tal problema, nós vamos mover a maior parte da funcionalidade para criação e edição de notas para dentro da classe NoteEdit e introduzir o ciclo de vida completo para edição de notas.
1) Remova o código em NoteEdit que faz o parse do title e body dos Bundles extras. Ao invés disso, vamos usar a classe DBHelper para acessar as notas do banco de dados diretamente. Tudo que precisaremos é passar para a atividade NoteEdit o mRowId (mas apenas enquanto editando. Se estivermos inserindo nós passamos um valor nulo). Portanto, remova essas linhas:
2) Teremos também de nos livrar das propriedades que foram passadas nos Bundles extras que nós estávamos usando para setar o valores title e body dentro da interface. Então, faça a exclusão também de:
Passo 2
Crie um campo dentro da classe NotesDbAdapter.
Dentro de NoteEdit, nós precisamos checar o savedInstanceState com o mRowId em caso da nota que se está editando conter um estado salvo dentro do Bunble, o qual nós recuperaremos.
1) Substitua o código que inicializa o mRowId:
2) Note a checagem por valores Null para savedInstanceState e que nós ainda precisamos carregar mRowId a partir do Bundle extras e ele não estiver sendo provido pelo savedInstanceState.
3) Note o uso de Bundle.getSerializable() ao invés de Bundle.getLong(). Esse último método retorna um primitivo long e não poderá ser usado para representar no caso do mRowId ser nulo.
Passo 4
Agora, nós precisamos popular os campos baseados na mRowId que passamos:
populateFields();
O código acima deverá ser inserido antes de confirmButton.setOnClickListener(). Vamos criar esse método daqui a pouco.
Passo 5
Se livre da criação do Bundle e configuração de valores de Bundle que se encontra no método onClick(). A atividade não precisa mais retornar quaisquer informações extras para quem o chama. E já que não precisa mais ter um Intent para retornar, nós usaremos uma versão mais curta de setResult():
O método completo de onCreate() deverá se parecer com o código abaixo:
Passo 6
Defina o método populateFields():
Passo 7
Ainda dentro da classe NoteEdit, nós fazemos o override dos métodos onSaveInstanceState(), onPause() e onResume(). Esses são métodos de ciclo de vida (juntamente com o onCreate() que nós já temos).
onSaveInstanceState() é chamado pelo Android se uma atividade está sendo parada e deve ser morta antes de ser resumida. Isso significa que nós devemos guardar quaisquer estados necessários para a sua reinicialização para a mesma condição de quando a atividade é reiniciada. Ela é a contraparte de onCreate() e, de fato, o Bundle savedInstanceState passado para o onCreate() é o mesmo Bundle que você construiu como ouState no método onSaveInstanceState().
onPause() e onResume() são também métodos complementares. onPause() é sempre chamado quando a atividade termina, mesmo que ela seja instigada (com um finish(), por exemplo). Nós usaremos isso para salvar a nota atual para o banco de dados. A boa prática é liberar quaisquer recursos que possam ser liberados durando o onPause() também, para que menos recursos sejam usados quando estivermos em estado passivo. onResume() vai chamar o nosso método populateFields() para ler a nota do banco de dados novamente e popular os campos.
Então, adicione algum espaço após o método populateFields() e adicione o seguinte método de ciclo de vida:
a) onSaveInstanceState():
b) onPause():
c) onResume():
Note que saveState() é chamado em onSaveInstanceState() e onPause() para certificarmos que os dados são salvos. Isso acontece já que não há garantias que onSaveInstanceState() será chamado e porque quando é chamado ele é chamado antes de onPause().
Passo 8
Defina o método saveState():
Passo 9
Agora insira o seguinte código para onActivityResult():
Remova as linhas que setam o title e body de onListItemClick().
e também remova:
para que o que deve sobrar no método é apenas o código abaixo:
Você também pode remover o campo mNotesCursor da classe e voltar a usar uma variável local no método fillData().
Pronto, basta rodar o aplicativo para ver como ficou.
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
2) Teremos também de nos livrar das propriedades que foram passadas nos Bundles extras que nós estávamos usando para setar o valores title e body dentro da interface. Então, faça a exclusão também de:
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
Passo 2
Crie um campo dentro da classe NotesDbAdapter.
private NotesDbAdapter mDbHelper;Também adicione uma instância de NotesDbAdapter no método onCreate() abaixo da chamada para super.onCreate():
mDbHelper = new otesDbAdapter(this);Passo 3
mDbHelper.open();
Dentro de NoteEdit, nós precisamos checar o savedInstanceState com o mRowId em caso da nota que se está editando conter um estado salvo dentro do Bunble, o qual nós recuperaremos.
1) Substitua o código que inicializa o mRowId:
mRowId = null;
Bundle extras = getIntent().getExtras();Por esse:
if (extras != null) {
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
}
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) Note a checagem por valores Null para savedInstanceState e que nós ainda precisamos carregar mRowId a partir do Bundle extras e ele não estiver sendo provido pelo savedInstanceState.
3) Note o uso de Bundle.getSerializable() ao invés de Bundle.getLong(). Esse último método retorna um primitivo long e não poderá ser usado para representar no caso do mRowId ser nulo.
Passo 4
Agora, nós precisamos popular os campos baseados na mRowId que passamos:
populateFields();
O código acima deverá ser inserido antes de confirmButton.setOnClickListener(). Vamos criar esse método daqui a pouco.
Passo 5
Se livre da criação do Bundle e configuração de valores de Bundle que se encontra no método onClick(). A atividade não precisa mais retornar quaisquer informações extras para quem o chama. E já que não precisa mais ter um Intent para retornar, nós usaremos uma versão mais curta de setResult():
public void onClick(View view) {Agora tomaremos conta do processo de guardar as atualizações e novas notas no banco de dados nós mesmos, usando os métodos de ciclo de vida.
setResult(RESULT_OK);
finish();
}
O método completo de onCreate() deverá se parecer com o código abaixo:
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
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;
}
populateFields();
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
});
Passo 6
Defina o método populateFields():
private void populateFields() {Esse método usa o método NotesDbAdapter.fetchNote() para encontrar a nota correta a ser editada e então chama startManagingCursor() a partir da classe Activity, que é um método conveniente do Android provido para tomar conta do ciclo de vida de um Cursor. Ele vai liberar e recriar recursos como ditado pelo ciclo de vida da Atividade, para que não precisemos nos preocupar em fazer isso nós mesmos. Após isso, nós apenas procuramos o title e body a partir do Cursor e populamos os elementos da View com eles.
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)));
}
}
Passo 7
Ainda dentro da classe NoteEdit, nós fazemos o override dos métodos onSaveInstanceState(), onPause() e onResume(). Esses são métodos de ciclo de vida (juntamente com o onCreate() que nós já temos).
onSaveInstanceState() é chamado pelo Android se uma atividade está sendo parada e deve ser morta antes de ser resumida. Isso significa que nós devemos guardar quaisquer estados necessários para a sua reinicialização para a mesma condição de quando a atividade é reiniciada. Ela é a contraparte de onCreate() e, de fato, o Bundle savedInstanceState passado para o onCreate() é o mesmo Bundle que você construiu como ouState no método onSaveInstanceState().
onPause() e onResume() são também métodos complementares. onPause() é sempre chamado quando a atividade termina, mesmo que ela seja instigada (com um finish(), por exemplo). Nós usaremos isso para salvar a nota atual para o banco de dados. A boa prática é liberar quaisquer recursos que possam ser liberados durando o onPause() também, para que menos recursos sejam usados quando estivermos em estado passivo. onResume() vai chamar o nosso método populateFields() para ler a nota do banco de dados novamente e popular os campos.
Então, adicione algum espaço após o método populateFields() e adicione o seguinte método de ciclo de vida:
a) onSaveInstanceState():
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveState();
outState.putSerializable(NotesDbAdapter.KEY_ROWID, mRowId);
}
b) onPause():
@Override
protected void onPause() {
super.onPause();
saveState();
}
c) onResume():
@Override
protected void onResume() {
super.onResume();
populateFields();
}
Note que saveState() é chamado em onSaveInstanceState() e onPause() para certificarmos que os dados são salvos. Isso acontece já que não há garantias que onSaveInstanceState() será chamado e porque quando é chamado ele é chamado antes de onPause().
Passo 8
Defina o método saveState():
private void saveState() {Note que nós capturamos o valor de retorno de createNote() e se um ID válido de linha é retornado, nós guardamos esse valor em mRowId para que nós possamos fazer o update dessa nota no futuro.
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);
}
}
Passo 9
Agora insira o seguinte código para onActivityResult():
Passo 10@Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); fillData(); }
Remova as linhas que setam o title e body de onListItemClick().
Cursor c = mNotesCursor; c.moveToPosition(position)
e também remova:
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
para que o que deve sobrar no método é apenas o código abaixo:
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(i, ACTIVITY_EDIT);
Você também pode remover o campo mNotesCursor da classe e voltar a usar uma variável local no método fillData().
Cursor notesCursor = mDbHelper.fetchAllNotes();
Pronto, basta rodar o aplicativo para ver como ficou.