Caso prefira, você encontrará todo esse material, em inglês, no site do Developer Android. A tradução e comentários dos materiais eu faço livremente para ajudar a comunidade que fala português.

quinta-feira, 28 de abril de 2011

0 comentários

Notepad - Exercício 03

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:

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);
mDbHelper.open();
Passo 3

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();
        if (extras != null) {
            mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
        }
Por esse:


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) {
    setResult(RESULT_OK);
    finish();
}
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.

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() {
    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)));
    }
}
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.

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() {
        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);
        }
    }
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.

Passo 9




Agora insira o seguinte código para onActivityResult():

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

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.

quarta-feira, 27 de abril de 2011

0 comentários

Notepad - Exercício 02

Nesse exercício vamos adicionar uma segunda atividade à aplicação de notepad desenvolvida no post anterior. Se você não passou pelo post anterior, recomendo que o faça para que possa entender o que vai acontecendo hoje aqui. Essa nova atividade permitirá ao usuário criar e editar notas. Também permitirá ao usuário excluir notas através de um menu de contexto. A nova atividade assumirá a responsabilidade de criar novas notas coletando os dados de entrada e empacotando-os em um Bundle que será retornado por um intent. O exercício demonstrará:

  • Como construir uma nova atividade e adicioná-la ao manifesto do Android;
  • Fazer a chamada de uma outra atividade de modo assincrono usando startActivityForResult();
  • Passar dados entre atividades dentro de objetos Bundle;
  • Como fazer uso de layouts de tela mais avançados e;
  • Como criar um menu de contexto.
Passo 1

Crie um novo projeto Android usando os códigos fontes de Notepadv2 dentro da pasta NotepadCodeLab que você baixou no post anterior. Se você tiver algum erro no AndroidManifest.xml ou algum outro problema, selecione Android Tools > Fix Project Properties no Eclipse para tentar corrigí-lo. Isso deverá resolver o problema.

Abrindo o projeto Notepadv2 você poderá perceber o seguinte:
  • Quando abrir o arquivo string.xml dentro de res/values perceberá inúmeros novos valores string que serão usados em nossas novas funcionalidades.
  • Também, abrindo a classe Notepadv2, você notará muitas novas constantes que foram definidas com o novo campo mNotesCursor usado para guardar o cursor que estamos usando.
  • Note também que o método fillData() tem mais comentários e agora usa novos campos para guardar as o Cursor de notas. O método onCreate() está do mesmo jeito que no primeiro exercício. Também note que o campo membro usado para guardar o Cursor de notas agora é chamado de mNotesCursor. O m à frente do campo denota que ele é um campo membro e é parte do padrão de codificação do Android.
  • Existem ainda outros métodos que foram sobrescritos (onCreateContextMenu(), onContextItemSelected(), onListItemClick() e onActivityResult()).
Passo 2

Primeiro, vamos criar o menu de contexto que permitirá aos usuários excluir notas individuais. Abra a classe Notepadv2.
  1. Para que cada cada item da lista na ListView se registre para o menu de contexto, nós chamamos registerForContextMenu() e passamos para a ListView. Então, ao final do método onCreate(), adicionamos essa linha:

    registerForContextMenu(getListView());

    Como nossa atividade extende a classe de ListActivity, getListView() retornará o objeto ListView local para a atividade. Agora, cada item na ListView vai ativar o menu de contexto.
  2. Agora vamos preencher o método onCreateContextMenu(). Aqui, adicionamos apenas uma linha, que adicionará um novo item de menu para excluir a nota. Chame menu.add() como mostrado abaixo:

    public void onCreateContextMenu(Menu menu, View v,
                         ContextMenu.ContextMenuInfo menuInfo) {
                   super.onCreateContextMenu(menu, v, menuInfo);
                   menu.add(0, DELETE_ID, 0, R.string.menu_delete);
    }


    O callback onCreateContextMenu() passa outras informações para o objeto de menu, como a View que está chamando o menu e um objeto extra que pode conter informações adicionais sobre o objeto selecionado. Contudo, nós não nos importaremos com isso agora, já que apenas um tipo de objeto na atividade usa o menu de contexto.
Passo 3

Agora que você já registrou nosso ListView para o menu de contexto e definiu nossos itens de menu de contexto, precisamos processar o callback quando selecionado. Para isso, precisamos identificar o ID de lista do item selecionado e então excluí-lo. Então, preencha o método onContextItemSelected() como mostrado abaixo:
public boolean onContextItemSelected(MenuItem item) {
    switch(item.getItemId()) {
    case DELETE_ID:
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        mDbHelper.deleteNote(info.id);
        fillData();
        return true;
    }
    return super.onContextItemSelected(item);
}

Aqui, nós retornamos o AdapterContextMenuInfo com o getMenuInfo(). O campo id do objeto mostra a posição do item no ListView. Nós então passamos ele ao método deleteNote() de nosso NotesDbAdapter e a nota será excluída. Notas agora podem ser excluídas.

Passo 4

Preencha o corpo do método createNote().

Crie um novo Intent para criar uma nota (ACTIVITY_CREATE) usando a classe NoteEdit. Então chame o Intent usando o método startActivityForResult():
Intent i = new Intent(this, NoteEdit.class);
startActivityForResult(i, ACTIVITY_CREATE);
Essa forma de chamada do Intent tem como alvo uma classe específica de nossa atividade, a NoteEdit. Já que a classe Intent precisará comunicar-se com o sistema Android para rotear requisições, nós também temos de prover um Context(this)

O método startActivityForResult() chama o Intent de uma maneira que causa a chamada de um método em nossa atividade quando a nova atividade é completada. O método em nossa atividade que recebe o callback é chamado de onActivityResult() e nós vamos implementá-lo em um passo posterior. A outra maneira de chamar uma atividade é usando startActivity() mas essa é uma chamada do tipo 'chame-e-esqueça' - dessa maneira nossa atividade não é informada quando a atividade foi completada e não existe nenhuma maneira de retornar a informação de resultado da atividade chamada com o startActivity().

Não se preocupe que a atividade NoteEdit não exista ainda. Mas à frente vamos criá-la.

Passo 5

Preencha o corpo do método onListItemClick().

Esse método é chamado quando o usuário seleciona um item da lista. É passado a ele quatro parâmetros: o objeto ListView que foi chamado, a View dentro da ListView que foi clicada, a posição na lista na qual foi clicada e a mRowId do item que foi clicado. Nessa instância podemos ignorar os dois primeiros parâmetros (nós temos apenas um único ListView que pode ser usado) e nós ignoramos a mRowId também. Todo que nos interessa é a posição que o usuário selecionou. Nós usando esse dado para buscar os dados da linha correta e inserí-lo num Bundle para ser enviado à atividade NoteEdit.

Em nossa implementação do callback, o método cria um Intent para editar a nota usando a classe NoteEdit. Ele então adiciona dados em Bundles extras do Intent que vamos passar para a atividade chamada. Nós o usamos para passar o título e texto do corpo e a mRowId para a nota que nós estamos editando. Finalmente, chamamos o intent usando startActivityForResult(). Aqui está o código que pertence ao onListItemClick():

super.onListItemClick(l, v, position, id);
Cursor c = mNotesCursor;

c.moveToPosition(position);
Intent i = new Intent(this, NoteEdit.class);

i.putExtra(NotesDbAdapter.KEY_ROWID, id);
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
        c.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
        c.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
startActivityForResult(i, ACTIVITY_EDIT);
  • putExtra() é o método que adiciona itens aos Bundles extras para passá-los em chamadas de Intent. Aqui nós estamos usando um Bundle para passar o título, corpo e mRowId da nota que nós podemos editar.
  • Os detalhes da nota são capturados a partir de uma pesquisa no Cursor que nós movemos para uma posição apropriada para o elemento que foi selecionado na lista com o método moveToPosition().
  • Com os extras adicionados ao Intent, nós chamamos o Intent na classe NoteEdit passando ao startActivityForResult() o Intent e o código de requisição (esse código será retornado para o onActivityResult como parâmetro requestCode).
Nota: Nós assinalamos o mNotesCursor para uma variável local no início do método. Isso é feito assim como uma otimização do código Android. Acessar variáveis locals é muito mais eficiente que acessar um campo na máquina virtual Dalvik. Então, fazendo isso, temos apenas um acesso ao campo na máquina virtual  Dalvik e cinco acessos à variável local, fazendo da rotina algo muito mais eficiente. É recomendado que você use esse tipo de otimização quando possível.

Passo 6

Os métodos createNote() e onListItemClick() usam uma chamada de Intent assincrona. Nós precisamos de um handler para o callback. Por isso, aqui preencheremos o corpo de onActivityResult().

onActivityResult(0 é um método sobrescrito que será chamado quando a atividade retornar com um resultado (lembre-se, uma atividade vai apenas retornar se lançada com startActivityForResult()). Os parâmetros providos ao callback são:
  • requestCode - o código original de requisição para uma invocação de Intent (ou ACTIVITY_CREATE ou ACTIVITY_EDIT).
  • resultCode - o resultado (ou código de erro) da chamada. Deverá retornar 0 se tudo correu bem ou deverá haver um valor diferente de 0 indicando que algum erro aconteceu. Existem códigos de erro padrão e você poderá criar suas próprias constantes para indicar erros específicos.
  • intent - esse é o Intent criado pela atividade que retorna os resultados. Ele pode ser usado para retornar dados em intents "extras".
A combinação de startActivityForResult() e onActivityResult() pode ser pensada como uma chamada assincrona RPC (remote procedure call) e forma a maneira recomendada para uma atividade chamar outra atividade e compartilhar serviços.

Aqui está o código que deverá estar em onActivityResult():
super.onActivityResult(requestCode, resultCode, intent);
Bundle extras = intent.getExtras();

switch(requestCode) {
case ACTIVITY_CREATE:
    String title = extras.getString(NotesDbAdapter.KEY_TITLE);
    String body = extras.getString(NotesDbAdapter.KEY_BODY);
    mDbHelper.createNote(title, body);
    fillData();
    break;
case ACTIVITY_EDIT:
    Long mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
    if (mRowId != null) {
        String editTitle = extras.getString(NotesDbAdapter.KEY_TITLE);
        String editBody = extras.getString(NotesDbAdapter.KEY_BODY);
        mDbHelper.updateNote(mRowId, editTitle, editBody);
    }
    fillData();
    break;
}
  • Estamos gerenciando ambos resultados de atividade com esse método, tanto o do ACTIVITY_CREATE quanto do ACTIVITY_EDIT.
  • No caso de ser create, nós capturamos o título e o corpo a partir dos extras (retornados de um Intent que foi retornado) e os usamos para criar uma nova nota.
  • No caso de ser edit, nós capturamos o mRowId também e o usamos para fazer o update da nota no banco de dados.
  • fillData() no final certifica que todo o conteúdo mostrado está atualizado.
Passo 7

Abra o arquivo note_edit.xml que está no projeto e dê uma olhada nele. Esse será o código fonte para o Note Editor.
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
   
    <LinearLayout android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/title" />
        <EditText android:id="@+id/title"
          android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>

    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/body" />
    <EditText android:id="@+id/body" android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:scrollbars="vertical" />
   
    <Button android:id="@+id/confirm"
      android:text="@string/confirm"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>
Existe um novo parâmetro usado aqui que nós ainda não vimos anteriormente: android:layout_weight. Ele está setado para o valor 1 em casa caso em que o usamos acima.

layout_weight é usado em layouts linears (LinearLayout) para assinalar importância da view dentro do layout. Isso foi mais explicado no link sobre Interface de Usuários - Declarando o Layout. Basta procurar pela seção LinearLayout.

Passo 8

Crie a classe NoteEdit que extende android.app.Activity.

Aqui é a primeira vez que vamos criar uma atividade sem que o plugin Android do Eclipse faça isso por nós. Quando você o faz, o método onCreate() não é automaticamente sobrescrito para você. É difícil imaginar uma atividade que não tenha o método onCreate() sobrescrito, então essa deverá ser a primeira coisa a se fazer.
  1. Clique com o botão direito no pacote com.android.demo.notepad2 dentro de Package Explorer e selecione New > Class a partir do menu popup.
  2. Preencha NoteEdit para o campo Name.
  3. No campo Superclass digite android.app.Activity.
  4. Clique em Finish.
  5. Na classe NoteEdit resultante, clique com o botão direito sobre a janela de editor e selecione Source > Override/Implement Methods...
  6. Role através da checklist na janela de diálogo até que você veja onCreate(Bundle) e marque o box ao lado dele.
  7. Clique em OK.
O método deverá aparecer em sua classe.

Passo 9

Preencha o corpo do método onCreate() para o NoteEdit.

Aqui é onde vamos setar o título de nossa nova atividade para, digamos, "Editar Nota" (ou qualquer valor que exista em strings.xml para essa opção). Vamos também setar o content view para usar nosso arquivo de layout note_edit.xml. Nós também buscaremos handles para o título e corpo e o botão de confirmação para que nossa classe possa usá-los para capturar e modificar títulos de nota e corpo e finalmente anexá-los ao evento de confirmação quando o usuário pressionar o botão correto.

Então nós vamos da separar os valores que foram passados para a Atividade com os Bundles extras anexados ao intent chamado. Vamos usá-los para pre-popular o título e texto do corpo da nota para que os usuários possam editá-los. Então vamos buscar e guardar o mRowId para que possamos saber que nota o usuário está editando.
  1. Dentro de onCreate(), vamos dizer qual layout usar:

    setContentView(R.layout.note_edit);
  2. Mudamos o título da atividade para o valor da string edit_note que se encontra em strings.xml

    setTitle(R.string.edit_note);
  3. Encontramos os componentes EditText e Button que precisamos:

    mTitleText = (EditText) findViewById(R.id.title);
    mBodyText = (EditText) findViewById(R.id.body);
    Button confirmButton = (Button) findViewbyId(R.id.confirm);

    Note que mTitleText e mBodyText são campos membros (eles devem ser declarados no topo da definição de classe).
  4. No topo da classe, declare um campo Long mRowId privado para guardar o mRowId atual que está sendo editado.
  5. Continuando dentro de onCreate(), adicione o código para inicializar o title, body e mRowId a partir dos Bundles extras do Intent (se ele estiver presente):

    mRowId = null;
    Bundle extras = getIntent().getExtras();
    if (extras != null) {
       String title = extras.getString(NotesDbAdapter.KEY_TITLE);
       String body = extras.getString(NotesDbAdapter.KEY_BODY);
       mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);

       if (title != null) {
          mTitleText.setText(title);
       }
       if (body != null) {
          mBodyText.setText(body);
       }
    }

    • Nós estamos capturando title e body a partir dos Bundles extras que foram inviados pela chamada do Intent.
    • Estamos também protegendo os campos texto de receber valores nulos.
     
  6. Criar um onClickListener para o botão:

    Listeners podem ser um dos mais confusos aspectos da implementação da interface, mas nós estamos tentando chegar a esse objetivo de maneira simples. Queremos que o método onClick seja passado quando o usuário pressionar o botão de confirmação e usá-lo para realizar algum trabalho e retornar os valores da nota editada para quem chama o Intent. Fazemos isso usando uma coisa chamada anonymous inner class. É um pouco confuso de se ver a não ser que já tenhamos tido contado antes, mas se você não quer usar isso, você terá de verificar no futuro um código Java para como criar um Listener e anexá-lo a um botão. Aqui vai o listener vazio:

    confirmButton.setOnClickListener(new View.OnClickListener() {
        public void onClick(View view) {
        }
    });

Passo 10

Preencha o corpo do método onClick() do OnClickListener criado no último passo.

Esse é o código que deverá rodar quando o usuário clica no botão de confirmação. Nós queremos que ele pegue o título e texto do corpo a partir dos campos da tela e colocá-los em um Bundle para retorná-lo para a atividade que invoda o NoteEdit. Se a operação é uma edição ao invés de criação, nós também vamos querer colocar o mRowId no Bundle para que a classe Notepadv2 possa salvar as mudanças de volta na nota correta.

1) Crie um Bundle e coloque o título e texto do corpo usando as constantes definidas em Notepadv2 como chaves:
Bundle bundle = new Bundle();

bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
if (mRowId != null) {
    bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}

2) Configure a informação de resultado (o Bundle em si) como um novo Intent e finalize a atividade.
Intent mIntent = new Intent();
mIntent.putExtras(bundle);
setResult(RESULT_OK, mIntent);
finish();

    • O intent é simplesmente um compartimento carregando nossos Bundles (com o título, corpo e mRowId).
    • O método setResult() é usado para setar o código de resultad e retornar  o Intent para ser passado de volta para o Intent que fez a chamada. No caso de tudo correr bem, retornamos RESULT_OK como código de resultado.
    • A chamada a finish() é usada para assinalar que a Atividade está pronta (como uma chamada de retorno). Qualquer coisa no resultado será retornado a quem chamou o Intent juntamente com o controle da execução.
O código completo de onCreate() (junto com as campos de classe de suporte a ela) deve se parecer como abaixo:
private EditText mTitleText;
private EditText mBodyText;
private Long mRowId;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    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 = null;
    Bundle extras = getIntent().getExtras();
    if (extras != null) {
        String title = extras.getString(NotesDbAdapter.KEY_TITLE);
        String body = extras.getString(NotesDbAdapter.KEY_BODY);
        mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);

        if (title != null) {
            mTitleText.setText(title);
        }
        if (body != null) {
            mBodyText.setText(body);
        }
    }

    confirmButton.setOnClickListener(new View.OnClickListener() {

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

            bundle.putString(NotesDbAdapter.KEY_TITLE, mTitleText.getText().toString());
            bundle.putString(NotesDbAdapter.KEY_BODY, mBodyText.getText().toString());
            if (mRowId != null) {
                bundle.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
            }

            Intent mIntent = new Intent();
            mIntent.putExtras(bundle);
            setResult(RESULT_OK, mIntent);
            finish();
        }
    });
}


Passo 11

Finalmente, uma nova atividade tem de ser definida no arquivo de manifesto.

Antes da nova atividade ser vista pelo Android, ela precisa ter sua entrada própria no arquivo AndroidManifest.xml. Isso é para fazer com que o sistema saiba especificar quals IntentFilters a atividade implementa aqui mas nós vamos deixar isso por agora e apenas fazer com que o Android saiba que a atividade foi definida.

Existe um editor para o Manifesto incluído no Eclipse que faz a edição deste algo muito mais simples.
  1. Abra o arquivo AndroidManifest.xml clicando duas vezes sobre ele.
  2. Clique a aba Application no rodapé do editor do manifesto.
  3. Clique em Add... na seção de Application Nodes. Caso você veja um diálogo com radioButtons no topo, selecione o radioButton do topo em que está escrito "Create a new element at the top level, in Application".
  4. Tenha a certeza que "(A) Activity" está selecionada no painel de seleção do diálogo e clique em OK.
  5. Clique no novo nó de Atividade, dentro da seção Application Nodes e então escreva .NoteEdit no campo Name. Pressione Enter.
Passo 12

Agora, é só rodar e correr para o abraço. Se tudo correr bem, uma tela como a de baixo deverá aparecer no seu emulador/smartphone.



segunda-feira, 25 de abril de 2011

2 comentários

Notepad - Exercício 01

Projeto Completo: Endereço para baixá-lo.

Nesse exemplo você vai construir uma lista simples de notas que permite ao usuário adicionar novas notas mas não editá-las. O exercício demonstrará:
  • A parte básica sobre ListActivities e criação e gerenciamento de opções de menu;
  • Como usar o banco de dados SQLite para guardar as notas;
  • Como fazer o bind dos dados de um cursor de banco de dados para uma ListView usando um SimpleCursosAdapter e;
  • A parte básica de layout de tela incluindo como inserir uma List View, como adicionar itens para o menu de atividade e como essas atividades gerenciam as seleções do menu.
Passo 1

Para que você possa rodar o notepad e, consequentemente, seguir esse exercício, você tem de baixar o projeto completo do Notepad cujo link está apontado no início do post. Após isso, descompacte o arquivo. Você verá dentro da pasta descompactada diversas pastas. A pasta que nos interessa no momento é a cujo nome é Notepadv1. A fim de abrí-lo no Eclipse, você deverá fazer o seguinte:
  1. Abra o Eclipse
  2. Inicie um novo projeto Eclipse clicando em File > New > Android Project
  3. Dentro da janela que se abrirá, selecione Create project from existing source.
  4. Clique em Browse e navegue até a pasta descompactada do projeto completo e selecione a pasta Notepadv1.
  5. O nome do projeto e outras propriedades devem ser automaticamente preenchidas para você. Você deve apenas selecionar o Build Target - nós recomendamos que você escolha a versão mais baixa. Também adicione um inteiro para o campo Min SDK Version que seja o mesmo da API Level que você selecionou em Build Target.
  6. Clique em Finish. O projeto Notepadv1 deverá ficar disponível na área de Package Explorer do Eclipse.
Caso você veja erros a respeito do AndroidManifest.xml ou outros problemas quaisquer, clique com o botão direito sobre o projeto e selecione Android Tools > Fix Project Properties. Isso deve resolver o problema.

Passo 2

Vamos dar uma analizada na classe NotesDbAdapter (ela deverá ser encontrada em src/com.android.demo.notepad1/):
/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.demo.notepad1;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

/**
 * Simple notes database access helper class. Defines the basic CRUD operations
 * for the notepad example, and gives the ability to list all notes as well as
 * retrieve or modify a specific note.
 *
 * This has been improved from the first version of this tutorial through the
 * addition of better error handling and also using returning a Cursor instead
 * of using a collection of inner classes (which is less scalable and not
 * recommended).
 */
public class NotesDbAdapter {

    /* Variáveis */
    public static final String KEY_TITLE = "title";
    public static final String KEY_BODY = "body";
    public static final String KEY_ROWID = "_id";

    private static final String TAG = "NotesDbAdapter";
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    /**
     * Database creation sql statement
     */
    private static final String DATABASE_CREATE =
        "create table notes (_id integer primary key autoincrement, "
        + "title text not null, body text not null);";

    private static final String DATABASE_NAME = "data";
    private static final String DATABASE_TABLE = "notes";
    private static final int DATABASE_VERSION = 2;

    private final Context mCtx;

    private static class DatabaseHelper extends SQLiteOpenHelper {

        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {

            db.execSQL(DATABASE_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Fazendo o Update do banco de dados da versão " + oldVersion +
                   
" para " + newVersion + ", ação essa que destruirá os dados antigos");
            db.execSQL("DROP TABLE IF EXISTS notes");
            onCreate(db);
        }
    }

    /**
     * Construtor - recebe o contexto para permitir que o banco de dados seja

     *
criado/aberto
     * @param ctx O contexto no qual trabalhar
     */
    public NotesDbAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    /**
     * Abre o banco de dados das notas. Se não pode ser aberto, cria o banco.
     * se não pode ser criado, emita uma exceção com sinal de falha
     *
     * @return this (Referencia a si mesmo, permitindo que seja canalizado para
     *         uma chamada inicial)
     * @throws SQLException se o banco de dados não pode ser aberto ou criado
     */
    public NotesDbAdapter open() throws SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() {
        mDbHelper.close();
    }


    /**
     * Cria uma nova nota usando o título e corpo provido. A nota criada com sucesso
     * retorna um novo rowId. Caso contrário, retorna -1 como sinal de falha
     *
     * @param title O título da nota
     * @param body O corpo da nota
     * @return Retorna rowId ou -1 se houve falha
     */
    public long createNote(String title, String body) {
        ContentValues initialValues = new ContentValues();
        initialValues.put(KEY_TITLE, title);
        initialValues.put(KEY_BODY, body);

        return mDb.insert(DATABASE_TABLE, null, initialValues);
    }

    /**
     * Exclui uma nota com um dado rowId
     *
     * @param rowId ID da nota a ser excluída
     * @return Retorna true se excluído e false caso não tenha sido excluído
     */
    public boolean deleteNote(long rowId) {

        return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
    }

    /**
     * Retorna um Cursor da lista de todas as notas do banco de dados
     *
     * @return Cursor de todas as notas
     */
    public Cursor fetchAllNotes() {

        return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
                KEY_BODY}, null, null, null, null, null);
    }

    /**
     * Retorna um cursor posicionado na nota que bate com o rowId fornecido
     *
     * @param rowId ID da nota a ser recuperada
     * @return Cursor posicionado na nota selecionada, se encontrada
     * @throws SQLException Se a nota não pôde ser encontrada/recuperada
     */
    public Cursor fetchNote(long rowId) throws SQLException {

        Cursor mCursor =

            mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
                    KEY_TITLE, KEY_BODY}, KEY_ROWID + "=" + rowId, null,
                    null, null, null, null);
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        return mCursor;

    }

    /**
     * Faz o Update da nota usando os detalhes providos. A nota a ser atualizada é
     * especificada usando a rowId e é alterada para usar os valores de título e corpo
     * enviados ao método
     *
     * @param rowId Id da nota a ser atualizada
     * @param Valor de título a ser atualizado na nota
     * @param body Valor a ser usado como corpo da nota
     * @return Retorna true se a nota foi atualizada com sucesso e false em caso contrário.
     */
    public boolean updateNote(long rowId, String title, String body) {
        ContentValues args = new ContentValues();
        args.put(KEY_TITLE, title);
        args.put(KEY_BODY, body);

        return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
    }
}
Essa classe é provida para encapsular os acessos de dados ao banco de dados SQLite que vai guardar as notas e nos permitir atualizá-las.

No início da classe existe o comentário da Google e também as definições das constantes que serão usadas na aplicação para pesquisar os dados a partir dos campos apropriados no banco de dados. Existe também uma string de criação de banco de dados que será usado para criar um novo esquema de banco de dados caso ainda não tenha criado.

Nosso banco de dados terá o nome data e terá uma tabela única chamada note que, internamente, têm três campos: _id, title e body. O _id é nomeado com a convenção de underscore no início dele. O _id usualmente tem de ser especificado quando pesquisando ou fazendo o update do banco de dados. Os outros campossão campos de texto que vão guardar os dados.

O construtor de NotesDbAdapter recebe um Context que o permite comunicar-se com aspectos do sistema operacional Android. É muito comum classes necessitarem de manter contato com o sistema Android de alguma maneira. A classe Activity implementa a classe de Context para que usualmente você tenha apenas de passar a palavra this a partir de sua atividade, quando precisar do Contexto.

O método open() chama uma instância de DatabaseHelper que é sua implementação local da classe SQLiteOpenHelper. Ele chama o método getWritableDatabase que manuseia a criação/abertura do banco para nós.

O método close() apenas fecha o banco de dados, liberando recursos relativos a conexão.

createNote() recebe strings para o título e corpo da nova nota e então cria a nota no banco de dados. Assumindo que uma nova nota foi criada com sucesso, o método também retorna o valor _id para a nota recém criada.

deleteNote() recebe um _id relacionado a uma rowId e a exclui do banco de dados.

fetchAllNotes() emite uma pesquisa para retornar um Cursor das notas no banco de dados. A chamada para query() vale a pena ser examinada e entendida. O primeiro argumento passado é o nome da tabela no banco de dados na qual pesquisar (nesse caso a DATABASE_TABLE é "notes"). O próximo argumento é a lista de campos que queremos que seja retornado e nesse caso queremos os campos _id, title e body que então são especificados para uma array de strings. Os argumentos remanecentes são, em ordem: selection, selectionArgs, groupBy, having e order by. Passado null em todos esses últimos argumentos faz que os dados sejam selecionados todos os dados sem necessidades de agrupamento e sem ordenação. Será mantida a ordem na qual as notas foram sendo inseridas no banco de dados.

Nota: Um cursor é retornado ao invés de uma coleção de linhas. Isso permite ao Android usar os recursos mais eficientemente -- ao invés de retornar dados e mais dados direto na memória o cursor vai buscar e retornar os dados à medida que for sendo necessário, o que é muito mais eficiente para tabelas com muitas linhas.

fetchNote() é similar a fetchAllNotes() mas ele apenas busca uma nota com uma rowId que especificamos. Ele usa uma versão diferente do método query() da classe SQLiteDatabase. O primeiro argumento (indicado como true) indica se nós estamos interessados em um resultado distinto. O argumento selection (que é o quarto na listagem) foi especificado para fazer a pesquisa apenas pela row cujo _id foi o passado.

E finalmente o updateNote() que recebe a rowId, title e body e usa uma instância de ContentValues para fazer o update da nota de uma dada rowId.

Passo 3

Abra o arquivo notepad_list.xml que se encontra em res/layout e veja como ele foi criado.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
</LinearLayout>
Esse é um arquivo de definição de layout bastante vazio. Aqui estão algumas coisas que você deveria saber a respeito desse arquivo de layout:
  • Todos os arquivos de layout do Android devem iniciar-se com a linha de cabeçalho como a que vemos acima.
    A próxima definição será na maioria das vezes (mas não em todas as vezes) a definição de um tipo de layout. No caso acima, usamos o LinearLayout.
    O namespace XML do Android deve sempre ser definido para um componente de alto nível ou layout no XML para que as tags android: possam ser usadas através do resto do arquivo.
    xmlns:android:"https://schemas.android.com/apk/res/android"
Passo 4

Precisamos criar um layout para guardar nossa lista. Então, que tal adicionar códigos dentro do elemento LinearLayout para que nosso arquivo completo se pareça com o que vai abaixo?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

  <ListView
        android:id="@android:id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
  <TextView
        android:id="@android:id/empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/no_notes"/>

</LinearLayout>
Alguns comentários a respeito do XML acima:
  • O símbolo @ perto das strings de id da ListView e da TextView significa que o parser XML deverá fazer o parse e expandir o resto da string id e usar um ID de recursos.
  • A ListView e TextView podem ser pensadas como duas views alternativas. Apenas uma delas será mostrada de cada vez. ListView será usado quando existem notas a serem mostradas enquanto que a TextView (que tem o valor padrão de "Nenhuma nota cadastrada" definido no arquivo de recursos em res/values/string.xml) será exibido se não existir nenhuma nota a ser mostrada.
  • Os ids list e empty são providos pela plataforma Android então você deverá prefixar o id com android: (e.g. @android:id/list)
  • A view com o id empty é usado automaticamente quando o ListAdapter não tem dados para a ListView. O ListAdapter sabe procurar por esse nome por padrão. Alternativamente, você poderá mudar a view vazia usando setEmptyView(View) dentro da ListView.
Passo 5

Para criar uma lista de notas na ListView, também precisamos definir uma View para cada linha.
  1. Crie um novo arquivo dentro de res/layout chamado notes_row.xml.
  2. Adicione o código seguinte dentro do arquivo:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </LinearLayout>
    Nesse caso nós criamos um novo id chamado text1. O + após o @ na id indica que o id deverá ser automaticamente criado como um recurso se ele não existir efetivamente. Então, nós o definimos como text1 e então o usamos.
  3. Salve o arquivo.
Passo 6

Agora vamos abrir a classe Notepadv1. Nos próximos passos vamos alterar essa classe para fazer dela um list adapter e mostrar nossas notas e também para permitir que possamos adicionar novas notas.
package com.android.demo.notepad1;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class Notepadv1 extends Activity {
    private int mNoteNumber = 1;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // TODO Auto-generated method stub
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // TODO Auto-generated method stub
        return super.onOptionsItemSelected(item);
    }
}
A classe Notepadv1 herda métodos de uma subclasse de Activity chamada ListActivity, que tem funcionalidades extras para acomodar os tipos de coisas que você deseja fazer com a lista. Por exemplo: mostrar um número arbitrário de itens de lista em linhas na tela, mover-se através de itens de lista e permitir que eles sejam selecionados.

Veja que no código da classe Notepadv1 existe uma variável privada chamada mNoteNumber que não foi usada e que usaremos para criar títulos de notas numeradas.

Existem também três métodos definidos: onCreate, onCreateOptionsMenu e onOptionsItemSelected.
  • onCreate() é chamado quando a atividade é iniciada - é mais ou menos como se fosse o método principal da atividade. Nós usamos ele para configurar recursos e o estado da atividade quando ela está rodando.
  • onCreateOptionsMenu() é usada para popular o menu para a atividade. Ele é mostrado quando o usuário pressiona o botão do menu e tem uma lista que ele pode selecionar (como "Criar Nota").
  • onOptionsItemSelected() é a outra metade da equação do menu e é usada para manusear eventos gerados a partir do menu (e.g. quando o usuário seleciona o item "Criar Nota").
Passo 7

Mude a herança de Notepadv1 de Activity para ListActivity.
public class Notepadv1 extends ListActivity
Nota: Você terá de importar ListActivity para a classe de Notepadv1 usando o Eclipse. Para tal, pressione Control+Shift+O.

Passo 8

Vamos preencher o método onCreate().

Nesse método vamos configurar um título para a atividade (mostrado no topo da tela), usar o layout notepad_list que nós criamos em XML, configurar uma instância de NotesDbAdapter que acessará os dados e popular a lista com as notas disponíveis.
  1. Dentro do método onCreate(), chame super.onCreate() com o parâmetro savedInstanceState que é passado para o método.
  2. Chame setContentView() e passe R.layout.notepad_list.
  3. No topo da classe crie um novo campo privado chamado mDbHelper do tipo NotesDbAdapter.
  4. De volta ao método onCreate, construa uma nova instância de NotesDbAdapter e assinále-a para o campo privado mDbHelper (passando this para o construtor de DBHelper).
  5. Finalmente, chame o novo método fillData() que vai buscar os dados e populá-los na ListView usando o helper - que nós ainda não definimos.
O onCreate() deverá se parecer com o código abaixo:
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.notepad_list);
        mDbHelper = new NotesDbAdapter(this);
        mDbHelper.open();
        fillData();
    }
Não se esqueça de definir o campo privado mDbHelper.
private NotesDbAdapter mDbHelper;
Passo 9

Vamos agora preencher o método onCreateOptionsMenu().

Nós agora vamos criar um botão de adição de item que poderá ser acessado pressionando o botão de menu no dispositivo. Vamos especificar também que ele ocupe a primeira posição no menu.
  1. Dentro do recurso strings.xml (dentro de res/values) adicione uma nova string chamada "menu_insert" cujo valor esteja setado para "Adicionar Item".

    <string name="menu_insert">Adicionar Item</string>
  2. Crie uma constante de posição de menu no topo da classe:

    public static final int INSERT_ID = Menu.FIRST;
  3. Dentro do método onCreateOptionsMenu() mude a chamada para super para que nós possamos capturar o booleano retornado como resultado. Nós retornaremos esse valos ao final do método.
  4. Por fim, adicione o item de menu com menu.add.
O código completo do método deverá se parecer com esse:
@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        boolean result = super.onCreateOptionsMenu(menu);
        menu.add(0, INSERT_ID, 0, R.string.menu_insert);
        return result;
    }
O argumento passado para menu.add indica: um identificador de grupo para esse menu (nenhum, no caso), um ID único (definido acima), a ordem do item (0 indica nenhuma preferência) e o recurso da string a ser usada no item).

Passo 10

Por fim, última modificação de método já existente: onOptionsItemSelected().

Esse método será responsável por receber o comando de inserir menu. Quando o método for chamado, ele será chamado com o item.getID setado para o INSERT_ID (que é a constante que usamos para identificar o item de menu). Nós podemos detectar que botão foi pressionado e tomar as ações apropriadas:,
  1. O método super.onOptionsItemSelected(item) vai para o final de seu método - queremos que ele capture os eventos em primeiro lugar.
  2. Escrevemos um switch para item.getItemId().
    No caso de ser INSERT_ID, o método createNote() será chamado e retornará true, já que nós manuseamos esse evento e nao queremos que ele se propague pelo sistema.
  3. Retorna o resultado para o método onOptionsItemSelected() da classe pai (a classe pai, no caso, que é a ListActivity).
O código completo deverá ser como mostrado abaixo:
@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case INSERT_ID:
            createNote();
            return true;
        }
      
        return super.onOptionsItemSelected(item);
    }
Passo 12

Vamos criar o método createNote()?

Sendo essa a primeira versão da nossa aplicação, o createNote() não será muito útil. Nós simplesmente criaremos uma nova nota com um título baseado na contagem de notas já existentes ("Nota 1", "Nota 2") e com um corpo vazio. Nesse momento, não há nenhuma maneira de se editar os conteúdos de uma nota e por agora nós fazemos com que o conteúdo seja criado com valores padrão.
  1. Construa o nome usando "Nota" e o contador que nós definimos na chasse:
    String noteName = "Nota " + mNoteNumber++
  2. Chame mDbHelper.createNome() usando noteName como título e "" como corpo da nota.
  3. Chame fillData() para popular a lista de notas (ineficiente mas simples) - nós criaremos esse método posteriormente.
O código completo deve ser como mostrado abaixo:
private void createNote() {
        String noteName = "Nota " + mNoteNumber++;
        mDbHelper.createNote(noteName, "");
        fillData();
    }
Passo 12

Vamos, enfim, definir o método fillData().

Esse método usa o SimpleCursorAdapter que toma um Cursor de banco de dados e faz o bind para os campos providos no layout. Esses campos definem os elementos de linha de nossa lista (nesse caso usamos o text1 do layout notes_row.xml), para que se permita facilmente popular a lista com entradas do nosso banco de dados.

Para fazer isso nós devemos prover um mapeamento do campo title no Cursor retornado para nosso TextView text1 que deve ser feito definindo suas arrays: a primeira é uma array de strings com a lista de colunas para se mapear a partir dele e, a segunda, um array de inteiros contendo referências para as views onde os dados terão o bind feito. Ou seja, diremos de onde retiraremos os dados e onde vamos inserir para ser mostrado na tela.

O código deverá ser:
private void fillData() {
        // Capture todas as notas do BD e crie uma lista de itens
        Cursor c = mDbHelper.fetchAllNotes();
        startManagingCursor(c);

        String[] from = new String[] { NotesDbAdapter.KEY_TITLE };
        int[] to = new int[] { R.id.text1 };
       
        // Agora crie um array adapter e configure-o para ser mostrado usando nossa linha
        SimpleCursorAdapter notes =
            new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
        setListAdapter(notes);
    }
O que nós fizemos acima é:
  1. Após obter o Cursor a partir de mDbFetcher.fetchAllNotes(), nós usamos o método da atividade chamado startManagingCursor() que permite ao Android tomar conta do ciclo de vida do Cursor ao invés de nós mesmos ter de nos preocupar com ele (nós chegaremos ao ciclo de vida no exercício 3).
  2. Então nós criamos uma arry de strings na qual declaramos as colunas que queremos (nesse caso, apenas o title) e uma array de inteiros que definirá as Views às quais queremos fazer o bind das colunas.
  3. Por fim, fazemos a instanciação de SimpleCursorAdapter. Como muitas classes do Android, o SimpleCursorAdapter precisa de um Context para que funcione então nós passamos this para o Contexto (isso funciona já que as subclasses de atividade implementam o Context). Nós passamos a View notes_row que criamos como o receptor dos dados, o Cursor que acabamos de criar e então nossos arrays.
No futuro, lembre-se de que o mapeamento entre os recursos "de onde" e "para onde" é feito usando a ordenação específica das duas arrays. Se nós tivéssemos mais colunas que quisessemos fazer o bind e mais Views a fazer o bind, nós teríamos de especificar em que ordem. O exemplo seria chamar assim: {NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_BODY} e {R.id.text1, R.id.text2} para fazer o bind de dois campos dentro de uma row. É dessa maneira que conseguimos fazer o bind de múltiplos campos em uma única linha (e obter, assim, uma linha customizada de layout).

Se tudo correr bem

Se tudo correr bem, o resultado que você terá deverá ser algo como o mostrado abaixo após tocar em Adicionar Item algumas vezes.



Mas se você não consegui, não se preocupe. Crie um novo projeto chamado Notepadv1Solution e siga o passo 01 desse post apontando para a pasta Notepadv1Solution. Assim você terá o código correto e poderá comparar com o seu código e descobrir onde errou.

Até a próxima!

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
Related Posts Plugin for WordPress, Blogger...