Android Avançado.pdf

  • Published on
    25-Nov-2015

  • View
    307

  • Download
    2

DESCRIPTION

Minicurso Android Avançado Flávio Augusto de Freitas flaviocefetrp@gmail.com http://flavioaf.blogspot.com Sumário Passos Iniciais ....................................................................................................................................................................…

Transcript

Minicurso Android Avançado Flávio Augusto de Freitas flaviocefetrp@gmail.com http://flavioaf.blogspot.com Sumário Passos Iniciais .................................................................................................................................................................... 3 Montando um Ambiente de Desenvolvimento para Android ...................................................................................... 4 Configurando um Dispositivo Android com o AVD Manager ....................................................................................... 9 Tutoriais Avançados ........................................................................................................................................................ 12 Persistência em Banco de Dados ................................................................................................................................ 13 Editando Registros do Banco de Dados ...................................................................................................................... 22 Configurando Preferências ......................................................................................................................................... 36 Ajustando o Layout para Paisagem (II) ....................................................................................................................... 41 Integrando Bibliotecas de Terceiros (Twitter) ............................................................................................................ 44 Utilizando um IntentService........................................................................................................................................ 53 Integrando o GPS ........................................................................................................................................................ 57 Alterando o Ícone do Launcher ................................................................................................................................... 65 Integrando com o Google Maps ................................................................................................................................. 67 Alarmes ....................................................................................................................................................................... 69 Notificações ................................................................................................................................................................ 76 Internacionalização (i18n) ........................................................................................................................................... 78 Widgets ....................................................................................................................................................................... 84 Fazendo Ligações (Chamadas) .................................................................................................................................... 91 Terminamos .................................................................................................................................................................... 97 Contatos .......................................................................................................................................................................... 98 Passos Iniciais Montando um Ambiente de Desenvolvimento para Android Hoje vou mostrar como montar um ambiente de desenvolvimento para Android! Pra quem nunca ouviu falar, o Android é um sistema operacional da Google para dispositivos móveis. Hoje em dia, milhões de celulares e tablets utilizam o Android como sistema. Uma coisa bem legal é que você pode disponibilizar seus aplicativos no Market do Android (após o pagamento de uma taxa) e talvez até ganhar um dinheiro com isso! Legal, né? O desenvolvimento de aplicativos para Android é feito utilizando a linguagem Java, com a utilização de arquivos XML para a criação das interfaces. Apesar de parecer complexo, é relativamente simples criar seus aplicativos. Além disso, é bem fácil ter acesso a diversos recursos geralmente disponíveis em dispositivos móveis, tais como câmera, GPS, Bluetooth, etc. Para facilitar o desenvolvimento, foi criado um plug-in para o Eclipse. Através dele, é fácil gerenciar as plataformas (diversas versões do Android) e as máquinas virtuais para executar seus aplicativos. Bom, então pra começar, vamos fazer o download da JDK. Se você já programa em Java, este passo não é necessário. No momento em que escrevo este tutorial, a versão mais recente é a Java 7 update 2. Siga até esta página e faça o download. A instalação deve ocorrer sem problemas (o famoso, next, next, next, finish). O próximo passo é baixar o Eclipse. Vá até esta página e faça o download relacionado a versão de seu sistema operacional. Para os nossos propósitos, a versão Eclipse IDE for Java Developers deve ser suficiente. Ao concluir o download, basta descompactar o arquivo em algum lugar da sua máquina. Eu recomendo, no caso do Windows, na raiz C: ou em sua pasta de usuário (C:\Users\). Neste exemplo, vou referenciar a pasta do Eclipse como C:\eclipse. Prosseguindo, devemos agora baixar o Android SDK. É ele quem nos fornecerá todas as ferramentas da plataforma, como emulador, bibliotecas, etc. Vá até essa página e baixe a versão zipada da SDK (apesar de recomendarem a versão “instalável”) – isso é pra evitarmos problemas de permissão na hora de baixar as SDKs, caso esteja na pasta de programas do sistema (Program Files ou Arquivos de Programas). No momento em que escrevo este tutorial, a versão mais recente é a 16. Após a conclusão do download, descompacte o arquivo (pode ser no mesmo local onde você colocou o Eclipse). Aqui, por exemplo, vai ficar C:\android-sdk-windows. Após extrair, vamos executar o SDK Manager para baixar uma SDK para começarmos a programar. Ao executar pela primeira vez, o SDK Manager irá verificar os repositórios do Android em busca das últimas versões do SDK. Para começar, vamos baixar o SDK da versão 2.2, já que os aplicativos desenvolvidos nela funcionam na grande maioria dos dispositivos Android de hoje. Se quiser instalar outras versões, fique à vontade. Expanda a pasta Android 2.2 (API 8 ) e marque as opções SDK Platform. Além disso, na categoria Tools, marque a opção Android SDK Platform- tools. Clique em Install 2 packages… (ou mais, se você selecionou mais alguma coisa), marque Accept All e então clique em Install. Após a conclusão dos downloads, é hora de configurar o Eclipse. Vá até o diretório onde ele foi descompactado e execute-o. Ao ser consultado sobre qual workspace utilizar, basta confirmar e utilizar o padrão (workspace é o local onde seus projetos serão salvos; ele fica na pasta C:\Users\\workspace). Vamos agora adicionar o plugin para integrar o SDK Manager e o AVD Manager ao Eclipse. Clique no menu Help -> Install New Software… e na janela que abrir, clique no botão Add…. Na tela seguinte, preencha o nome do plugin (ADT Plugin) e coloque o endereço https://dl-ssl.google.com/android/eclipse, conforme a imagem abaixo: Clique em OK e aguarde o carregamento do repositório. Ao concluir, marque a caixa Developer Tools e clique em Next > duas vezes. Na tela seguinte, aceite os termos da licença e clique em Finish. Agora aguarde a instalação e, caso seja alertado sobre conteúdo não-assinado, clique em OK para continuar. Ao final, clique em Restart Now para reiniciar o Eclipse e concluir a instalação. O próximo passo é configurar o local onde as SDKs estão. No Eclipse, vá ao menu Window -> Preferences. Clique no botão Browse… e aponte para a pasta que você descompactou. Após a confirmação, deverão ser exibidas as SDKs que você baixou. Pronto! Seu ambiente Android já está pronto para ser utilizado! No próximo tutorial veremos como configurar um dispositivo para executar nossa aplicação. Configurando um Dispositivo Android com o AVD Manager Olá pessoal! No último tutorial sobre Android, vimos como configurar o ambiente para programarmos, utilizando o Eclipse. Neste tutorial, vamos ver como criar um dispositivo para a execução dos aplicativos que serão criados. Assim, você não precisa necessariamente de um celular com Android para começar a desenvolver para a plataforma. Bom, o primeiro passo é abrir o Eclipse e clicar no ícone do AVD Manager (AVD = Android Virtual Device), ou ir até o menu Window -> AVD Manager. Será, então, aberta a janela com a listagem de dispositivos criados (no nosso caso, nenhum ainda). Então, para criarmos um novo dispositivos, clicamos no botão New…. Nesta tela, devemos preencher os dados relativos ao nosso dispositivo, como nome (Name), versão do Android que irá executar (Target), além de dados como tamanho do cartão SD virtual (caso desejado), tamanho da tela e periféricos (câmera, GPS, Acelerômetro, Teclado físico, etc.). Após montar seu dispositivo, clique em Create AVD e terá seu dispositivo listado! Quando estiver desenvolvendo, é recomendável criar diferentes tipos de dispositivos, com versões diferentes do Android e tamanhos de tela variados, de forma a fazer seu aplicativo ser executado corretamente em diversas configurações. Tutoriais Avançados Persistência em Banco de Dados Olá leitores! No tutorial de hoje, vamos criar um sistema de persistência para a nossa Lista de Restaurantes. Assim, os restaurantes cadastrados serão mantidos a cada execução do aplicativo. O sistema Android nos fornece nativamente as opções de persistir dados utilizando arquivos ou em banco de dados, utilizando o SQLite. Se você não conhece o projeto, é interessante dar uma lida sobre ele. É um banco de dados bastante leve, que nos permite facilmente trabalhar com SQL sobre um arquivo. Neste tutorial, estou assumindo que você tenha um conhecimento básico em SQL. Se você nunca mexeu com isso, não se preocupe, pois os conceitos não são complicados de entender. Bom, começando o nosso tutorial, vamos criar uma classe que gerenciará a criação e abertura do nosso banco de dados. Vamos chamá-la de GerenciadorRestaurantes. Coloque-a no pacote com.blogspot.flavioaf.restaurante. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.content.Context; 4 import android.database.sqlite.SQLiteDatabase; 5 import android.database.sqlite.SQLiteOpenHelper; 6 7 public class GerenciadorRestaurantes extends SQLiteOpenHelper { 8 9 private static final String NOME_BANCO = "restaurantes.db"; 10 private static final int VERSAO_SCHEMA = 1; 11 12 public GerenciadorRestaurantes(Context context) { 13 super(context, NOME_BANCO, null, VERSAO_SCHEMA); 14 } 15 16 @Override 17 public void onCreate(SQLiteDatabase db) { 18 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," 19 + " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);"); 20 21 } 22 23 @Override 24 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 25 26 } 27 } A princípio, definimos que nosso banco de dados será armazenado no arquivo restaurantes.db e que utilizaremos a primeira versão do schema do banco. Neste ponto o projeto ainda deve compilar sem problemas. Em seguida, vamos implementar o método onCreate() para que ele crie o nosso banco de dados. 18 @Override 19 public void onCreate(SQLiteDatabase db) { 20 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 21 " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);"); 22 } Neste trecho simplesmente criamos a tabela restaurantes com seus campos. O método onUpgrade() não será útil para nós por enquanto. Em um aplicativo real, você poderia implementá-lo para fazer backup dos dados em uma tabela temporária, atualizar a estrutura do banco e então retornar os dados. O próximo passo é remover partes do código da classe ListaRestaurantes que não nos serão úteis daqui pra frente (como os trechos que manipulam a nossa barra de progresso). As primeiras exclusões são os atributos estaAtivo e progresso. Em seguida, podemos remover a chamada ao método requestWindowFeature() dentro do método onCreate(). Podemos também excluir as implementações dos métodos onPause(), onResume(), onCreateOptionsMenu() e onOptionsItemSelected(). Por fim, podemos excluir também os métodos iniciarTarefa(), fazerAlgoDemorado() e a nossa tarefaLonga. A classe GerenciadorRestaurantes será a nossa ponte entre a aplicação e o banco de dados. Dessa forma, vamos criar um atributo na classe ListaRestaurantes chamado gerenciador do tipo GerenciadorRestaurante. 34 GerenciadorRestaurantes gerenciador; Lá no método onCreate(), após a chamada a setContentView(), vamos então instanciar o atributo: 40 gerenciador = new GerenciadorRestaurantes(this); Complementando, implemente o método onDestroy() na classe ListaRestaurantes. 67 @Override 68 public void onDestroy() { 69 super.onDestroy(); 70 gerenciador.close(); 71 } Nós vamos agora, substituir nosso objeto de modelo (e seu ArrayList associado) pelo banco de dados, utilizando também a classe Cursor do Android para controlar as instâncias. Primeiramente, vamos adicionar o método inserir() na classe GerenciadorRestaurantes: 27 public void inserir(String nome, String endereco, String tipo, String anotacoes) { 28 ContentValues valores = new ContentValues(); 29 30 valores.put("nome", nome); 31 valores.put("endereco", endereco); 32 valores.put("tipo", tipo); 33 valores.put("anotacoes", anotacoes); 34 35 getWritableDatabase().insert("restaurantes", "nome", valores); 36 } Neste método, recebemos os valores individuais dos campos que compõem a classe Restaurante e adicionamos a um objeto ContentValues, relacionando os valores com as colunas da tabela do nosso banco de dados. Por fim, obtemos uma instância do banco para escrita e inserimos os valores na tabela restaurantes. Agora, devemos realizar a chamada a este método ao pressionarmos o botão Salvar em nosso formulário (onSave). 93 private OnClickListener onSave = new OnClickListener() { 94 95 public void onClick(View arg0) { 96 String tipo = null; 97 98 switch (tipos.getCheckedRadioButtonId()) { 99 case R.id.rodizio: 100 tipo = "rodizio"; 101 break; 102 case R.id.fast_food: 103 tipo = "fast_food"; 104 break; 105 case R.id.a_domicilio: 106 tipo = "a_domicilio"; 107 break; 108 } 109 110 gerenciador.inserir(nome.getText().toString(), 111 endereco.getText().toString(), tipo, 112 anotacoes.getText().toString()); 113 } 114 }; Em seguida, vamos fazer com que a listagem de restaurantes seja realizada a partir do nosso banco de dados. Se você já mexeu com banco de dados no Java, já deve ter visto o funcionamento de um ResultSet. Ele armazena o conteúdo de uma consulta ao banco de dados. No Android, utilizamos a classe Cursor que tem funcionamento semelhante. Assim, vamos criar um método na classe GerenciadorRestaurantes para obter a lista de restaurantes salvos no banco. Vamos implementar o método obterTodos(): 39 public Cursor obterTodos() { 40 return getReadableDatabase().rawQuery("select id, nome, endereco, tipo, " + 41 "anotacoes FROM restaurantes ORDER BY nome", null); 42 } Precisaremos também de métodos que nos forneçam acesso a determinados campos do Cursor. Dessa forma, adicione estes métodos à classe GerenciadorRestaurantes: 44 public String obterNome(Cursor c) { 45 return c.getString(1); 46 } 47 48 public String obterEndereco(Cursor c) { 49 return c.getString(2); 50 } 51 52 public String obterTipo(Cursor c) { 53 return c.getString(3); 54 } 55 56 public String obterAnotacoes(Cursor c) { 57 return c.getString(4); 58 } Na nossa implementação atual, a classe Adaptador estende a classe ArrayAdapter, de forma que ela não conseguirá manipular os dados contidos no Cursor. Assim, modificaremos sua implementação para, então, estender não mais ArrayAdapter, mas sim CursorAdapter. 118 class AdaptadorRestaurante extends CursorAdapter { 119 AdaptadorRestaurante(Cursor c) { 120 super(ListaRestaurantes.this, c); 121 } 122 123 @Override 124 public void bindView(View view, Context context, Cursor cursor) { 125 ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag(); 126 armazenador.popularFormulario(cursor, gerenciador); 127 } 128 129 @Override 130 public View newView(Context context, Cursor cursor, ViewGroup parent) { 131 LayoutInflater inflater = getLayoutInflater(); 132 View linha = inflater.inflate(R.layout.linha, parent, false); 133 ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha); 134 linha.setTag(armazenador); 135 return linha; 136 } 137 } Como pode ser percebido, a classe ArmazenadorRestaurante também necessita de alguns ajustes, para manipular o objeto da classe Cursor. Mas antes, vamos modificar o atributo listaRestaurantes do tipo List para Cursor. 28 Cursor listaRestaurantes; Agora, no método onCreate(), substitua o código que populava o antigo ArrayList por este: 53 listaRestaurantes = gerenciador.obterTodos(); 54 startManagingCursor(listaRestaurantes); 55 adaptador = new AdaptadorRestaurante(listaRestaurantes); 56 lista.setAdapter(adaptador); Prosseguindo, vamos atualizar a classe ArmazenadorRestaurante para trabalhar com o Cursor: 138 static class ArmazenadorRestaurante { 139 private TextView nome = null; 140 private TextView endereco = null; 141 private ImageView icone = null; 142 143 ArmazenadorRestaurante(View linha) { 144 nome = (TextView) linha.findViewById(R.id.titulo); 145 endereco = (TextView) linha.findViewById(R.id.endereco); 146 icone = (ImageView) linha.findViewById(R.id.icone); 147 } 148 149 void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) { 150 nome.setText(gerenciador.obterNome(c)); 151 endereco.setText(gerenciador.obterEndereco(c)); 152 153 if (gerenciador.obterTipo(c).equals("rodizio")) { 154 icone.setImageResource(R.drawable.rodizio); 155 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 156 icone.setImageResource(R.drawable.fast_food); 157 } else { 158 icone.setImageResource(R.drawable.entrega); 159 } 160 } 161 } Por fim, vamos modificar todas as referências ao ArrayList que tínhamos no nosso onListClick. 78 private OnItemClickListener onListClick = new OnItemClickListener() { 79 public void onItemClick(AdapterView parent, View view, int position, 80 long id) { 81 listaRestaurantes.moveToPosition(position); 82 nome.setText(gerenciador.obterNome(listaRestaurantes)); 83 endereco.setText(gerenciador.obterEndereco(listaRestaurantes)); 84 anotacoes.setText(gerenciador.obterAnotacoes(listaRestaurantes)); 85 86 if (gerenciador.obterTipo(listaRestaurantes).equals("rodizio")) { 87 tipos.check(R.id.rodizio); 88 } else if (gerenciador.obterTipo(listaRestaurantes).equals("fast_food")) { 89 tipos.check(R.id.fast_food); 90 } else { 91 tipos.check(R.id.a_domicilio); 92 } 93 94 getTabHost().setCurrentTab(1); 95 } 96 }; Como último passo precisamos adicionar uma linha para que a lista seja atualizada a cada inserção. Insira a seguinte linha após a inserção lá no onSave: 117 listaRestaurantes.requery(); Pronto! Você já pode executar a sua versão persistente do Lista de Restaurantes! Segue a listagem completa da classe ListaRestaurantes: 1 package com.blogspot.flavioaf.restaurante; 2 3 import com.blogspot.flavioaf.restaurante.model.Restaurante; 4 import android.app.TabActivity; 5 import android.content.Context; 6 import android.database.Cursor; 7 import android.os.Bundle; 8 import android.view.LayoutInflater; 9 import android.view.View; 10 import android.view.View.OnClickListener; 11 import android.view.ViewGroup; 12 import android.widget.AdapterView; 13 import android.widget.AdapterView.OnItemClickListener; 14 import android.widget.Button; 15 import android.widget.CursorAdapter; 16 import android.widget.EditText; 17 import android.widget.ImageView; 18 import android.widget.ListView; 19 import android.widget.RadioGroup; 20 import android.widget.TabHost.TabSpec; 21 import android.widget.TextView; 22 public class ListaRestaurantes extends TabActivity { 23 24 Cursor listaRestaurantes; 25 AdaptadorRestaurante adaptador = null; 26 Restaurante atual = null; 27 28 EditText nome = null; 29 EditText endereco = null; 30 EditText anotacoes = null; 31 RadioGroup tipos = null; 32 GerenciadorRestaurantes gerenciador; 33 34 @Override 35 public void onCreate(Bundle savedInstanceState) { 36 super.onCreate(savedInstanceState); 37 setContentView(R.layout.main); 38 gerenciador = new GerenciadorRestaurantes(this); 39 40 nome = (EditText) findViewById(R.id.nome); 41 endereco = (EditText) findViewById(R.id.end); 42 anotacoes = (EditText) findViewById(R.id.anotacoes); 43 tipos = (RadioGroup) findViewById(R.id.tipos); 44 45 Button salvar = (Button) findViewById(R.id.salvar); 46 salvar.setOnClickListener(onSave); 47 48 ListView lista = (ListView) findViewById(R.id.restaurantes); 49 50 listaRestaurantes = gerenciador.obterTodos(); 51 startManagingCursor(listaRestaurantes); 52 adaptador = new AdaptadorRestaurante(listaRestaurantes); 53 lista.setAdapter(adaptador); 54 55 TabSpec descritor = getTabHost().newTabSpec("tag1"); 56 descritor.setContent(R.id.restaurantes); 57 descritor.setIndicator("Lista", 58 getResources().getDrawable(R.drawable.lista)); 59 getTabHost().addTab(descritor); 60 61 descritor = getTabHost().newTabSpec("tag2"); 62 descritor.setContent(R.id.detalhes); 63 descritor.setIndicator("Detalhes", 64 getResources().getDrawable(R.drawable.restaurante)); 65 getTabHost().addTab(descritor); 66 67 getTabHost().setCurrentTab(0); 68 69 lista.setOnItemClickListener(onListClick); 70 } 71 72 @Override 73 public void onDestroy() { 74 super.onDestroy(); 75 gerenciador.close(); 76 } 77 78 private OnItemClickListener onListClick = new OnItemClickListener() { 79 public void onItemClick(AdapterView parent, View view, int position, 80 long id) { 81 listaRestaurantes.moveToPosition(position); 82 nome.setText(gerenciador.obterNome(listaRestaurantes)); 83 endereco.setText(gerenciador.obterEndereco(listaRestaurantes)); 84 anotacoes.setText(gerenciador.obterAnotacoes(listaRestaurantes)); 85 86 if (gerenciador.obterTipo(listaRestaurantes).equals("rodizio")) { 87 tipos.check(R.id.rodizio); 88 } else if (gerenciador.obterTipo(listaRestaurantes).equals("fast_food")) { 89 tipos.check(R.id.fast_food); 90 } else { 91 tipos.check(R.id.a_domicilio); 92 } 93 94 getTabHost().setCurrentTab(1); 95 } 96 }; 97 98 private OnClickListener onSave = new OnClickListener() { 99 100 public void onClick(View arg0) { 101 String tipo = null; 102 103 switch (tipos.getCheckedRadioButtonId()) { 104 case R.id.rodizio: 105 tipo = "rodizio"; 106 break; 107 case R.id.fast_food: 108 tipo = "fast_food"; 109 break; 110 case R.id.a_domicilio: 111 tipo = "a_domicilio"; 112 break; 113 } 114 115 gerenciador.inserir(nome.getText().toString(), endereco.getText() 116 .toString(), tipo, anotacoes.getText().toString()); 117 listaRestaurantes.requery(); 118 } 119 }; 120 121 class AdaptadorRestaurante extends CursorAdapter { 122 AdaptadorRestaurante(Cursor c) { 123 super(ListaRestaurantes.this, c); 124 } 125 126 @Override 127 public void bindView(View view, Context context, Cursor cursor) { 128 ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag(); 129 armazenador.popularFormulario(cursor, gerenciador); 130 } 131 132 @Override 133 public View newView(Context context, Cursor cursor, ViewGroup parent) { 134 LayoutInflater inflater = getLayoutInflater(); 135 View linha = inflater.inflate(R.layout.linha, parent, false); 136 ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha); 137 linha.setTag(armazenador); 138 return linha; 139 } 140 } 141 142 static class ArmazenadorRestaurante { 143 private TextView nome = null; 144 private TextView endereco = null; 145 private ImageView icone = null; 146 147 ArmazenadorRestaurante(View linha) { 148 nome = (TextView) linha.findViewById(R.id.titulo); 149 endereco = (TextView) linha.findViewById(R.id.endereco); 150 icone = (ImageView) linha.findViewById(R.id.icone); 151 } 152 153 void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) { 154 nome.setText(gerenciador.obterNome(c)); 155 endereco.setText(gerenciador.obterEndereco(c)); 156 157 if (gerenciador.obterTipo(c).equals("rodizio")) { 158 icone.setImageResource(R.drawable.rodizio); 159 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 160 icone.setImageResource(R.drawable.fast_food); 161 } else { 162 icone.setImageResource(R.drawable.entrega); 163 } 164 } 165 } 166 } … e GerenciadorRestaurantes … 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.content.ContentValues; 4 import android.content.Context; 5 import android.database.Cursor; 6 import android.database.sqlite.SQLiteDatabase; 7 import android.database.sqlite.SQLiteOpenHelper; 8 9 public class GerenciadorRestaurantes extends SQLiteOpenHelper { 10 11 private static final String NOME_BANCO = "restaurantes.db"; 12 private static final int VERSAO_SCHEMA = 1; 13 14 public GerenciadorRestaurantes(Context context) { 15 super(context, NOME_BANCO, null, VERSAO_SCHEMA); 16 } 17 18 @Override 19 public void onCreate(SQLiteDatabase db) { 20 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 21 " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);"); 22 } 23 24 @Override 25 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 26 27 } 28 29 public void inserir(String nome, String endereco, String tipo, String anotacoes) { 30 ContentValues valores = new ContentValues(); 31 32 valores.put("nome", nome); 33 valores.put("endereco", endereco); 34 valores.put("tipo", tipo); 35 valores.put("anotacoes", anotacoes); 36 37 getWritableDatabase().insert("restaurantes", "nome", valores); 38 } 39 40 public Cursor obterTodos() { 41 return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " + 42 "anotacoes FROM restaurantes ORDER BY nome", null); 43 } 44 45 public String obterNome(Cursor c) { 46 return c.getString(1); 47 } 48 49 public String obterEndereco(Cursor c) { 50 return c.getString(2); 51 } 52 53 public String obterTipo(Cursor c) { 54 return c.getString(3); 55 } 56 57 public String obterAnotacoes(Cursor c) { 58 return c.getString(4); 59 } 60 } Editando Registros do Banco de Dados No último tutorial, tornamos o nosso aplicativo Lista de Restaurantes persistente. No tutorial de hoje, vamos aprimorar a forma como ele lida com o banco de dados, fazendo com que os registros inseridos possam ser editados. Além disso, também faremos uma mudança no visual da aplicação, retirando as abas e incluindo a tela de adição de restaurantes como uma opção no menu. O primeiro passo é criarmos uma nova Activity, que será onde ficará, a partir de agora, o nosso formulário de cadastro (e consequentemente, de edição). Separaremos as funções da nossa Activity inicial. Então, crie a classe FormularioDetalhes. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 6 public class FormularioDetalhes extends Activity { 7 8 @Override 9 public void onCreate(Bundle savedInstanceState) { 10 super.onCreate(savedInstanceState); 11 } 12 } Por enquanto, esta Activity não tem nenhum layout atribuído, já que ainda não criamos o seu layout. Antes de utilizar esta Activity em nosso projeto, precisamos declará-la no arquivo AndroidManifest.xml. Ele encontra-se na raiz da árvore do projeto. Abra-o e selecione a aba inferior AndroidManifest.xml para abri-lo para a edição. Dentro do nó application, adicionaremos um novo nó activity. 1 2 6 7 8 9 12 15 16 17 18 19 20 21 22 23 24 25 Prosseguindo, precisamos iniciar esta Activity quando clicarmos sobre um dos itens da lista. Assim, modifique o onListClick dessa forma: 80 private OnItemClickListener onListClick = new OnItemClickListener() { 81 public void onItemClick(AdapterView parent, View view, int position, 82 long id) { 83 Intent i = new Intent(ListaRestaurantes.this, FormularioDetalhes.class); 84 startActivity(i); 85 } 86 }; Se quiser testar a aplicação, ela deve exibir uma tela vazia ao clicar em algum item da lista. Continuando, vamos agora fazer a migração do formulário para a nova Activity. Primeiramente, crie o arquivo form_detalhes.xml na pasta res/layout, podendo utilizar o main.xml como base para ele: 1 2 6 7 8 9 10 11 12 13 14 15 16 17 19 21 23 24 25 26 27 34 35 39 Agora, volte a Activity FormularioDetalhes e adicione esta linha ao final do método onCreate: 11 setContentView(R.layout.form_detalhes); O próximo passo é mover toda a lógica do formulário para a nossa classe FormularioDetalhes. Primeiramente, adicione os atributos da classe que estavam na ListaRestaurantes para a FormularioDetalhes: 10 EditText nome = null; 11 EditText endereco = null; 12 EditText anotacoes = null; 13 RadioGroup tipos = null; 14 GerenciadorRestaurantes gerenciador; Agora, copie a busca aos widgets no formulário do método onCreate() do ListaRestaurantes para o FormularioDetalhes, no mesmo local. 22 gerenciador = new GerenciadorRestaurantes(this); 23 24 nome = (EditText) findViewById(R.id.nome); 25 endereco = (EditText) findViewById(R.id.end); 26 anotacoes = (EditText) findViewById(R.id.anotacoes); 27 tipos = (RadioGroup) findViewById(R.id.tipos); 28 29 Button salvar = (Button) findViewById(R.id.salvar); 30 salvar.setOnClickListener(onSave); Por fim, vamos copiar a implementação do nosso listener onSave para a classe FormularioDetalhes, porém retirando a parte que trata da inserção no banco de dados: 35 private OnClickListener onSave = new OnClickListener() { 36 37 public void onClick(View arg0) { 38 String tipo = null; 39 40 switch (tipos.getCheckedRadioButtonId()) { 41 case R.id.rodizio: 42 tipo = "rodizio"; 43 break; 44 case R.id.fast_food: 45 tipo = "fast_food"; 46 break; 47 case R.id.a_domicilio: 48 tipo = "a_domicilio"; 49 break; 50 } 51 } 52 }; Agora é hora de “limparmos” a interface original do aplicativo, no main.xml. Retiraremos o formulário que existia e as abas, além do ajuste no layout para abrigar somente a lista. O que nos resta é isso: 1 2 Após isso, exclua a pasta layout_land e o arquivo main.xml dentro dela. No momento, ListaRestaurantes estende TabActivity. Porém, como modificamos a estrutura de nossa aplicação, isso não é mais necessário. Modifique a classe, de forma que ListaRestaurante estenda ListActivity. Em seguida, modifique o método onCreate() para retirar os códigos que diziam respeito às abas que utilizávamos: 34 @Override 35 public void onCreate(Bundle savedInstanceState) { 36 super.onCreate(savedInstanceState); 37 setContentView(R.layout.main); 38 39 gerenciador = new GerenciadorRestaurantes(this); 40 listaRestaurantes = gerenciador.obterTodos(); 41 startManagingCursor(listaRestaurantes); 42 adaptador = new AdaptadorRestaurante(listaRestaurantes); 43 setListAdapter(adaptador); 44 } Antes de seguirmos em frente, vamos analisar o que vamos fazer: o FormularioDetalhes será utilizado tanto na criação de novos restaurantes quando na edição de restaurantes já cadastrados. Além disso, ele precisa saber, quando estiver editando, qual restaurante se trata. Para isso, precisamos do identificador do restaurante (o campo _id do banco de dados). Primeiramente, vamos criar um atributo para a classe ListaRestaurantes: 27 public final static String _ID = "com.blogspot.flavioaf.restaurante._ID"; Após isso, vamos mudar o objeto onListClick para um onListItemClick(), onde vamos passar o valor do id para a outra Activity: 53 @Override 54 public void onListItemClick(ListView l, View v, int position, long id) { 55 Intent i = new Intent(ListaRestaurantes.this, FormularioDetalhes.class); 56 i.putExtra(_ID, String.valueOf(id)); 57 startActivity(i); 58 } Em seguida, adicione o seguinte atributo na classe FormularioDetalhes: 18 String idRestaurante = null; Este atributo será nulo se estivermos adicionando um novo restaurante, ou o identificador, caso estejamos editando um restaurante. Como criamos o GerenciadorRestaurantes no método onCreate(), precisamos encerrá-lo no método onDestroy(): 36 @Override 37 public void onDestroy() { 38 super.onDestroy(); 39 gerenciador.close(); 40 } Como agora temos o ID como controle dos restaurantes, precisamos de um método que nos retorne o Restaurante com o identificador correspondente. Adicione o seguinte método a classe GerenciadorRestaurantes: 61 public Cursor obterPorId(String id) { 62 String[] argumentos = {id}; 63 64 return getReadableDatabase().rawQuery( 65 "SELECT _id, nome, endereco, tipo, anotacoes " + 66 "FROM restaurantes WHERE _id = ?", argumentos); 67 } Agora, adicione o seguinte trecho ao fim do método onCreate() da classe FormularioDetalhes: 35 idRestaurante = getIntent().getStringExtra(ListaRestaurantes._ID); 36 37 if (idRestaurante != null) { 38 carregar(); 39 } Adicione, então, a implementação do método carregar(): 66 private void carregar() { 67 Cursor c = gerenciador.obterPorId(idRestaurante); 68 69 c.moveToFirst(); 70 nome.setText(gerenciador.obterNome(c)); 71 endereco.setText(gerenciador.obterEndereco(c)); 72 anotacoes.setText(gerenciador.obterAnotacoes(c)); 73 74 if (gerenciador.obterTipo(c).equals("rodizio")) { 75 tipos.check(R.id.rodizio); 76 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 77 tipos.check(R.id.fast_food); 78 } else { 79 tipos.check(R.id.a_domicilio); 80 } 81 82 c.close(); 83 } Agora, vamos adicionar a opção de menu Adicionar para que possamos, a partir da listagem (que agora será a tela principal do aplicativo), inserir um novo restaurante. Modifique o arquivo opcao.xml que encontra-se em res/menu. 1 2 3 6 Este item de mídia é padrão do Android, e pode ser encontrado no seu diretório de instalação do SDK, em platforms -> versão do Android que está usando (no meu caso, android-8 (ou 2.2)) -> data -> res -> tamanho de tela (podemos utilizar drawable-mdpi). Procure pelo ícone ic_menu_add.png. Copie-o e coloque na pasta res/drawable da sua aplicação. Para padronizar o nome, eu o renomeei para adicionar.png. Agora que já temos o menu, vamos ajustar a classe ListaRestaurantes para manipulá-lo corretamente. Vamos novamente implementar o método onCreateOptionsMenu(): 62 @Override 63 public boolean onCreateOptionsMenu(Menu menu) { 64 new MenuInflater(this).inflate(R.menu.opcao, menu); 65 66 return super.onCreateOptionsMenu(menu); 67 } E adicione, também, a implementação de onOptionsItemSelected(): 70 @Override 71 public boolean onOptionsItemSelected(MenuItem item) { 72 if (item.getItemId() == R.id.adicionar) { 73 startActivity(new Intent(ListaRestaurantes.this, FormularioDetalhes.class)); 74 return true; 75 } 76 77 return super.onOptionsItemSelected(item); 78 } Bom, lá na nossa classe GerenciadorRestaurantes, temos o método para inserir um novo restaurante, mas não temos o método para atualizar. Portanto, adicione o seguinte método à classe: 40 public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes) { 41 ContentValues valores = new ContentValues(); 42 String[] argumentos = {id}; 43 44 valores.put("nome", nome); 45 valores.put("endereco", endereco); 46 valores.put("tipo", tipo); 47 valores.put("anotacoes", anotacoes); 48 49 getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos); 50 } Por fim, precisamos adicionar o comportamento do botão Salvar no formulário. Modifique a implementação do onSave na classe FormularioDetalhes para verificar se a operação é de inclusão ou alteração: 47 private OnClickListener onSave = new OnClickListener() { 48 public void onClick(View arg0) { 49 String tipo = null; 50 51 switch (tipos.getCheckedRadioButtonId()) { 52 case R.id.rodizio: 53 tipo = "rodizio"; 54 break; 55 case R.id.fast_food: 56 tipo = "fast_food"; 57 break; 58 case R.id.a_domicilio: 59 tipo = "a_domicilio"; 60 break; 61 } 62 63 if (idRestaurante == null) { 64 gerenciador.inserir(nome.getText().toString(), 65 endereco.getText().toString(), 66 tipo, anotacoes.getText().toString()); 67 } else { 68 gerenciador.atualizar(idRestaurante, 69 nome.getText().toString(), 70 endereco.getText().toString(), 71 tipo, anotacoes.getText().toString()); 72 } 73 74 finish(); 75 } 76 }; Verifique se não há código duplicado, corrija os imports (Ctrl + Shift + O) e pronto! Já temos nossa aplicação funcionando! Pra simplificar, aí vão as listagens completas: Restaurante.java: 1 package com.blogspot.flavioaf.restaurante.model; 2 3 public class Restaurante { 4 5 private String nome = ""; 6 private String endereco = ""; 7 private String tipo = ""; 8 private String anotacoes = ""; 9 10 public String getNome() { 11 return nome; 12 } 13 14 public void setNome(String nome) { 15 this.nome = nome; 16 } 17 18 public String getEndereco() { 19 return endereco; 20 } 21 22 public void setEndereco(String endereco) { 23 this.endereco = endereco; 24 } 25 26 public String getTipo() { 27 return tipo; 28 } 29 30 public void setTipo(String tipo) { 31 this.tipo = tipo; 32 } 33 34 public String getAnotacoes() { 35 return anotacoes; 36 } 37 38 public void setAnotacoes(String anotacoes) { 39 this.anotacoes = anotacoes; 40 } 41 42 @Override 43 public String toString() { 44 return getNome(); 45 } 46 } FormularioDetalhes.java: 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.app.Activity; 4 import android.database.Cursor; 5 import android.os.Bundle; 6 import android.view.View; 7 import android.view.View.OnClickListener; 8 import android.widget.Button; 9 import android.widget.EditText; 10 import android.widget.RadioGroup; 11 12 public class FormularioDetalhes extends Activity { 13 14 EditText nome = null; 15 EditText endereco = null; 16 EditText anotacoes = null; 17 RadioGroup tipos = null; 18 GerenciadorRestaurantes gerenciador; 19 String idRestaurante = null; 20 21 @Override 22 public void onCreate(Bundle savedInstanceState) { 23 super.onCreate(savedInstanceState); 24 setContentView(R.layout.form_detalhes); 25 26 gerenciador = new GerenciadorRestaurantes(this); 27 28 nome = (EditText) findViewById(R.id.nome); 29 endereco = (EditText) findViewById(R.id.end); 30 anotacoes = (EditText) findViewById(R.id.anotacoes); 31 tipos = (RadioGroup) findViewById(R.id.tipos); 32 33 Button salvar = (Button) findViewById(R.id.salvar); 34 salvar.setOnClickListener(onSave); 35 36 idRestaurante = getIntent().getStringExtra(ListaRestaurantes._ID); 37 38 if (idRestaurante != null) { 39 carregar(); 40 } 41 } 42 43 @Override 44 public void onDestroy() { 45 super.onDestroy(); 46 gerenciador.close(); 47 } 48 49 private OnClickListener onSave = new OnClickListener() { 50 51 public void onClick(View arg0) { 52 String tipo = null; 53 54 switch (tipos.getCheckedRadioButtonId()) { 55 case R.id.rodizio: 56 tipo = "rodizio"; 57 break; 58 case R.id.fast_food: 59 tipo = "fast_food"; 60 break; 61 case R.id.a_domicilio: 62 tipo = "a_domicilio"; 63 break; 64 } 65 66 if (idRestaurante == null) { 67 gerenciador.inserir(nome.getText().toString(), 68 endereco.getText().toString(), 69 tipo, anotacoes.getText().toString()); 70 } else { 71 gerenciador.atualizar(idRestaurante, 72 nome.getText().toString(), 73 endereco.getText().toString(), 74 tipo, anotacoes.getText().toString()); 75 } 76 77 finish(); 78 } 79 }; 80 81 private void carregar() { 82 Cursor c = gerenciador.obterPorId(idRestaurante); 83 84 c.moveToFirst(); 85 nome.setText(gerenciador.obterNome(c)); 86 endereco.setText(gerenciador.obterEndereco(c)); 87 anotacoes.setText(gerenciador.obterAnotacoes(c)); 88 89 if (gerenciador.obterTipo(c).equals("rodizio")) { 90 tipos.check(R.id.rodizio); 91 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 92 tipos.check(R.id.fast_food); 93 } else { 94 tipos.check(R.id.a_domicilio); 95 } 96 97 c.close(); 98 } 99 } GerenciadorRestaurantes.java: 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.content.ContentValues; 4 import android.content.Context; 5 import android.database.Cursor; 6 import android.database.sqlite.SQLiteDatabase; 7 import android.database.sqlite.SQLiteOpenHelper; 8 9 public class GerenciadorRestaurantes extends SQLiteOpenHelper { 10 11 private static final String NOME_BANCO = "restaurantes.db"; 12 private static final int VERSAO_SCHEMA = 1; 13 14 public GerenciadorRestaurantes(Context context) { 15 super(context, NOME_BANCO, null, VERSAO_SCHEMA); 16 } 17 18 @Override 19 public void onCreate(SQLiteDatabase db) { 20 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 21 " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);"); 22 } 23 24 @Override 25 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 26 27 } 28 29 public void inserir(String nome, String endereco, String tipo, String anotacoes) { 30 ContentValues valores = new ContentValues(); 31 32 valores.put("nome", nome); 33 valores.put("endereco", endereco); 34 valores.put("tipo", tipo); 35 valores.put("anotacoes", anotacoes); 36 37 getWritableDatabase().insert("restaurantes", "nome", valores); 38 } 39 40 public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes) { 41 ContentValues valores = new ContentValues(); 42 String[] argumentos = {id}; 43 44 valores.put("nome", nome); 45 valores.put("endereco", endereco); 46 valores.put("tipo", tipo); 47 valores.put("anotacoes", anotacoes); 48 49 getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos); 50 } 51 52 public Cursor obterTodos() { 53 return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " + 54 "anotacoes FROM restaurantes ORDER BY nome", null); 55 } 56 57 public String obterNome(Cursor c) { 58 return c.getString(1); 59 } 60 61 public String obterEndereco(Cursor c) { 62 return c.getString(2); 63 } 64 65 public String obterTipo(Cursor c) { 66 return c.getString(3); 67 } 68 69 public String obterAnotacoes(Cursor c) { 70 return c.getString(4); 71 } 72 73 public Cursor obterPorId(String id) { 74 String[] argumentos = {id}; 75 76 return getReadableDatabase().rawQuery( 77 "SELECT _id, nome, endereco, tipo, anotacoes " + 78 "FROM restaurantes WHERE _id = ?", argumentos); 79 } 80 } ListaRestaurantes.java: 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.app.ListActivity; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.database.Cursor; 7 import android.os.Bundle; 8 import android.view.LayoutInflater; 9 import android.view.Menu; 10 import android.view.MenuInflater; 11 import android.view.MenuItem; 12 import android.view.View; 13 import android.view.ViewGroup; 14 import android.widget.CursorAdapter; 15 import android.widget.ImageView; 16 import android.widget.ListView; 17 import android.widget.TextView; 18 19 public class ListaRestaurantes extends ListActivity { 20 21 Cursor listaRestaurantes = null; 22 AdaptadorRestaurante adaptador = null; 23 public final static String _ID = "com.blogspot.flavioaf.restaurante._ID"; 24 GerenciadorRestaurantes gerenciador; 25 26 @Override 27 public void onCreate(Bundle savedInstanceState) { 28 super.onCreate(savedInstanceState); 29 setContentView(R.layout.main); 30 31 gerenciador = new GerenciadorRestaurantes(this); 32 listaRestaurantes = gerenciador.obterTodos(); 33 startManagingCursor(listaRestaurantes); 34 adaptador = new AdaptadorRestaurante(listaRestaurantes); 35 setListAdapter(adaptador); 36 } 37 38 @Override 39 public void onDestroy() { 40 super.onDestroy(); 41 gerenciador.close(); 42 } 43 44 @Override 45 public void onListItemClick(ListView l, View v, int position, long id) { 46 Intent i = new Intent(ListaRestaurantes.this, FormularioDetalhes.class); 47 i.putExtra(_ID, String.valueOf(id)); 48 System.out.println("ID VALUE: " + id); 49 startActivity(i); 50 } 51 52 @Override 53 public boolean onCreateOptionsMenu(Menu menu) { 54 new MenuInflater(this).inflate(R.menu.opcao, menu); 55 56 return super.onCreateOptionsMenu(menu); 57 } 58 59 @Override 60 public boolean onOptionsItemSelected(MenuItem item) { 61 if (item.getItemId() == R.id.adicionar) { 62 startActivity(new Intent(ListaRestaurantes.this, FormularioDetalhes.class)); 63 return true; 64 } 65 66 return super.onOptionsItemSelected(item); 67 } 68 69 class AdaptadorRestaurante extends CursorAdapter { 70 AdaptadorRestaurante(Cursor c) { 71 super(ListaRestaurantes.this, c); 72 } 73 74 @Override 75 public void bindView(View view, Context context, Cursor cursor) { 76 ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag(); 77 armazenador.popularFormulario(cursor, gerenciador); 78 } 79 80 @Override 81 public View newView(Context context, Cursor cursor, ViewGroup parent) { 82 LayoutInflater inflater = getLayoutInflater(); 83 View linha = inflater.inflate(R.layout.linha, parent, false); 84 ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha); 85 linha.setTag(armazenador); 86 return linha; 87 } 88 } 89 90 static class ArmazenadorRestaurante { 91 private TextView nome = null; 92 private TextView endereco = null; 93 private ImageView icone = null; 94 95 ArmazenadorRestaurante(View linha) { 96 nome = (TextView) linha.findViewById(R.id.titulo); 97 endereco = (TextView) linha.findViewById(R.id.endereco); 98 icone = (ImageView) linha.findViewById(R.id.icone); 99 } 100 101 void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) { 102 nome.setText(gerenciador.obterNome(c)); 103 endereco.setText(gerenciador.obterEndereco(c)); 104 105 if (gerenciador.obterTipo(c).equals("rodizio")) { 106 icone.setImageResource(R.drawable.rodizio); 107 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 108 icone.setImageResource(R.drawable.fast_food); 109 } else { 110 icone.setImageResource(R.drawable.entrega); 111 } 112 } 113 } 114 } Configurando Preferências Vamos adicionar ao nosso aplicativo Lista de Restaurantes a opção do usuário configurar de que forma deve ocorrer a listagem dos restaurantes (nome, tipo, ordem alfabética, etc.). Pra começar, vamos criar um arquivo XML que tomará conta das configurações de preferência. Dessa forma, crie o arquivo preferencias.xml e coloque-o em res/xml (a pasta ainda não existe… então crie-a). O conteúdo dele será: 1 2 9 Em seguida, vamos criar o arquivo arrays.xml que definirá os dois arrays referenciados no XML definido acima. O arquivo arrays.xml deverá ser salvo na pasta res/values. Seu conteúdo é listado a seguir: 1 2 3 4 Por Nome, Ascendente 5 Por Nome, Descendente 6 Por Tipo 7 Por Endereço, Ascendente 8 Por Endereço, Descendente 9 10 11 nome ASC 12 nome DESC 13 tipo, nome ASC 14 endereco ASC 15 endereco DESC 16 17 O próximo passo é a criação da Activity responsável pelas preferências. Vamos criar a classe EdicaoPreferencias, que estenderá PreferenceActivity, dentro do pacote com.blogspot.flavioaf.restaurante: 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.os.Bundle; 4 import android.preference.PreferenceActivity; 5 6 public class EdicaoPreferencias extends PreferenceActivity { 7 8 @Override 9 public void onCreate(Bundle savedInstanceState) { 10 super.onCreate(savedInstanceState); 11 12 addPreferencesFromResource(R.xml.preferencias); 13 } 14 } Também é necessário atualizar o arquivo AndroidManifest.xml, já que adicionamos uma nova Activity ao nosso projeto. 1 2 6 7 8 9 12 15 16 17 18 19 20 21 22 23 24 25 26 Continuando, vamos agora vincular a nossa nova Activity ao menu de opções. Primeiramente, vamos editar o arquivo opcao.xml, que se encontra em res/menu. 1 2 3 6 9 O ícone referenciado é padrão do sistema e, como mostrado no último tutorial, pode ser encontrado na própria instalação da SDK. Este ícone utilizado se chama ic_menu_preferences (aqui foi renomeado para menu_preferencias em nosso projeto). Agora, vamos modificar o método onOptionsItemSelected na classe ListaRestaurantes para mapear esta nova opção adicionada ao menu: 58 @Override 59 public boolean onOptionsItemSelected(MenuItem item) { 60 if (item.getItemId() == R.id.adicionar) { 61 startActivity(new Intent(ListaRestaurantes.this, FormularioDetalhes.class)); 62 return true; 63 } else if (item.getItemId() == R.id.prefs) { 64 startActivity(new Intent(this, EdicaoPreferencias.class)); 65 return true; 66 } 67 68 return super.onOptionsItemSelected(item); 69 } Neste ponto, se você rodar a aplicação, já poderá conferir o menu: Agora, já que a parte visual está pronta, vamos aplicar a ordenação a nossa lista. Primeiramente, precisamos que o método obterTodos() da classe GerenciadorRestaurantes precisa receber o método de ordenação por parâmetro e aplicá-lo a SQL. Modifique-o para que fique assim: 52 public Cursor obterTodos(String ordenacao) { 53 return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " + 54 "anotacoes FROM restaurantes ORDER BY " + ordenacao, null); 55 } Agora, precisamos de um atributo na classe ListaRestaurantes que nos permita saber a ordenação selecionada e aplicá-la a listagem. Adicione um atributo à classe chamado prefs do tipo SharedPreferences. 26 SharedPreferences prefs = null; Em seguida, adicione a inicialização do atributo no método onCreate(), próximo ao seu início. 34 prefs = PreferenceManager.getDefaultSharedPreferences(this); E modifique a chamada ao método obterTodos() logo em seguida: 36 listaRestaurantes = gerenciador.obterTodos(prefs.getString("ordenacao", "nome")); Por fim, vamos fazer com que seja aplicada as alterações realizadas pelo usuário em tempo de execução, já que, por enquanto, é necessário fechar a aplicação para que a nova ordenação tenha efeito. Adicione esta linha ao fim do método onCreate(): 40 prefs.registerOnSharedPreferenceChangeListener(prefListener); Em seguida, vamos criar o listener dentro da classe ListaRestaurantes: 1 private OnSharedPreferenceChangeListener prefListener = new OnSharedPreferenceChangeListener() { 2 3 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 4 String key) { 5 if (key.equals("ordenacao")) { 6 7 } 8 } 9 }; Continuando, vamos isolar a inicialização da lista em um método à parte, deixando o método onCreate() mais limpo. Crie o método inicializarLista(): 77 private void inicializarLista() { 78 if (listaRestaurantes != null) { 79 stopManagingCursor(listaRestaurantes); 80 listaRestaurantes.close(); 81 } 82 83 listaRestaurantes = gerenciador.obterTodos(prefs.getString("listagem", "nome")); 84 startManagingCursor(listaRestaurantes); 85 adaptador = new AdaptadorRestaurante(listaRestaurantes); 86 setListAdapter(adaptador); 87 } Agora, referenciamos o recém-criado método inicializarLista() no método onCreate(): 30 @Override 31 public void onCreate(Bundle savedInstanceState) { 32 super.onCreate(savedInstanceState); 33 setContentView(R.layout.main); 34 35 prefs = PreferenceManager.getDefaultSharedPreferences(this); 36 gerenciador = new GerenciadorRestaurantes(this); 37 inicializarLista(); 38 prefs.registerOnSharedPreferenceChangeListener(prefListener); 39 } E então, também fazemos uma chamada ao método inicializarLista() dentro do nosso prefListener: 86 private OnSharedPreferenceChangeListener prefListener = new OnSharedPreferenceChangeListener() { 87 88 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 89 String key) { 90 if (key.equals("listagem")) { 91 inicializarLista(); 92 } 93 } 94 }; E pronto! Já temos o nosso aplicativo funcionando! Ajustando o Layout para Paisagem (II) Vamos tratar da adaptação do layout da aplicação Lista de Restaurantes também para o modo paisagem. Neste tutorial vamos fazer a rotação de uma maneira mais organizada, evitando alguns problemas. Primeiramente, precisamos de uma forma de armazenar os valores no caso da mudança de orientação do celular. Lá na classe FormularioDetalhes, vamos sobrescrever o método onSaveInstanceState(), que armazenará os valores pra gente. Adicione a seguinte implementação ao final da classe: 100 @Override 101 public void onSaveInstanceState(Bundle outState) { 102 super.onSaveInstanceState(outState); 103 104 outState.putString("nome", nome.getText().toString()); 105 outState.putString("endereco", endereco.getText().toString()); 106 outState.putString("anotacoes", anotacoes.getText().toString()); 107 outState.putInt("tipo", tipos.getCheckedRadioButtonId()); 108 } Pronto. Já fizemos com que os valores do formulário fossem salvos. Agora, vamos implementar o método onRestoreInstanceState() que devolverá os dados no formulário. 110 @Override 111 public void onRestoreInstanceState(Bundle savedInstanceState) { 112 super.onRestoreInstanceState(savedInstanceState); 113 114 nome.setText(savedInstanceState.getString("nome")); 115 endereco.setText(savedInstanceState.getString("endereco")); 116 anotacoes.setText(savedInstanceState.getString("anotacoes")); 117 tipos.check(savedInstanceState.getInt("tipo")); 118 } Por fim, vamos definir novamente o nosso layout em modo paisagem. Crie novamente a pasta (se você a excluiu) res/layout-land e crie o arquivo form_detalhes.xml. Se você ainda tem o arquivo main.xml lá, exclua-o. 1 2 6 7 8 10 11 12 13 15 16 17 18 19 21 22 android:text="Fast Food"/> 23 25 26 27 31 40 44 45 46 E pronto! Quanto a tela de listagem, não precisamos alterar seu layout pois ele funciona bem tanto em modo retrato quanto paisagem. Observação Após a última atualização do plugin ADT do Eclipse, ele acusou alguns ‘warnings‘ nos layouts XML. Por enquanto não se preocupem com isso! Integrando Bibliotecas de Terceiros (Twitter) Veremos neste tutorial como integrar uma biblioteca externa ao nosso aplicativo em Android. Através dela, vamos vincular uma conta do Twitter ao restaurante e poderemos obter os últimos tweets referentes àquele restaurante. Para o acesso ao Twitter, utilizaremos a biblioteca twitter4j, que nos fornece acesso completo aos recursos da rede social. No tutorial vamos utilizar a versão 2.2.5 otimizada para Android (twitter4j-android-2.2.5), ou superior. O primeiro passo é adicionar a conta do Twitter ao nosso modelo de dados. Isso implica em modificar a classe de persistência GerenciadorRestaurantes. Comece alterando o método onCreate() para abrigar o novo campo no banco de dados: 18 @Override 19 public void onCreate(SQLiteDatabase db) { 20 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 21 " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT);"); 22 } Além disso, vamos alterar a versão do Schema do banco, para que ele seja atualizado em versões anteriores: 12 private static final int VERSAO_SCHEMA = 2; Precisamos também atualizar o nosso método onUpdate(), que antes não fazia nada. Ele será executado se o usuário possuir a versão antiga do banco. Nesse caso, iremos adicionar a nova coluna twitter a tabela restaurantes. 24 @Override 25 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 26 db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT"); 27 } Para concluir as modificações nesta classe, vamos atualizar os método obterTodos(), obterPorId(), inserir() e atualizar(). Além disso, também vamos adicionar o método obterTwitter(): 29 public void inserir(String nome, String endereco, String tipo, String anotacoes, String twitter) { 30 ContentValues valores = new ContentValues(); 31 32 valores.put("nome", nome); 33 valores.put("endereco", endereco); 34 valores.put("tipo", tipo); 35 valores.put("anotacoes", anotacoes); 36 valores.put("twitter", twitter); 37 38 getWritableDatabase().insert("restaurantes", "nome", valores); 39 } 40 41 public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes, String twitter) { 42 ContentValues valores = new ContentValues(); 43 String[] argumentos = {id}; 44 45 valores.put("nome", nome); 46 valores.put("endereco", endereco); 47 valores.put("tipo", tipo); 48 valores.put("anotacoes", anotacoes); 49 valores.put("twitter", twitter); 50 51 getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos); 52 } 53 54 public Cursor obterTodos(String ordenacao) { 55 return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " + 56 "anotacoes, twitter FROM restaurantes ORDER BY " + ordenacao, null); 57 } 58 59 public String obterTwitter(Cursor c) { 60 return c.getString(5); 61 } 62 63 public Cursor obterPorId(String id) { 64 String[] argumentos = {id}; 65 66 return getReadableDatabase().rawQuery( 67 "SELECT _id, nome, endereco, tipo, anotacoes, twitter " + 68 "FROM restaurantes WHERE _id = ?", argumentos); 69 } Pronto. Com relação aos dados do aplicativo, já estamos prontos. Vamos agora ajustar o formulário de detalhes, adicionando o campo Twitter em ambos. Lembre-se que, como temos duas versões deste formulário (res/layout e res/layout-land), precisaremos fazer a modificação em ambos. Primeiramente o formulário para modo retrato (res/layout/form_detalhes.xml): 1 2 6 7 8 9 10 11 12 13 14 15 16 17 19 21 23 24 25 33 35 37 android:layout_height="wrap_content" 38 android:text="Salvar"/> 39 Basicamente, a única modificação é a adição do campo twitter e a remoção do TextView do campo anotações, exibido agora como o atributo hint. A mesma coisa fazemos no formulário do modo paisagem (res/layout- land/form_detalhes.xml): 1 2 6 7 8 10 11 12 13 15 16 17 18 19 21 23 25 26 27 31 41 45 49 50 51 Agora, iremos adicionar o atributo twitter na classe FormularioDetalhes. 24 EditText twitter = null; Em seguida, no método onCreate(), obtemos o valor do formulário e o aplicamos ao atributo da classe: 39 twitter = (EditText) findViewById(R.id.twitter); E no método carregar(), configuramos o texto do EditText: 130 twitter.setText(gerenciador.obterTwitter(c)); Finalmente, vamos modificar as chamadas aos métodos de persistência lá no onSave: 58 private OnClickListener onSave = new OnClickListener() { 59 60 public void onClick(View arg0) { 61 String tipo = null; 62 63 switch (tipos.getCheckedRadioButtonId()) { 64 case R.id.rodizio: 65 tipo = "rodizio"; 66 break; 67 case R.id.fast_food: 68 tipo = "fast_food"; 69 break; 70 case R.id.a_domicilio: 71 tipo = "a_domicilio"; 72 break; 73 } 74 75 if (idRestaurante == null) { 76 gerenciador.inserir(nome.getText().toString(), 77 endereco.getText().toString(), 78 tipo, anotacoes.getText().toString(), 79 twitter.getText().toString()); 80 } else { 81 gerenciador.atualizar(idRestaurante, 82 nome.getText().toString(), 83 endereco.getText().toString(), 84 tipo, anotacoes.getText().toString(), 85 twitter.getText().toString()); 86 } 87 88 finish(); 89 } 90 }; Acompanharam? O próximo passo é adicionar a opção Twitter ao menu. Crie o novo arquivo opcao_detalhes.xml e o salve em res/menu. O ícone tem pra download junto com o projeto lá no final do tutorial. 1 2 3 5 android:icon="@drawable/twitter"/> 6 Em seguida, adicione o método onCreateOptionsMenu() a classe FormularioDetalhes. 92 @Override 93 public boolean onCreateOptionsMenu(Menu menu) { 94 new MenuInflater(this).inflate(R.menu.opcao_detalhes, menu); 95 96 return super.onCreateOptionsMenu(menu); 97 }; Como vamos utilizar a conexão de rede do celular com a Internet, precisamos verificar se essa conexão está disponível. Vamos então criar os métodos redeDisponivel() e o método onOptionsItemSelected() lá na classe FormularioDetalhes: 99 @Override 100 public boolean onOptionsItemSelected(MenuItem item) { 101 if (item.getItemId() == R.id.twitter) { 102 if (redeDisponivel()) { 103 Intent i = new Intent(this, TwitterActivity.class); 104 i.putExtra(TwitterActivity.PERFIL, twitter.getText().toString()); 105 startActivity(i); 106 } else { 107 Toast.makeText(this, "Conexão com a Internet indisponível", Toast.LENGTH_LONG).show(); 108 } 109 110 return true; 111 } 112 113 return super.onOptionsItemSelected(item); 114 } 115 116 private boolean redeDisponivel() { 117 ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 118 NetworkInfo info = cm.getActiveNetworkInfo(); 119 120 return (info != null); 121 } Ao inserir estes dois métodos, o Eclipse vai chiar pela ausência da classe TwitterActivity. Calma! Já já resolveremos isso. Também precisaremos adicionar ao AndroidManifest.xml que nosso aplicativo precisa de permissão para acessar a rede. Assim, adicione as duas linhas seguintes ao arquivo, antes do nó application. 9 10 Agora vamos começar a mexer com a obtenção dos tweets propriamente ditos. Para adicionar a biblioteca (dentro do arquivo Zip baixado, vamos adicionar o arquivo twitter4j-core-android-2.2.5.jar (ou superior) que se encontra dentro da pasta lib), clique com o botão direito sobre o projeto, selecione Properties, clique em Java Build Path, aba Libraries e clique no botão Add External JARs…. Localize o arquivo, e confirme. Crie uma nova classe no pacote com.blogspot.flavioaf.restaurante chamada TwitterActivity estendendo ListActivity. Em seguida, adicione o mapeamento desta Activity no AndroidManifest.xml: 28 29 Agora, crie uma classe interna a classe TwitterActivity chamada TarefaTwitter: 21 private static class TarefaTwitter extends AsyncTask { 22 private Twitter t = new TwitterFactory().getInstance(); 23 private Exception ex = null; 24 private TwitterActivity activity = null; 25 26 TarefaTwitter(TwitterActivity activity) { 27 anexar(activity); 28 } 29 30 void anexar(TwitterActivity activity) { 31 this.activity = activity; 32 } 33 34 void desanexar() { 35 activity = null; 36 } 37 38 @Override 39 protected List doInBackground(String... params) { 40 List resultado = null; 41 42 try { 43 resultado = t.getUserTimeline(params[0]); 44 } catch (Exception ex) { 45 this.ex = ex; 46 } 47 48 return resultado; 49 } 50 51 @Override 52 public void onTutorialExecute(List result) { 53 if (ex == null) { 54 activity.atribuirTweets(result); 55 } else { 56 Log.e("ListaRestaurantes", "Erro manipulando timeline twitter", ex); 57 activity.atirarErro(ex); 58 } 59 } 60 } Esta classe é a responsável por carregar os tweets da conta selecionada e enviá-los para a Activity (carregamento na linha 43 e atribuição para a Activity na linha 54). Sempre lembrando de corrigir os imports com o Ctrl + Shift + O Prosseguindo, vamos implementar os métodos da classe TwitterActivity referenciados pela classe TarefaTwitter. Primeiramente, vamos com o atirarErro(): 21 private void atirarErro(Throwable t) { 22 Builder builder = new Builder(this); 23 builder.setTitle("Erro!").setMessage(t.toString()).setPositiveButton("OK", null).show(); 24 } Por fim, vamos fazer com que os tweets obtidos sejam exibidos. Dentro da TwitterActivity, vamos criar a classe AdaptadorTweets que será responsável por adaptar os nossos tweets para a exibição. 58 private class AdaptadorTweets extends BaseAdapter { 59 List status = null; 60 61 AdaptadorTweets(List status) { 62 super(); 63 this.status = status; 64 } 65 66 public int getCount() { 67 return status.size(); 68 } 69 70 public Object getItem(int position) { 71 return status.get(position); 72 } 73 74 public long getItemId(int position) { 75 return position; 76 } 77 78 public View getView(int position, View convertView, ViewGroup parent) { 79 View linha = convertView; 80 81 if (linha == null) { 82 LayoutInflater inflater = getLayoutInflater(); 83 linha = inflater.inflate(android.R.layout.simple_list_item_1, parent, false); 84 } 85 86 Status item = (Status) getItem(position); 87 ((TextView) linha).setText(item.getText()); 88 89 return linha; 90 } 91 } Com este adaptador, vamos exibir os tweets em uma lista comum (android.R.layout.simple_list_item_1). Para gerenciar as instâncias da TarefaTwitter e da lista de tweets, vamos criar uma classe interna StatusInstance: 139 private static class StatusInstancia { 140 List tweets = null; 141 TarefaTwitter tarefa = null; 142 } Em seguida adicionamos um objeto desta classe como atributo da classe TwitterActivity: 22 private StatusInstancia status = null; E implemente os métodos onCreate(), onRetainNonConfigurationInstance() e atribuirTweets(). 24 @Override 25 public void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 28 status = (StatusInstancia) getLastNonConfigurationInstance(); 29 30 if (status == null) { 31 status = new StatusInstancia(); 32 status.tarefa = new TarefaTwitter(this); 33 status.tarefa.execute(getIntent().getStringExtra(PERFIL)); 34 } else { 35 if (status.tarefa != null) { 36 status.tarefa.anexar(this); 37 } 38 39 if (status.tweets != null) { 40 atribuirTweets(status.tweets); 41 } 42 } 43 } 44 45 @Override 46 public Object onRetainNonConfigurationInstance() { 47 if (status.tarefa != null) { 48 status.tarefa.desanexar(); 49 } 50 return status; 51 } 52 53 private void atribuirTweets(List tweets) { 54 status.tweets = tweets; 55 setListAdapter(new AdaptadorTweets(tweets)); 56 } E no final, vamos criar o atributo PERFIL na classe: 21 public static final String PERFIL = "com.blogspot.flavioaf.PERFIL"; E prontinho! Cadastre um restaurante e adicione uma conta de Twitter! Utilizando um IntentService No tutorial anterior, utilizamos uma AsyncTask para recuperar o conteúdo do Twitter. Isso foi necessário para que pudéssemos obter comunicação com a rede fora da thread principal do aplicativo e, portanto, evitar lentidão na interface. Outra forma de resolver esse problema é usando um IntentService. Um IntentService é um componente à parte que aceita comandos vindos de uma Activity, executa os comandos em linhas em background e, opcionalmente, responde às atividades ou o usuário. Neste tutorial, vamos configurar um IntentService como um substituto para a AsyncTask. Primeiramente, crie uma nova classe no pacote com.blogspot.flavioaf.restaurante chamada TwitterService, estendendo IntentService: 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.app.IntentService; 4 import android.content.Intent; 5 6 public class TwitterService extends IntentService { 7 8 public TwitterService() { 9 super("TwitterService"); 10 } 11 12 @Override 13 protected void onHandleIntent(Intent intent) { 14 15 } 16 } Em seguida, vamos adicionar um novo nó service lá no arquivo AndroidManifest.xml logo após os nós activity, dentro do nó application. 29 30 O método onHandleIntent() da IntentService é chamado sempre em background, razão principal de a utilizarmos. Vamos começar com uma implementação inicial deste método lá na nossa classe TwitterService, importando parte da lógica que tínhamos no método doInBackground(): 18 @Override 19 protected void onHandleIntent(Intent intent) { 20 Twitter t = new TwitterFactory().getInstance(); 21 22 try { 23 List resultado = t.getUserTimeline(intent.getStringExtra(PERFIL_EXTRA)); 24 } catch (Exception ex) { 25 Log.e("ListaRestaurantes", "Erro manipulando timeline twitter", ex); 26 } 27 } Adicione também o atributo da classe referenciado no método, que servirá para obter o perfil do Twitter que obteremos os tweets. 14 public static final String PERFIL_EXTRA = "com.blogspot.flavioaf.PERFIL_EXTRA"; Continuando, precisamos agora enviar os tweets para a Activity. Para realizar a comunicação, utilizaremos um Messenger, que servirá para obtermos informações do serviço. Dessa forma, atualize a implementação do método onHandleIntent(): 23 @Override 24 protected void onHandleIntent(Intent intent) { 25 Twitter t = new TwitterFactory().getInstance(); 26 Messenger messenger = (Messenger) intent.getExtras().get(MESSENGER_EXTRA); 27 Message msg = Message.obtain(); 28 29 try { 30 List resultado = t.getUserTimeline(intent.getStringExtra(PERFIL_EXTRA)); 31 32 msg.arg1 = Activity.RESULT_OK; 33 msg.obj = resultado; 34 } catch (Exception ex) { 35 Log.e("ListaRestaurantes", "Erro manipulando timeline twitter", ex); 36 msg.arg1 = Activity.RESULT_CANCELED; 37 msg.obj = ex; 38 } 39 40 try { 41 messenger.send(msg); 42 } catch (Exception ex) { 43 Log.w("ListaRestaurantes", "Erro enviando dados para a Activity", ex); 44 } 45 } Para completar, só precisamos adicionar o atributo MESSENGER_EXTRA a nossa classe. 18 public static final String MESSENGER_EXTRA = "com.blogspot.flavioaf.MESSENGER_EXTRA"; Por fim, vamos fazer as modificações na TwitterActivity para que ela trabalhe com o TwitterService em vez da TarefaTwitter. Primeiramente, vamos converter a nossa TarefaTwitter para HandlerTwitter, que estenderá Handler em vez de AsyncTask. Os métodos anexar() e desanexar() serão mantidos para gerenciar as mudanças na configuração. Já o método doInBackground() será removido, já que a lógica foi movida para o serviço. O método onTutorialExecute() vira handleMessage(), para pegar o objeto Message do TwitterService, chamando os métodos atribuirTweets() ou atirarErro() dependendo do retorno do serviço. O resultado será esse: 1 private static class HandlerTwitter extends Handler { 2 private TwitterActivity activity = null; 3 4 HandlerTwitter(TwitterActivity activity) { 5 anexar(activity); 6 } 7 8 void anexar(TwitterActivity activity) { 9 this.activity = activity; 10 } 11 12 void desanexar() { 13 activity = null; 14 } 15 16 @Override 17 public void handleMessage(Message msg) { 18 if (msg.arg1 == RESULT_OK) { 19 activity.atribuirTweets((List) msg.obj); 20 } else { 21 activity.atirarErro((Exception) msg.obj); 22 } 23 } Como não temos mais a TarefaTwitter, não precisamos mais dele no StatusInstancia. Porém, precisamos guardar nosso Handler como parte de nosso status, de forma que quando o usuário rotacionar a tela, nosso objeto Messenger ainda possa comunicar-se corretamente com a TwitterActivity. Assim, modifique a classe StatusInstancia: 122 private static class StatusInstancia { 123 List tweets = null; 124 HandlerTwitter handler = null; 125 } Assim, também precisaremos modificar o método onRetainNonConfigurationInstance() para acomodar o Handler em vez da tarefa. 44 @Override 45 public Object onRetainNonConfigurationInstance() { 46 if (status.handler != null) { 47 status.handler.desanexar(); 48 } 49 return status; 50 } Por fim, vamos modificar o método onCreate() para trabalhar com o TwitterService, criando o Messenger caso o status seja nulo, ou anexando-o caso já exista: 25 @Override 26 public void onCreate(Bundle savedInstanceState) { 27 super.onCreate(savedInstanceState); 28 29 status = (StatusInstancia) getLastNonConfigurationInstance(); 30 31 if (status == null) { 32 status = new StatusInstancia(); 33 status.handler = new HandlerTwitter(this); 34 35 Intent i = new Intent(this, TwitterService.class); 36 i.putExtra(TwitterService.PERFIL_EXTRA, getIntent().getStringExtra(PERFIL)); 37 i.putExtra(TwitterService.MESSENGER_EXTRA, new Messenger(status.handler)); 38 39 startService(i); 40 } else { 41 if (status.handler != null) { 42 status.handler.anexar(this); 43 } 44 45 if (status.tweets != null) { 46 atribuirTweets(status.tweets); 47 } 48 } 49 } Pronto! Já podemos executar novamente o aplicativo, mas nenhuma mudança deve ser percebida. Se nenhuma mudança é percebida, por que tudo isso? Bem, pode ser que neste caso a diferença não seja visível, mas imagine que em vez dos tweets fôssemos baixar um vídeo. Utilizando o IntentService, a operação ocorre sem estar vinculada a nenhuma Activity. Ou seja, o usuário não precisa ficar esperando o download terminar para continuar. O IntentService fará o download por si próprio e se auto-destruirá quando terminar. Integrando o GPS Depois de fazer nosso aplicativo de lista de restaurantes buscar tweets, vamos agora utilizar outro recurso presente nos smartphones: o GPS. Neste tutorial, faremos com que, no momento em que o restaurante for cadastrado, ele guarde a sua localização (contando que o cadastro esteja sendo feito no próprio restaurante) e salve junto ao registro daquele restaurante. Interessante, não? Pra começar, vamos adicionar os dados de latitude e longitude em nosso modelo de dados. Primeiramente, modifique o método onCreate da classe GerenciadorRestaurantes. 18 @Override 19 public void onCreate(SQLiteDatabase db) { 20 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 21 " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT," + 22 " latitude REAL, longitude REAL);"); 23 } Tal modificação exige que atualizemos a versão do schema do banco de dados: 12 private static final int VERSAO_SCHEMA = 3; Na hora de modificarmos o método onUpdate() devemos ficar atentos. Precisamos prepará-lo para atualizar bancos tanto com o schema 1 quanto com o schema 2. 25 @Override 26 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 27 if (oldVersion < 2) { 28 db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT"); 29 } 30 31 if (oldVersion < 3) { 32 db.execSQL("ALTER TABLE restaurantes ADD COLUMN latitude REAL"); 33 db.execSQL("ALTER TABLE restaurantes ADD COLUMN longitude REAL"); 34 } 35 } Precisaremos atualizar também os métodos obterTodos() e obterPorId() para abranger também os novos campos. 62 public Cursor obterTodos(String ordenacao) { 63 return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " + 64 "anotacoes, twitter, latitude, longitude FROM restaurantes ORDER BY " + 65 ordenacao, null); 66 } 67 68 public Cursor obterPorId(String id) { 69 String[] argumentos = {id}; 70 71 return getReadableDatabase().rawQuery( 72 "SELECT _id, nome, endereco, tipo, anotacoes, twitter, latitude," + 73 " longitude FROM restaurantes WHERE _id = ?", argumentos); 74 } Como não vamos inserir diretamente as coordenadas de latitude e longitude diretamente (pedir pro usuário digitar é picardia, né?), não precisaremos nos preocupar com os métodos inserir() e atualizar(). Criaremos um método atualizarLocalizacao(). 62 public void atualizarLocalizacao(String id, double latitude, double longitude) { 63 ContentValues cv = new ContentValues(); 64 String[] args = {id}; 65 66 cv.put("latitude", latitude); 67 cv.put("longitude", longitude); 68 69 getWritableDatabase().update("restaurantes", cv, "_ID = ?", args); 70 } O método obviamente só funcionará para restaurantes que já existam no banco de dados, restrições que iremos aplicar através da interface. Por fim, vamos criar os dois métodos para obter os valores de latitude e longitude de um Cursor lido do banco. 98 public double obterLatitude(Cursor c) { 99 return c.getDouble(6); 100 } 101 102 public double obterLongitude(Cursor c) { 103 return c.getDouble(7); 104 } Precisamos agora adicionar um lugar para exibir as coordenadas GPS na tela. Na nossa interface, uma porção considerável da tela é ocupada pelo botão Salvar. Na maioria das interfaces, este botão não está presente. Assim, temos duas abordagens para resolver a situação: (1) Adicionar uma opção no menu para salvar ou; (2) Salvar automaticamente quando a Activity passar para o estado de pausa No caso, iremos utilizar a segunda abordagem, para salvar sempre que o usuário pressionar a tecla voltar ou home. Para isso, precisaremos nos desfazer de todas as referências ao botão salvar na classe FormularioDetalhes. Para começar, vamos converter o objeto onSave para um método chamado salvar(). 58 private void salvar() { 59 String tipo = null; 60 61 switch (tipos.getCheckedRadioButtonId()) { 62 case R.id.rodizio: 63 tipo = "rodizio"; 64 break; 65 case R.id.fast_food: 66 tipo = "fast_food"; 67 break; 68 case R.id.a_domicilio: 69 tipo = "a_domicilio"; 70 break; 71 } 72 73 if (idRestaurante == null) { 74 gerenciador.inserir(nome.getText().toString(), 75 endereco.getText().toString(), 76 tipo, anotacoes.getText().toString(), 77 twitter.getText().toString()); 78 } else { 79 gerenciador.atualizar(idRestaurante, 80 nome.getText().toString(), 81 endereco.getText().toString(), 82 tipo, anotacoes.getText().toString(), 83 twitter.getText().toString()); 84 } 85 86 finish(); 87 } Remova as linhas que fazem referência ao botão no método onCreate() e implemente o método onPause() que faz a chamada ao método salvar(). 48 @Override 49 public void onPause() { 50 salvar(); 51 52 super.onPause(); 53 } O próximo passo é modificar o layout do formulário, removendo o botão salvar e adicionando a opção de menu para obter a localização do GPS. Primeiramente, vamos modificar o layout para o modo retrato (res/layout/form_detalhes.xml). 1 2 6 7 8 9 10 11 12 13 14 15 16 17 19 21 23 24 25 26 27 28 29 34 android:maxLines="2" 35 android:maxWidth="200sp" 36 android:hint="Anotações" 37 android:layout_marginTop="4dip"/> 38 41 E em seguida o layout para a versão paisagem (res/layout-land/form_detalhes.xml). 1 2 6 7 8 10 11 12 13 15 16 17 18 19 21 23 25 26 30 40 44 48 51 55 56 57 58 Na classe FormularioDetalhes, adicione o atributo localizacao. 24 TextView localizacao = null; No método onCreate(), adicione a linha seguinte para obter o item do formulário. 40 localizacao = (TextView) findViewById(R.id.localizacao); Em seguida, no método carregar(), obtemos os valores do GerenciadorRestaurantes e o colocamos no formulário. 125 private void carregar() { 126 Cursor c = gerenciador.obterPorId(idRestaurante); 127 128 c.moveToFirst(); 129 nome.setText(gerenciador.obterNome(c)); 130 endereco.setText(gerenciador.obterEndereco(c)); 131 anotacoes.setText(gerenciador.obterAnotacoes(c)); 132 twitter.setText(gerenciador.obterTwitter(c)); 133 134 if (gerenciador.obterTipo(c).equals("rodizio")) { 135 tipos.check(R.id.rodizio); 136 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 137 tipos.check(R.id.fast_food); 138 } else { 139 tipos.check(R.id.a_domicilio); 140 } 141 142 localizacao.setText(String.valueOf(gerenciador.obterLatitude(c)) + 143 ", " + String.valueOf(gerenciador.obterLongitude(c))); 144 145 c.close(); 146 } Também precisamos adicionar no menu a opção para obter a localização. Modifique o arquivo de menu em res/menu/opcao_detalhes.xml. 1 2 3 6 9 O ícone pode ser encontrado lá na pasta do SDK sob o nome de ic_menu_compass.png (renomeado para gps.png). Em seguida, vamos adicionar a permissão de acessar a localização, através do arquivo AndroidManifest.xml. Adicione a seguinte linha junto às outras permissões: 11 O próximo passo é obter a localização de fato. Primeiramente, adicione o atributo locationManager na classe FormularioDetalhes: 28 LocationManager locationManager = null; Em seguida, adicione a sua localização no método onCreate(): 50 locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); Agora, precisamos fazer a chamada ao método requestLocationUpdates() da classe LocationManager para pedir a localização quando o usuário selecionar o botão no menu. Dessa forma, modifique o método onOptionsItemSelected(): 105 @Override 106 public boolean onOptionsItemSelected(MenuItem item) { 107 if (item.getItemId() == R.id.twitter) { 108 if (redeDisponivel()) { 109 Intent i = new Intent(this, TwitterActivity.class); 110 i.putExtra(TwitterActivity.PERFIL, twitter.getText().toString()); 111 startActivity(i); 112 } else { 113 Toast.makeText(this, "Conexão com a Internet indisponível", Toast.LENGTH_LONG).show(); 114 } 115 116 return true; 117 } else if (item.getItemId() == R.id.localizacao) { 118 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 119 0, 0, onLocationChange); 120 } 121 122 return super.onOptionsItemSelected(item); 123 } Como pode ser percebido, adicionamos um listener para obter o resultado a chamada. Por isso, vamos implementar o objeto onLocationChange como um atributo da classe FormularioDetalhes: 177 LocationListener onLocationChange = new LocationListener() { 178 179 public void onLocationChanged(Location location) { 180 gerenciador.atualizarLocalizacao(idRestaurante, location.getLatitude(), 181 location.getLongitude()); 182 localizacao.setText(String.valueOf(location.getLatitude()) + ", " + 183 String.valueOf(location.getLongitude())); 184 locationManager.removeUpdates(onLocationChange); 185 186 Toast.makeText(FormularioDetalhes.this, "Localização salva", Toast.LENGTH_LONG); 187 } 188 189 public void onProviderDisabled(String provider) { 190 // Requerido pela interface. Não utilizado 191 } 192 193 public void onProviderEnabled(String provider) { 194 // Requerido pela interface. Não utilizado 195 } 196 197 public void onStatusChanged(String provider, int status, Bundle extras) { 198 // Requerido pela interface. Não utilizado 199 } 200 }; No caso, obtemos os dados do GPS, atualizamos o banco e a interface e exibimos uma mensagem para o usuário. Porém, pode ocorrer do usuário sair da tela enquanto a requisição estiver sendo processada. Neste caso, é sensato cancelá-la para evitar problemas. Assim, atualize o método onPause(): 55 @Override 56 public void onPause() { 57 salvar(); 58 locationManager.removeUpdates(onLocationChange); 59 60 super.onPause(); 61 } Por fim, precisamos controlar para somente exibir a opção de obter os dados do GPS se o restaurante já tiver sido salvo no banco de dados. Adicione a implementação do método onPrepareOptionsMenu(): 69 @Override 70 public boolean onPrepareOptionsMenu(Menu menu) { 71 if (idRestaurante == null) { 72 menu.findItem(R.id.localizacao).setEnabled(false); 73 } 74 75 return super.onPrepareOptionsMenu(menu); 76 } Pront! Vamos testar? Os dois primeiros screenshots foram feitos no emulador e o último foi feito usando um celular real (LG Optimus One). Caso você não tenha um celular real para testar, você pode emular coordenadas GPS conectando via telnet no localhost porta 5554. No console que aparecer, digite o comando: 1 geo fix Onde e devem ser os valores desejados (você consegue pegar os valores de um lugar facilmente usando o Google Maps). Alterando o Ícone do Launcher Esse tutorial é curtinho. Nele vamos modificar o ícone do launcher do nosso aplicativo e corrigir um bug que tínhamos até então (que fazia o aplicativo encerrar inesperadamente e não funcionava novamente caso fosse pressionada a tecla voltar no formulário sem preencher nada – provavelmente você nem tenha percebido). Primeiramente, para alterar o ícone, devemos encontrar um ícone no formato PNG com dimensões de 72x72px. A Google possui algumas orientações de design para a confecção deste ícone. É sempre bom seguir tais recomendações caso você venha a disponibilizar seu aplicativo na Play Store (antigo Android Market). Para ler sobre o assunto, basta clicar aqui e aqui. Como no nosso caso é apenas um tutorial, encontre um ícone qualquer que lhe agrade, renomeie-o para ic_launcher.png e coloque-o na pasta res/drawable. E… pronto! Caso, por algum motivo, você queria colocar o ícone com outro nome, pode utilizá-lo alterando a propriedade android:icon do nó application no arquivo AndroidManifest.xml. Bom, e quanto ao nosso “bug”, o que ocorre é que, caso não preenchemos nada e apertamos a tecla voltar, o aplicativo tenta salvar valores nulos no banco de dados, o que acaba ocasionando uma exceção (exception). Para corrigir, basta controlar para que a gravação ou atualização não seja feita caso os valores básicos (nome, endereço e tipo) não sejam preenchidos. Assim, altere o método salvar() da classe FormularioDetalhes. 82 private void salvar() { 83 String tipo = null; 84 85 switch (tipos.getCheckedRadioButtonId()) { 86 case R.id.rodizio: 87 tipo = "rodizio"; 88 break; 89 case R.id.fast_food: 90 tipo = "fast_food"; 91 break; 92 case R.id.a_domicilio: 93 tipo = "a_domicilio"; 94 break; 95 } 96 97 if (tipo != null && endereco.getText().toString() != null && 98 nome.getText().toString() != null) { 99 100 if (idRestaurante == null) { 101 gerenciador.inserir(nome.getText().toString(), 102 endereco.getText().toString(), 103 tipo, anotacoes.getText().toString(), 104 twitter.getText().toString()); 105 } else { 106 gerenciador.atualizar(idRestaurante, 107 nome.getText().toString(), 108 endereco.getText().toString(), 109 tipo, anotacoes.getText().toString(), 110 twitter.getText().toString()); 111 } 112 } 113 114 finish(); 115 } Pronto! Integrando com o Google Maps Vamos aproveitar as coordenadas do GPS obtidas com o tutorial do GPS e inseri-las em um mapa provido pelo Google Maps. Portanto, precisaremos que ele esteja instalado no emulador ou no seu celular para tal funcionalidade. Além disso, você também precisará de uma chave de desenvolvimento para que possa ocorrer a integração. Neste link, você obtém as informações necessárias sobre como conseguir esta chave. Começando, vamos adicionar a opção para a visualização do mapa no FormularioDetalhes. Dessa forma, modifique o arquivo res/menu/opcao_detalhes.xml: 1 2 3 6 9 12 O ícone do menu (mapa.png) é o ícone ic_menu_mapmode.png, devidamente renomeado, que pode ser encontrado lá na pasta da sua instalação do Android SDK. Em seguida, vamos modificar o método onPrepareOptionsMenu() no FormularioDetalhes para somente habilitar esta opção quando já houverem as informações básicas do restaurante (edição). 75 @Override 76 public boolean onPrepareOptionsMenu(Menu menu) { 77 if (idRestaurante == null) { 78 menu.findItem(R.id.localizacao).setEnabled(false); 79 menu.findItem(R.id.mapa).setEnabled(false); 80 } 81 82 return super.onPrepareOptionsMenu(menu); 83 } O próximo passo é criar uma Activity que será responsável por exibir o nosso mapa. Porém, antes disso, vamos informar ao arquivo AndroidManifest.xml que nossa aplicação fará uso da API do Google Maps. Dessa forma, dentro do nó application, adicione a seguinte linha: 16 Em seguida, vamos criar uma nova classe no pacote principal do projeto (com.blogspot.flavioaf.restaurante), com o nome de MapaRestaurante. Esta classe estenderá MapActivity. Inicialmente teremos o método onCreate(), onde simplesmente atribuiremos seu layout, e isRouteDisplayed(), método abstrato exigido. Neste segundo, por ora simplesmente retornaremos falso. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.os.Bundle; 4 import com.google.android.maps.MapActivity; 5 6 public class MapaRestaurante extends MapActivity { 7 8 @Override 9 public void onCreate(Bundle icicle) { 10 super.onCreate(icicle); 11 setContentView(R.layout.mapa); 12 } 13 14 @Override 15 protected boolean isRouteDisplayed() { 16 return false; 17 } 18 } O próximo passo é modificar o método onOptionsItemSelected() para que inicie a Activity MapaRestaurante. @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.twitter) { if (redeDisponivel()) { Intent i = new Intent(this, TwitterActivity.class); i.putExtra(TwitterActivity.PERFIL, twitter.getText().toString()); startActivity(i); } else { Toast.makeText(this, “Conexão com a Internet indisponível”, Toast.LENGTH_LONG Alarmes Veremos como criar um alarme para nos avisar a hora do almoço (tudo a ver com o aplicativo de restaurante, não?). O primeiro passo é criarmos uma forma para o usuário configurar o horário em que ele deseja ser avisado do almoço. Poderíamos definir isto em uma Activity, mas esta opção soa mais como uma configuração. Dessa forma, vamos criar a classe PreferenciaHorario no pacote com.blogspot.flavioaf.restaurante, estendendo a classe DialogPreference. Logo em seguida explicarei os conceitos principais dela. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.content.Context; 4 import android.content.res.TypedArray; 5 import android.preference.DialogPreference; 6 import android.util.AttributeSet; 7 import android.view.View; 8 import android.widget.TimePicker; 9 10 public class PreferenciaHorario extends DialogPreference { 11 12 private int ultimaHora = 0; 13 private int ultimoMinuto = 0; 14 private TimePicker picker = null; 15 16 public static int obterHora(String tempo) { 17 String[] fragmentos = tempo.split(":"); 18 return Integer.parseInt(fragmentos[0]); 19 } 20 21 public static int obterMinuto(String tempo) { 22 String[] fragmentos = tempo.split(":"); 23 return Integer.parseInt(fragmentos[1]); 24 } 25 26 public PreferenciaHorario(Context contexto) { 27 this(contexto, null); 28 } 29 30 public PreferenciaHorario(Context contexto, AttributeSet atributos) { 31 this(contexto, atributos, 0); 32 } 33 34 public PreferenciaHorario(Context contexto, AttributeSet atributos, int estilo) { 35 super(contexto, atributos, estilo); 36 37 setPositiveButtonText("Definir"); 38 setNegativeButtonText("Cancelar"); 39 } 40 41 @Override 42 protected View onCreateDialogView() { 43 picker = new TimePicker(getContext()); 44 return picker; 45 } 46 47 @Override 48 protected void onBindDialogView(View view) { 49 super.onBindDialogView(view); 50 51 picker.setCurrentHour(ultimaHora); 52 picker.setCurrentMinute(ultimoMinuto); 53 } 54 55 @Override 56 protected void onDialogClosed(boolean positiveResult) { 57 super.onDialogClosed(positiveResult); 58 59 if (positiveResult) { 60 ultimaHora = picker.getCurrentHour(); 61 ultimoMinuto = picker.getCurrentMinute(); 62 63 String tempo = String.valueOf(ultimaHora) + ":" + String.valueOf(ultimoMinuto); 64 65 if (callChangeListener(tempo)) { 66 persistString(tempo); 67 } 68 } 69 } 70 71 @Override 72 protected Object onGetDefaultValue(TypedArray a, int index) { 73 74 return a.getString(index); 75 } 76 77 @Override 78 protected void onSetInitialValue(boolean restorePersistedValue, 79 Object defaultValue) { 80 String tempo = null; 81 82 if (restorePersistedValue) { 83 if (defaultValue == null) { 84 tempo = getPersistedString("00:00"); 85 } else { 86 tempo = getPersistedString(defaultValue.toString()); 87 } 88 } else { 89 tempo = defaultValue.toString(); 90 } 91 92 ultimaHora = obterHora(tempo); 93 ultimoMinuto = obterMinuto(tempo); 94 } 95 } Muita coisa? Vamos por partes. Os métodos obterHora() e obterMinuto() servem para extrair a parte inteira do horário que será armazenado como uma string “00:00“. Temos três versões do construtor da classe, que no final sempre referenciam o terceiro. Isso é devido à superclasse. Ainda no construtor, definimos os nomes dos botões na janela de configuração de horário. onCreateDialogView() devolve um objeto View com a tela criada. Poderíamos aqui definirmos um layout, mas simplesmente devolvemos um widget TimePicker. O método onBindDialogView() é chamado após o onCreateDialogView() é encarregado de preencher a caixa de diálogo. onDialogClose(), como o próprio nome diz, é chamado quando a janelinha é encerrada. Caso o usuário pressione o botão de confirmar (condição positiveResult), o valor é armazenado em SharedPreferences. O método onGetDefaultValue() é utilizado para a conversão interna do Android para o tipo do objeto. Por último, o método onSetInitialValue(), como o próprio nome diz, atribui um valor padrão. Ele verifica se há algum valor já salvo, ou padrão ou então atribui 00:00. O próximo passo é adicionar a opção para a configuração do alarme pelo usuário. Dessa forma, edite o arquivo preferencias.xml da seguinte forma: 1 2 7 android:entryValues="@array/opcoes_ordenacao" 8 android:dialogTitle="Escolha o modo de listagem" /> 9 13 19 A primeira opção adicionada, do tipo CheckBoxPreference não tem muito segredo… a segunda, foi a que definimos na classe PreferenciaH0rario. Configuramos seu valor padrão para 12:00 e definimos que ela depende da opção alarme, ou seja, ela só estará habilitada caso alarme também esteja habilitada. Neste projeto vamos utilizar o AlarmManager para gerenciar o nosso alarme. Porém, ele tem uma falha: toda vez que o celular é desligado, ao ligar novamente os alarmes não são configurados. Para resolver isso, vamos criar a classe ReceptorBoot para realizar essa configuração toda vez que o sistema for ligado. Crie-a no pacote com.blogspot.flavioaf.restaurante. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.content.BroadcastReceiver; 4 import android.content.Context; 5 import android.content.Intent; 6 7 public class ReceptorBoot extends BroadcastReceiver { 8 9 @Override 10 public void onReceive(Context context, Intent intent) { 11 12 } 13 } A tarefa do ReceptorBoot será realizado no método onReceive(). Por enquanto, coloque-o pra descansar. Já já voltamos nele. Prosseguindo, precisamos adicionar o nó no arquivo AndroidManifest.xml para que ele possa atuar no boot. Adicione-o ao final do nó application. 39 41 42 43 44 Além disso, adicione também a permissão para obter o sinal de boot completo do sistema. 12 Precisamos agora tratar as preferências do usuário para configurar o alarme. Quando o usuário ativar o checkbox do alarme, precisamos ativar o alarme no tempo selecionado. Quando o usuário modificar o alarme (por exemplo, para 11:00), devemos criar um novo alarme com o AlarmManager. Se ele desativar, precisamos cancelar o alarme existente. E, por fim, em um processo de boot, se o alarme estiver selecionado, precisamos criá-lo. Para fazer todo esse trabalho, adicione os seguintes métodos na classe ReceptorBoot. Para corrigir os imports, só lembrar do Ctrl + Shift + O. 20 public static void configurarAlarme(Context contexto) { 21 AlarmManager gerenciador = (AlarmManager) contexto.getSystemService(Context.ALARM_SERVICE); 22 Calendar cal = Calendar.getInstance(); 23 SharedPreferences preferencias = PreferenceManager.getDefaultSharedPreferences(contexto); 24 String horario = preferencias.getString("horario_alarme", "12:00"); 25 26 cal.set(Calendar.HOUR_OF_DAY, PreferenciaHorario.obterHora(horario)); 27 cal.set(Calendar.MINUTE, PreferenciaHorario.obterMinuto(horario)); 28 cal.set(Calendar.SECOND, 0); 29 cal.set(Calendar.MILLISECOND, 0); 30 31 if (cal.getTimeInMillis() < System.currentTimeMillis()) { 32 cal.add(Calendar.DAY_OF_YEAR, 1); 33 } 34 35 gerenciador.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 36 AlarmManager.INTERVAL_DAY, obterIntentPendente(contexto)); 37 } 38 39 public static void cancelarAlarme(Context contexto) { 40 AlarmManager gerenciador = (AlarmManager) contexto.getSystemService(Context.ALARM_SERVICE); 41 gerenciador.cancel(obterIntentPendente(contexto)); 42 } 43 44 private static PendingIntent obterIntentPendente(Context contexto) { 45 Intent i = new Intent(contexto, ReceptorAlarme.class); 46 return PendingIntent.getBroadcast(contexto, 0, i, 0); 47 } Também atualize o método onReceive(): 15 @Override 16 public void onReceive(Context context, Intent intent) { 17 configurarAlarme(context); 18 } Bem, no código listado acima, primeiramente, ao receber o sinal do boot (método onReceive()), configuramos o alarme, através do método configurarAlarme(). Neste método, obtemos o AlarmManager, e obtemos as preferências do usuário para o alarme (se existirem), e a montamos em um objeto do tipo Calendar. Caso alarme seja anterior ao horário atual, adicionamos um dia a ele e configuramos para repeti-lo diariamente. Já no método cancelarAlarme(), cancelamos o alarme vinculado ao contexto, obtendo o AlarmManager e obtendo um objeto PendingIntent (como se fosse uma tarefa pendente) com o método obterIntentPendente(). No código que temos até agora, o alarme só é armado na inicialização do sistema. Para que ele funcione da maneira como desejamos, precisamos adicionar alguns método a classe EdicaoPreferencias: 22 @Override 23 protected void onResume() { 24 super.onResume(); 25 26 preferencias = PreferenceManager.getDefaultSharedPreferences(this); 27 preferencias.registerOnSharedPreferenceChangeListener(onChange); 28 } 29 30 @Override 31 protected void onPause() { 32 preferencias.unregisterOnSharedPreferenceChangeListener(onChange); 33 34 super.onPause(); 35 } 36 37 OnSharedPreferenceChangeListener onChange = new SharedPreferences.OnSharedPreferenceChangeListener() { 38 39 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 40 if ("alarme".equals(key)) { 41 boolean habilitado = preferencias.getBoolean(key, false); 42 int flag = (habilitado ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : 43 PackageManager.COMPONENT_ENABLED_STATE_DISABLED); 44 ComponentName componente = new ComponentName(EdicaoPreferencias.this, ReceptorBoot.class); 45 46 getPackageManager().setComponentEnabledSetting(componente, flag, PackageManager.DONT_KILL_APP); 47 48 if (habilitado) { 49 ReceptorBoot.configurarAlarme(EdicaoPreferencias.this); 50 } else { 51 ReceptorBoot.cancelarAlarme(EdicaoPreferencias.this); 52 } 53 } else if ("horario_alarme".equals(key)) { 54 ReceptorBoot.cancelarAlarme(EdicaoPreferencias.this); 55 ReceptorBoot.configurarAlarme(EdicaoPreferencias.this); 56 } 57 } 58 }; Lembre-se também de adicionar o membro privado da classe chamado preferencias: 13 SharedPreferences preferencias = null; O que nos falta fazer é criar um receptor que exiba o alarme na tela quando o alarme disparar. Para isso, primeiramente crie o arquivo alarme.xml na pasta res/layout: 1 2 Bastante simples, ele simplesmente exibirá bem grande na tela Hora do almoço!. Agora vamos criar a Activity que exibirá o aviso propriamente dito. Crie a classe AlarmeActivity no pacote com.blogspot.flavioaf.restaurante: 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 6 public class AlarmeActivity extends Activity { 7 8 @Override 9 protected void onCreate(Bundle savedInstanceState) { 10 super.onCreate(savedInstanceState); 11 setContentView(R.layout.alarme); 12 } 13 } Crie também uma classe chamada ReceptorAlarme que será encarregada de iniciar a AlarmeActivity. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.content.BroadcastReceiver; 4 import android.content.Context; 5 import android.content.Intent; 6 7 public class ReceptorAlarme extends BroadcastReceiver { 8 9 @Override 10 public void onReceive(Context context, Intent intent) { 11 Intent i = new Intent(context, AlarmeActivity.class); 12 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 13 context.startActivity(i); 14 } 15 } Para terminarmos falta somente adicionarmos esse último receptor no AndroidManifest.xml. Adicione-o no fim do nó application. 45 46 E é isso! Notificações No tutorial anterior vimos como utilizar alarmes em nossos aplicativos utilizando o AlarmManager. Porém, o alarme que definimos era exibido em tela cheia e, talvez, isso nem sempre é interessante. Agora veremos como adicionar a opção para o caso do usuário preferir uma notificação simples em vez de exibir ela em tela cheia. O primeiro passo é adicionarmos mais essa opção no menu de opções. Assim, edite o arquivo preferencias.xml para adicionar um novo CheckBoxPreference. 1 2 9 13 19 25 Por fim, vamos editar o método onReceive da classe ReceptorAlarme para realizar a exibição da notificação, caso esta opção esteja selecionada. 14 @Override 15 public void onReceive(Context context, Intent intent) { 16 SharedPreferences preferencias = PreferenceManager.getDefaultSharedPreferences(context); 17 boolean usarNotificacao = preferencias.getBoolean("usar_notificacao", true); 18 19 if (usarNotificacao) { 20 NotificationManager gerenciador = (NotificationManager) 21 context.getSystemService(Context.NOTIFICATION_SERVICE); 22 Notification nota = new Notification(R.drawable.notificacao, "Hora do Almoço!", System.currentTimeMillis()); 23 PendingIntent i = PendingIntent.getActivity(context, 0, new Intent(context, AlarmeActivity.class), 0); 24 nota.setLatestEventInfo(context, "Lista de Restaurantes", "Hora do Almoço! Está com fome?", i); 25 nota.flags |= Notification.FLAG_AUTO_CANCEL; 26 gerenciador.notify(ID_NOTIFICACAO, nota); 27 } else { 28 Intent i = new Intent(context, AlarmeActivity.class); 29 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 30 context.startActivity(i); 31 } Para essa operação, será necessário adicionarmos um atributo na classe, que é basicamente um número único para diferenciar esta notificação de outras que, eventualmente, venhamos utilizar em nosso aplicativo. O ícone referenciado no aplicativo é o ícone de notificação do GTalk, que pode ser obtido no diretório de instalação da SDK sob o nome de stat_notify_chat. Caso deseje utilizar outro, fique à vontade. E pronto! Já temos nosso aplicativo funcionando. Configure o alarme e realize seus testes. Internacionalização (i18n) Veremos como traduzir nossa aplicação para que ela se torne multi-linguagem. Isso é bastante interessante caso você tenha a intenção de colocar sua aplicação lá no Google Play. Bom, o grande “segredo” da internacionalização consiste na pasta values do projeto. Nessa pasta, no caso do nosso projeto, temos hoje o arquivo arrays.xml e strings.xml. Esses arquivos irão conter valores correspondentes a cada um dos idiomas que nossa aplicação suportará. Hoje, temos nossa aplicação com o idioma padrão Português. Como o emulador do Android 2.2 não tem o idioma português, vamos deixá-lo como padrão mesmo, e adicionar suporte ao idioma Espanhol1 Para início de conversa, vamos preparar nosso aplicativo para a internacionalização. Modifique o arquivo strings.xml para que contenha os seguintes valores: 1 2 3 Hello World, ListaRestaurantes! 4 Lista de Restaurantes 5 Nome: 6 Endereço: 7 Tipo: 8 Rodízio 9 Fast Food 10 A Domicílio 11 Localização 12 (não atribuída) 13 Anotações 14 Conta do Twitter 15 Hora do Almoço! 16 Descartado. 17 Conexão com a Internet indisponível 18 Localização salva 19 Definir 20 Cancelar 21 Hora do Almoço! Está com fome? 22 Erro manipulando timeline Twitter 23 Erro enviando dados para a Activity 24 Timeline Twitter 25 Salvar Localização 26 Exibir Mapa 27 Adicionar 28 Configurações 29 Modo de Listagem 30 Escolha o modo de listagem a ser utilizado 31 Escolha o modo de listagem 32 Tocar Alarme no Almoço 33 Marque se deseja ser informado sobre a hora do almoço 34 Horário do Alarme do Almoço 35 Configure seu horário desejado para o alarme 36 Ativar Notificação 37 Marque caso deseje um ícone na barra de status, ou desmarque para a notificação em tela cheia 38 Basicamente, definimos alguns alias para as strings do nosso aplicativo. Porém, em alguns trechos do código, ainda temos strings como constantes de texto. Para isso, vamos alterá-los para utilizar os resources deste arquivo. Primeiramente, nos formulários e menus em XML, vamos alterar os arquivos form_detalhes.xml, tanto na pasta layout… 1 As sentenças mostradas foram todas traduzidas com o auxílio do Google Tradutor e podem não estar totalmente corretas. 1 2 6 7 8 9 10 11 12 13 14 15 16 17 19 21 23 24 25 26 27 28 29 38 40 … quanto na pasta layout-land… 1 2 6 7 8 10 11 12 13 15 16 17 18 19 21 23 25 26 30 40 44 48 51 55 56 57 58 … além dos arquivos opcao.xml… 1 2 3 6 9 e opcao_detalhes.xml, da pasta menu… 1 2 3 6 9 10 android:title="@string/exibir_mapa" 11 android:icon="@drawable/mapa"/> 12 Nas nossas classes, também temos alguns trechos que precisam ser adaptados. Primeiramente na classe FormularioDetalhes… 61 Toast.makeText(FormularioDetalhes.this, getString(R.string.descartado), Toast.LENGTH_SHORT); 136 Toast.makeText(this, getString(R.string.sem_internet), Toast.LENGTH_LONG).show(); 219 Toast.makeText(FormularioDetalhes.this, getString(R.string.local_salvo), Toast.LENGTH_LONG); … na classe PreferenciaHorario… 37 setPositiveButtonText(getContext().getString(R.string.definir)); 38 setNegativeButtonText(getContext().getString(R.string.cancelar)); … na classe ReceptorAlarme… 23 Notification nota = new Notification(R.drawable.notificacao, context.getString(R.string.hora_do_almoco), System.currentTimeMillis()); 25 nota.setLatestEventInfo(context, context.getString(R.string.app_name), context.getString(R.string.notificacao), i); … e na classe TwitterService… 36 Log.e(getString(R.string.app_name), getString(R.string.erro_timeline), ex); 44 Log.w(getString(R.string.app_name), getString(R.string.erro_activity), ex); Caso a aplicação seja executada agora, ela deve rodar normalmente como rodava antes. Agora, vamos criar os arquivos relativos ao novo idioma. Crie um novo diretório no projeto chamado values-es na pasta res. Dentro dele, teremos 2 arquivos XML, arrays.xml… 1 2 3 4 Por Nombre, Ascendente 5 Por Nombre, Descendente 6 Por Tipo 7 Por Dirección, Ascendente 8 Por Dirección, Descendente 9 10 11 nome ASC 12 nome DESC 13 tipo, nome ASC 14 endereco ASC 15 endereco DESC 16 17 e strings.xml 1 2 3 Hello World, ListaRestaurantes! 4 Lista de Restaurantes 5 Nombre: 6 Dirección: 7 Tipo: 8 Rotación 9 Comida Rápida 10 Ubicación 11 Posición 12 (sin asignar) 13 Anotaciones 14 Cuenta de Twitter 15 La Hora del Almuerzo! 16 Tirado. 17 Conexión com la Internet no está disponible 18 Posición salva 19 Definir 20 Cancelar 21 La Hora del Almuerzo! ¿Tienes hambre? 22 Error de Línea de Tiempo de Twitter 23 Erros al enviar datos a la Activity 24 Línea de Tiempo de Twitter 25 Guardar Posición 26 Ver Mapa 27 Añadir 28 Ajustes 29 Modo de Lista 30 Elija el modo de lista que se utiliza 31 Elija el modo de lista 32 Reproducir Alarma en el Almuerzo 33 Selecciona se desea recibir información sobre la hora del almuerzo 34 La Hora del Almuerzo 35 Ajuste el tiempo deseado para el alarma 36 Habilitar Notificación 37 Seleccione si desea un icono en la barra de estado o borrar la notificación en pantalla completa 38 E pronto! Para ver seu aplicativo no idioma espanhol, siga até o menu principal, Settings -> Language & keyboard -> Select language e selecione Español. Widgets Pra quem não sabe, widgets é uma espécie de miniatura do aplicativo que você pode deixar em uma das áreas de trabalho do Android, colocando à disposição do usuário informações de maneira mais rápida e prática. O widget também pode redirecionar o usuário para o aplicativo principal, funcionando como uma espécie de “atalho”. Primeiramente, precisamos definir o layout do nosso widget. Para isso, crie o arquivo widget.xml dentro da pasta res/layout. Ele será bastante simples, inicialmente apenas exibindo o nome de um restaurante cadastrado. Sendo assim, ele terá apenas um TextView em sua estrutura: 1 2 6 13 De diferente do que já fizemos das outras vezes, somente as propriedades que modificam o tamanho e a cor do texto. No mais, tudo dentro dos conformes. O arquivo frame.9.png pode ser baixado aqui. Por que este 9? Porque a imagem é uma NinePatch, ideal para compor fundos de frames. Entenda melhor como funciona aqui. frame.9.png original ampliado (só por curiosidade) O próximo passo é criarmos uma classe para gerenciar o conteúdo do widget. Inicialmente, apenas crie uma classe chamada WidgetAplicativo dentro do pacote com.blogspot.flavioaf.restaurante, estendendo AppWidgetProvider. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.appwidget.AppWidgetProvider; 4 5 public class WidgetAplicativo extends AppWidgetProvider { 6 } Continuando, vamos agora definir algumas propriedades do widget em um arquivo XML. Crie dentro da pasta res/xml o arquivo provedor_widget.xml. 1 2 Basicamente definimos a largura e altura mínimas, o tempo de atualização das informações do widget (no caso, dos restaurantes – a cada 30 minutos) e qual o layout a ser utilizado por ele (no caso, o que definimos no XML anterior). Em seguida, precisamos atualizar o AndroidManifest.xml para que o nosso aplicativo suporte o widget. Adicione o seguinte nó receiver ao final do nó application. 46 49 50 51 52 54 Neste trecho, definimos que a classe que representa o widget é a WidgetApp, que o nome e o ícone a serem exibidos nas opções são os mesmos da aplicação no menu (app_name e ic_launcher). Além disso, definimos que o widget realizará operações de atualização e que suas propriedades estão definidas no arquivo provedor_widget dentro da pasta xml. Por fim, vamos implementar o método onUpdate() para a classe WidgetApp. É este método que fará a busca em nosso banco de dados para exibir o nome de um restaurante. 12 @Override 13 public void onUpdate(Context context, AppWidgetManager appWidgetManager, 14 int[] appWidgetIds) { 15 ComponentName cn = new ComponentName(context, WidgetApp.class); 16 RemoteViews atualizarFrame = new RemoteViews("com.blogspot.flavioaf.restaurante", R.layout.widget); 17 GerenciadorRestaurantes gerenciador = new GerenciadorRestaurantes(context); 18 19 try { 20 Cursor c = gerenciador.getReadableDatabase().rawQuery("SELECT COUNT(*) FROM restaurantes", null); 21 c.moveToFirst(); 22 int count = c.getInt(0); 23 c.close(); 24 25 if (count > 0) { 26 int offset = (int) (count * Math.random()); 27 String args[] = {String.valueOf(offset)}; 28 c = gerenciador.getReadableDatabase().rawQuery("SELECT nome FROM restaurantes LIMIT 1 OFFSET ?", args); 29 c.moveToFirst(); 30 atualizarFrame.setTextViewText(R.id.nome, c.getString(0)); 31 } else { 32 atualizarFrame.setTextViewText(R.id.nome, context.getString(R.string.vazio)); 33 } 34 } finally { 35 gerenciador.close(); 36 } 37 38 appWidgetManager.updateAppWidget(cn, atualizarFrame); 39 } Resumidamente, esse trecho de código:  Cria um objeto RemoteView, que nos permite modificar o widget;  Estabelece uma conexão com o banco de dados;  Verifica quantos restaurantes salvos existem;  Carrega um restaurante aleatório (por isso o uso de Math.random());  Exibe o nome do restaurante, ou uma mensagem dizendo que não existem restaurantes cadastrados;  Atualiza o widget propriamente dito. A mensagem “vazio” (R.string.vazio) deve ser definida nos seus arquivos string.xml nas pastas values que você tem. No meu caso, vou defini-la em português e em espanhol (idiomas que minha aplicação suporta). string.xml em português: 38 Nenhum registro. string.xml em espanhol: 38 Ningún registro. E pronto! Para ativar o widget, clique e segure sobre a área de trabalho para aparecer o menu e a opção de inserir widget. Até agora vimos como criar um widget simples com o nome do restaurante. Vamos incrementá-lo em dois pontos principais: (1) ele terá um botão que mudará o restaurante que é exibido, mostrando outro aleatoriamente e; (2) ao tocar sobre o nome do restaurante no widget, é aberto o formulário para que você possa ver as outras informações sobre aquele restaurante. O primeiro passo é adicionarmos o botão ao layout de nosso widget. Dessa forma, abra o arquivo widget.xml que está em res/layout e faça a adição da imagem do botão, do tipo ImageButton: 1 2 6 13 19 Os atributos, no ponto em que estamos, não devem ser nenhuma surpresa. Neste ponto, já deve ser possível vê-lo no layout ao recompilar a aplicação. O arquivo de imagem do ícone pode ser baixado aqui. No tutorial anterior, toda a nossa lógica de consulta ao banco estava dentro do método onUpdate() do nosso widget. Isso funciona bem, mas, caso a lógica se expanda muito, corremos o risco da atualização demorar mais que o esperado e comprometer o desempenho da aplicação, já que o método é chamado em nossa thread principal. Para melhorar isso, vamos novamente fazer uso de um IntentService que nos possibilita a realizar a consulta de modo assíncrono, sem comprometer o sistema. Portanto, crie uma classe chamada WidgetService e coloque-a no pacote com.blogspot.flavioaf.restaurante. Esta classe, que estenderá IntentService abrigará a lógica que estávamos executando no widget. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.app.IntentService; 4 import android.app.PendingIntent; 5 import android.appwidget.AppWidgetManager; 6 import android.content.ComponentName; 7 import android.content.Intent; 8 import android.database.Cursor; 9 import android.widget.RemoteViews; 10 11 public class WidgetService extends IntentService { 12 13 public WidgetService() { 14 super("WidgetService"); 15 } 16 17 @Override 18 protected void onHandleIntent(Intent intent) { 19 ComponentName cn = new ComponentName(this, WidgetAplicativo.class); 20 RemoteViews atualizarFrame = new RemoteViews("com.blogspot.flavioaf.restaurante", R.layout.widget); 21 GerenciadorRestaurantes gerenciador = new GerenciadorRestaurantes(this); 22 AppWidgetManager mgr = AppWidgetManager.getInstance(this); 23 24 try { 25 Cursor c = gerenciador.getReadableDatabase().rawQuery("SELECT COUNT(*) FROM restaurantes", null); 26 c.moveToFirst(); 27 int contador = c.getInt(0); 28 c.close(); 29 30 if (contador > 0) { 31 int deslocamento = (int) (contador * Math.random()); 32 String args[] = {String.valueOf(deslocamento)}; 33 c = gerenciador.getReadableDatabase().rawQuery("SELECT _id, nome FROM restaurantes LIMIT 1 OFFSET ?", args); 34 c.moveToFirst(); 35 atualizarFrame.setTextViewText(R.id.nome, c.getString(1)); 36 37 c.close(); 38 } else { 39 atualizarFrame.setTextViewText(R.id.nome, getString(R.string.vazio)); 40 } 41 } finally { 42 gerenciador.close(); 43 } 44 45 mgr.updateAppWidget(cn, atualizarFrame); 46 } 47 } Basicamente o que fizemos foi passar a lógica para o nosso WidgetService. O próximo passo é adicionar esse nosso serviço lá no arquivo AndroidManifest.xml para que ele possa funcionar. Assim, adicione ao fim do nó application a seguinte linha: 57 Agora, vamos atualizar a classe WidgetAplicativo para que faça uso do nosso serviço. 1 package com.blogspot.flavioaf.restaurante; 2 3 import android.appwidget.AppWidgetManager; 4 import android.appwidget.AppWidgetProvider; 5 import android.content.Context; 6 import android.content.Intent; 7 8 public class WidgetAplicativo extends AppWidgetProvider { 9 10 @Override 11 public void onUpdate(Context context, AppWidgetManager appWidgetManager, 12 int[] appWidgetIds) { 13 context.startService(new Intent(context, WidgetService.class)); 14 } 15 } Pronto. A parte mais “complicada” está feita. Agora precisamos apenas gerenciar os toques no botão e no nome do restaurante. Primeiramente, vamos fazer com que um novo restaurante seja exibido caso seja acionado o botão. Para esse processo, utilizaremos um PendingIntent para acionar a nossa Intent quando o “clique” for realizado. Para isso, adicione as seguintes linhas ao final do método onHandleEvent() da classe WidgetService: 50 Intent i = new Intent(this, WidgetService.class); 51 PendingIntent pi = PendingIntent.getService(this, 0, i, 0); 52 atualizarFrame.setOnClickPendingIntent(R.id.proximo, pi); 53 mgr.updateAppWidget(cn, atualizarFrame); Com isso, toda vez que o botão for acionado, o nosso serviço de carregamento será executado novamente. Por fim, precisamos agora gerenciar para que, ao tocar sobre o nome do restaurante, seja aberta a tela com o formulário. Para isso, precisaremos de realizar algumas mudanças sutis no método onHandleIntent(). As principais são que, além do nome, precisaremos também do identificador, pois através dele iremos carregar os dados no formulário. Além disso, também utilizaremos um PendingIntent para acionar a exibição do formulário. 18 @Override 19 protected void onHandleIntent(Intent intent) { 20 ComponentName cn = new ComponentName(this, WidgetAplicativo.class); 21 RemoteViews atualizarFrame = new RemoteViews("com.blogspot.flavioaf.restaurante", R.layout.widget); 22 GerenciadorRestaurantes gerenciador = new GerenciadorRestaurantes(this); 23 AppWidgetManager mgr = AppWidgetManager.getInstance(this); 24 25 try { 26 Cursor c = gerenciador.getReadableDatabase().rawQuery("SELECT COUNT(*) FROM restaurantes", null); 27 c.moveToFirst(); 28 int contador = c.getInt(0); 29 c.close(); 30 31 if (contador > 0) { 32 int deslocamento = (int) (contador * Math.random()); 33 String args[] = {String.valueOf(deslocamento)}; 34 c = gerenciador.getReadableDatabase().rawQuery("SELECT _id, nome FROM restaurantes LIMIT 1 OFFSET ?", args); 35 c.moveToFirst(); 36 atualizarFrame.setTextViewText(R.id.nome, c.getString(1)); 37 38 Intent i = new Intent(this, FormularioDetalhes.class); 39 i.putExtra(ListaRestaurantes._ID, c.getString(0)); 40 PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); 41 atualizarFrame.setOnClickPendingIntent(R.id.nome, pi); 42 43 c.close(); 44 } else { 45 atualizarFrame.setTextViewText(R.id.nome, getString(R.string.vazio)); 46 } 47 } finally { 48 gerenciador.close(); 49 } 50 51 Intent i = new Intent(this, WidgetService.class); 52 PendingIntent pi = PendingIntent.getService(this, 0, i, 0); 53 atualizarFrame.setOnClickPendingIntent(R.id.proximo, pi); 54 mgr.updateAppWidget(cn, atualizarFrame); 55 } E… pronto! Fazendo Ligações (Chamadas) Vamos ver agora como realizar chamadas. Nesta última sessão de modificações em nosso aplicativo, vamos adicionar um campo denominado telefone aos nossos restaurantes e, a partir dele, vamos possibilitar que o usuário faça uma chamada diretamente do nosso aplicativo. Começando, temos inicialmente que atualizar o nosso modelo de dados para que armazene o novo dado necessário (o número de telefone). Primeiramente, modifique o método onCreate() da classe GerenciadorRestaurantes para abrigar o novo campo: 1 @Override 2 public void onCreate(SQLiteDatabase db) { 3 db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 4 " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT," + 5 " latitude REAL, longitude REAL, telefone TEXT);"); 6 } Em seguida, altere o método onUpgrade() para atualizar o modelo de dados do banco, caso o usuário esteja vindo de uma versão anterior: 25 @Override 26 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 27 if (oldVersion < 2) { 28 db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT"); 29 } 30 31 if (oldVersion < 3) { 32 db.execSQL("ALTER TABLE restaurantes ADD COLUMN latitude REAL"); 33 db.execSQL("ALTER TABLE restaurantes ADD COLUMN longitude REAL"); 34 } 35 36 if (oldVersion < 4) { 37 db.execSQL("ALTER TABLE restaurantes ADD COLUMN telefone TEXT"); 38 } 39 } Pronto. Agora, vamos procurar pelos métodos inserir(), atualizar(), obterTodos() e obterPorId() para adicionar o campo telefone a eles também. 41 public void inserir(String nome, String endereco, String tipo, String anotacoes, String twitter, 42 String telefone) { 43 ContentValues valores = new ContentValues(); 44 45 valores.put("nome", nome); 46 valores.put("endereco", endereco); 47 valores.put("tipo", tipo); 48 valores.put("anotacoes", anotacoes); 49 valores.put("twitter", twitter); 50 valores.put("telefone", telefone); 51 52 getWritableDatabase().insert("restaurantes", "nome", valores); 53 } 54 55 public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes, 56 String twitter, String telefone) { 57 ContentValues valores = new ContentValues(); 58 String[] argumentos = {id}; 59 60 valores.put("nome", nome); 61 valores.put("endereco", endereco); 62 valores.put("tipo", tipo); 63 valores.put("anotacoes", anotacoes); 64 valores.put("twitter", twitter); 65 valores.put("telefone", telefone); 66 67 getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos); 68 } 69 70 public Cursor obterTodos(String ordenacao) { 71 return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " + 72 "anotacoes, twitter, latitude, longitude, telefone FROM restaurantes ORDER BY " + 73 ordenacao, null); 74 } 75 76 public Cursor obterPorId(String id) { 77 String[] argumentos = {id}; 78 79 return getReadableDatabase().rawQuery( 80 "SELECT _id, nome, endereco, tipo, anotacoes, twitter, latitude," + 81 " longitude, telefone FROM restaurantes WHERE _id = ?", argumentos); 82 } Para concluir as alterações nesta classe, crie o método obterTelefone(): 114 public String obterTelefone(Cursor c) { 115 return c.getString(8); 116 } Pronto. Com relação a persistência, já estamos aptos a prosseguir. Vamos agora adicionar o campo telefone aos nossos formulários. Como devem estar lembrados, temos 2 layouts, um para o modo retrato, outro para o modo paisagem. Primeiro no modo retrato, adicione o seguinte trecho logo após o campo de endereço, no arquivo res/layout/form_detalhes.xml. 14 15 16 17 Agora no res/layout-land/form_detalhes.xml. Também, logo depois do trecho do campo de endereço. 16 17 18 20 Pronto. Nossas modificações agora serão na classe FormularioDetalhes. Primeiramente adicione o atributo telefone à classe: 25 EditText telefone = null; Agora, precisamos atualizar os métodos onCreate(), salvar(), carregar(), onSaveInstanceState() e onRestoreInstanceState(). Pode parecer bastante coisa, mas são apenas ajustes leves para adicionar o novo campo. 36 @Override 37 public void onCreate(Bundle savedInstanceState) { 38 super.onCreate(savedInstanceState); 39 setContentView(R.layout.form_detalhes); 40 41 locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); 42 gerenciador = new GerenciadorRestaurantes(this); 43 44 nome = (EditText) findViewById(R.id.nome); 45 endereco = (EditText) findViewById(R.id.end); 46 telefone = (EditText) findViewById(R.id.telefone); 47 anotacoes = (EditText) findViewById(R.id.anotacoes); 48 twitter = (EditText) findViewById(R.id.twitter); 49 tipos = (RadioGroup) findViewById(R.id.tipos); 50 localizacao = (TextView) findViewById(R.id.localizacao); 51 52 idRestaurante = getIntent().getStringExtra(ListaRestaurantes._ID); 53 54 if (idRestaurante != null) { 55 carregar(); 56 } 57 } 58 59 private void salvar() { 60 String tipo = null; 61 62 switch (tipos.getCheckedRadioButtonId()) { 63 case R.id.rodizio: 64 tipo = "rodizio"; 65 break; 66 case R.id.fast_food: 67 tipo = "fast_food"; 68 break; 69 case R.id.a_domicilio: 70 tipo = "a_domicilio"; 71 break; 72 } 73 74 if (tipo != null && endereco.getText().toString() != null && 75 nome.getText().toString() != null) { 76 77 if (idRestaurante == null) { 78 gerenciador.inserir(nome.getText().toString(), 79 endereco.getText().toString(), 80 tipo, anotacoes.getText().toString(), 81 twitter.getText().toString(), telefone.getText().toString()); 82 } else { 83 gerenciador.atualizar(idRestaurante, 84 nome.getText().toString(), 85 endereco.getText().toString(), 86 tipo, anotacoes.getText().toString(), 87 twitter.getText().toString(), telefone.getText().toString()); 88 } 89 } 90 91 finish(); 92 } 93 94 private void carregar() { 95 Cursor c = gerenciador.obterPorId(idRestaurante); 96 97 c.moveToFirst(); 98 nome.setText(gerenciador.obterNome(c)); 99 endereco.setText(gerenciador.obterEndereco(c)); 100 telefone.setText(gerenciador.obterTelefone(c)); 101 anotacoes.setText(gerenciador.obterAnotacoes(c)); 102 twitter.setText(gerenciador.obterTwitter(c)); 103 104 if (gerenciador.obterTipo(c).equals("rodizio")) { 105 tipos.check(R.id.rodizio); 106 } else if (gerenciador.obterTipo(c).equals("fast_food")) { 107 tipos.check(R.id.fast_food); 108 } else { 109 tipos.check(R.id.a_domicilio); 110 } 111 112 latitude = gerenciador.obterLatitude(c); 113 longitude = gerenciador.obterLongitude(c); 114 115 localizacao.setText(String.valueOf(gerenciador.obterLatitude(c)) + 116 ", " + String.valueOf(gerenciador.obterLongitude(c))); 117 118 c.close(); 119 } 120 121 @Override 122 public void onSaveInstanceState(Bundle outState) { 123 super.onSaveInstanceState(outState); 124 125 outState.putString("nome", nome.getText().toString()); 126 outState.putString("endereco", endereco.getText().toString()); 127 outState.putString("telefone", telefone.getText().toString()); 128 outState.putString("anotacoes", anotacoes.getText().toString()); 129 outState.putInt("tipo", tipos.getCheckedRadioButtonId()); 130 } 131 132 @Override 133 protected void onRestoreInstanceState(Bundle savedInstanceState) { 134 super.onRestoreInstanceState(savedInstanceState); 135 136 nome.setText(savedInstanceState.getString("nome")); 137 endereco.setText(savedInstanceState.getString("endereco")); 138 telefone.setText(savedInstanceState.getString("telefone")); 139 anotacoes.setText(savedInstanceState.getString("anotacoes")); 140 tipos.check(savedInstanceState.getInt("tipo")); 141 } Perceberam como as alterações foram mínimas? Prosseguindo, precisamos dizer ao Android que nossa aplicação deseja realizar chamadas. Para isso, adicione a seguinte linha às permissões no arquivo AndroidManifest.xml. 13 Agora, vamos criar a opção ao menu para realizar as chamadas. Edite o arquivo res/menu/opcao_detalhes.xml para acomodar a nova opção (é… vai ficar meio espremido em telas pequenas…). 1 2 3 6 9 12 15 O arquivo de ícone utilizado foi o ic_menu_call.png encontrado na pasta de instalação do Android e devidamente renomeado para chamada.png. Agora precisamos definir as novas strings utilizadas no formulário e no menu. Adicione-as ao arquivo res/values/strings.xml… 39 Telefone: 40 Telefonar … e no res/values-es/strings.xml. 39 Teléfono: 40 Llamar Por fim, vamos fazer com que a opção de menu realize a chamada. Adicione o seguinte trecho aos encadeamentos de ifs no método onOptionsItemSelected(). 155 } else if (item.getItemId() == R.id.chamar) { 156 String numero = "tel:" + telefone.getText().toString(); 157 if (numero.length() > 4) { 158 startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(numero))); 159 } 160 } Execute a aplicação para conferir as modificações. Caso queira que a ligação seja realizada diretamente, sem exibir o discador, modifique, na linha 158 da última listagem de código, ACTION_DIAL por ACTION_CALL. Terminamos Parabéns, você chegou ao fim desse curso avançado de Android. Contatos Você pode se comunicar comigo de diversas formas: www.facebook.com/flaviofreitas www.twitter.com/zz4fff www.orkut.com/xxx flaviocefetrp@gmail.com Nem minha mãe sabe Nem o FBI conseguiu descobrir http://flavioaf.blogspot.com :: http://financeaccess.blogspot.com :: http://financasja.blogspot.com https://www.facebook.com/aulaparticulardoflavio