Android Avanado.pdf

  • Published on
    25-Nov-2015

  • View
    385

  • Download
    2

Transcript

  • Minicurso

    Android Avanado

    Flvio Augusto de Freitas

    flaviocefetrp@gmail.com

    http://flavioaf.blogspot.com

  • Sumrio Passos Iniciais .................................................................................................................................................................... 3

    Montando um Ambiente de Desenvolvimento para Android ...................................................................................... 4

    Configurando um Dispositivo Android com o AVD Manager ....................................................................................... 9

    Tutoriais Avanados ........................................................................................................................................................ 12

    Persistncia em Banco de Dados ................................................................................................................................ 13

    Editando Registros do Banco de Dados ...................................................................................................................... 22

    Configurando Preferncias ......................................................................................................................................... 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

    Notificaes ................................................................................................................................................................ 76

    Internacionalizao (i18n) ........................................................................................................................................... 78

    Widgets ....................................................................................................................................................................... 84

    Fazendo Ligaes (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 mveis. Hoje em dia, milhes de celulares e tablets

    utilizam o Android como sistema.

    Uma coisa bem legal que voc pode disponibilizar seus aplicativos no Market do Android (aps 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 utilizao de arquivos XML

    para a criao das interfaces. Apesar de parecer complexo, relativamente simples criar seus aplicativos. Alm disso,

    bem fcil ter acesso a diversos recursos geralmente disponveis em dispositivos mveis, tais como cmera, GPS,

    Bluetooth, etc.

    Para facilitar o desenvolvimento, foi criado um plug-in para o Eclipse. Atravs dele, fcil gerenciar as plataformas

    (diversas verses do Android) e as mquinas virtuais para executar seus aplicativos.

    Bom, ento pra comear, vamos fazer o download da JDK. Se voc j programa em Java, este passo no necessrio.

    No momento em que escrevo este tutorial, a verso mais recente a Java 7 update 2. Siga at esta pgina e faa o

    download.

    A instalao deve ocorrer sem problemas (o famoso, next, next, next, finish). O prximo passo baixar o Eclipse. V

    at esta pgina e faa o download relacionado a verso de seu sistema operacional. Para os nossos propsitos, a

    verso Eclipse IDE for Java Developers deve ser suficiente.

  • Ao concluir o download, basta descompactar o arquivo em algum lugar da sua mquina. Eu recomendo, no caso do

    Windows, na raiz C: ou em sua pasta de usurio (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 pgina e baixe a verso zipada da SDK (apesar de recomendarem a

    verso instalvel) isso pra evitarmos problemas de permisso 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

    verso mais recente a 16.

    Aps a concluso do download, descompacte o arquivo (pode ser no mesmo local onde voc colocou o Eclipse).

    Aqui, por exemplo, vai ficar C:\android-sdk-windows. Aps extrair, vamos executar o SDK Manager para baixar uma

    SDK para comearmos a programar. Ao executar pela primeira vez, o SDK Manager ir verificar os repositrios do

    Android em busca das ltimas verses do SDK.

  • Para comear, vamos baixar o SDK da verso 2.2, j que os aplicativos desenvolvidos nela funcionam na grande

    maioria dos dispositivos Android de hoje. Se quiser instalar outras verses, fique vontade. Expanda a pasta Android

    2.2 (API 8 ) e marque as opes SDK Platform. Alm disso, na categoria Tools, marque a opo Android SDK Platform-

    tools. Clique em Install 2 packages (ou mais, se voc selecionou mais alguma coisa), marque Accept All e ento

    clique em Install.

    Aps a concluso dos downloads, hora de configurar o Eclipse. V at o diretrio onde ele foi descompactado e

    execute-o.

  • Ao ser consultado sobre qual workspace utilizar, basta confirmar e utilizar o padro (workspace o local onde seus

    projetos sero 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 boto Add. Na tela seguinte, preencha o nome do plugin (ADT Plugin) e coloque o endereo

    https://dl-ssl.google.com/android/eclipse, conforme a imagem abaixo:

    Clique em OK e aguarde o carregamento do repositrio. Ao concluir, marque a caixa Developer Tools e clique em

    Next > duas vezes. Na tela seguinte, aceite os termos da licena e clique em Finish. Agora aguarde a instalao e,

    caso seja alertado sobre contedo no-assinado, clique em OK para continuar.

    Ao final, clique em Restart Now para reiniciar o Eclipse e concluir a instalao. O prximo passo configurar o local

    onde as SDKs esto. No Eclipse, v ao menu Window -> Preferences. Clique no boto Browse e aponte para a pasta

    que voc descompactou. Aps a confirmao, devero ser exibidas as SDKs que voc baixou.

  • Pronto! Seu ambiente Android j est pronto para ser utilizado! No prximo tutorial veremos como configurar um

    dispositivo para executar nossa aplicao.

  • 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 execuo dos aplicativos que sero criados.

    Assim, voc no precisa necessariamente de um celular com Android para comear 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, ento, aberta a janela com a listagem de dispositivos criados (no nosso caso, nenhum ainda).

    Ento, para criarmos um novo dispositivos, clicamos no boto New. Nesta tela, devemos preencher os dados

    relativos ao nosso dispositivo, como nome (Name), verso do Android que ir executar (Target), alm de dados

    como tamanho do carto SD virtual (caso desejado), tamanho da tela e perifricos (cmera, GPS, Acelermetro,

    Teclado fsico, etc.).

  • Aps montar seu dispositivo, clique em Create AVD e ter seu dispositivo listado!

  • Quando estiver desenvolvendo, recomendvel criar diferentes tipos de dispositivos, com verses diferentes do

    Android e tamanhos de tela variados, de forma a fazer seu aplicativo ser executado corretamente em diversas

    configuraes.

  • Tutoriais Avanados

  • Persistncia em Banco de Dados Ol leitores! No tutorial de hoje, vamos criar um sistema de persistncia para a nossa Lista de Restaurantes. Assim,

    os restaurantes cadastrados sero mantidos a cada execuo do aplicativo.

    O sistema Android nos fornece nativamente as opes de persistir dados utilizando arquivos ou em banco de dados,

    utilizando o SQLite. Se voc no 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 bsico em SQL. Se voc nunca mexeu com isso,

    no se preocupe, pois os conceitos no so complicados de entender.

    Bom, comeando o nosso tutorial, vamos criar uma classe que gerenciar a criao 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 princpio, definimos que nosso banco de dados ser armazenado no arquivo restaurantes.db e que utilizaremos a

    primeira verso do schema do banco. Neste ponto o projeto ainda deve compilar sem problemas.

    Em seguida, vamos implementar o mtodo 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 mtodo onUpgrade() no ser til

    para ns por enquanto. Em um aplicativo real, voc poderia implement-lo para fazer backup dos dados em uma

    tabela temporria, atualizar a estrutura do banco e ento retornar os dados.

  • O prximo passo remover partes do cdigo da classe ListaRestaurantes que no nos sero teis daqui pra frente

    (como os trechos que manipulam a nossa barra de progresso). As primeiras excluses so os atributos estaAtivo e

    progresso. Em seguida, podemos remover a chamada ao mtodo requestWindowFeature() dentro do mtodo

    onCreate(). Podemos tambm excluir as implementaes dos mtodos onPause(), onResume(),

    onCreateOptionsMenu() e onOptionsItemSelected(). Por fim, podemos excluir tambm os mtodos iniciarTarefa(),

    fazerAlgoDemorado() e a nossa tarefaLonga.

    A classe GerenciadorRestaurantes ser a nossa ponte entre a aplicao e o banco de dados. Dessa forma, vamos

    criar um atributo na classe ListaRestaurantes chamado gerenciador do tipo GerenciadorRestaurante.

    34 GerenciadorRestaurantes gerenciador;

    L no mtodo onCreate(), aps a chamada a setContentView(), vamos ento instanciar o atributo:

    40 gerenciador = new GerenciadorRestaurantes(this);

    Complementando, implemente o mtodo onDestroy() na classe ListaRestaurantes.

    67 @Override

    68 public void onDestroy() {

    69 super.onDestroy();

    70 gerenciador.close();

    71 }

    Ns vamos agora, substituir nosso objeto de modelo (e seu ArrayList associado) pelo banco de dados, utilizando

    tambm a classe Cursor do Android para controlar as instncias. Primeiramente, vamos adicionar o mtodo 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 mtodo, recebemos os valores individuais dos campos que compem 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 instncia do banco para escrita e inserimos os valores na tabela restaurantes.

    Agora, devemos realizar a chamada a este mtodo ao pressionarmos o boto Salvar em nosso formulrio (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

    contedo de uma consulta ao banco de dados. No Android, utilizamos a classe Cursor que tem funcionamento

    semelhante.

    Assim, vamos criar um mtodo na classe GerenciadorRestaurantes para obter a lista de restaurantes salvos no

    banco. Vamos implementar o mtodo obterTodos():

    39 public Cursor obterTodos() {

    40 return getReadableDatabase().rawQuery("select id, nome, endereco, tipo, " +

    41 "anotacoes FROM restaurantes ORDER BY nome", null);

    42 }

    Precisaremos tambm de mtodos que nos forneam acesso a determinados campos do Cursor. Dessa forma,

    adicione estes mtodos 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 implementao atual, a classe Adaptador estende a classe ArrayAdapter, de forma que ela no conseguir

    manipular os dados contidos no Cursor. Assim, modificaremos sua implementao para, ento, estender no 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 tambm 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 mtodo onCreate(), substitua o cdigo 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 referncias ao ArrayList que tnhamos 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 insero. Insira a seguinte

    linha aps a insero l no onSave:

    117 listaRestaurantes.requery();

    Pronto! Voc j pode executar a sua verso 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.

    Alm disso, tambm faremos uma mudana no visual da aplicao, retirando as abas e incluindo a tela de adio de

    restaurantes como uma opo no menu.

    O primeiro passo criarmos uma nova Activity, que ser onde ficar, a partir de agora, o nosso formulrio de

    cadastro (e consequentemente, de edio). Separaremos as funes da nossa Activity inicial. Ento, 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 no tem nenhum layout atribudo, j que ainda no 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 edio. 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 aplicao, ela deve exibir uma tela vazia ao clicar em algum item da lista.

    Continuando, vamos agora fazer a migrao do formulrio 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 mtodo onCreate:

  • 11 setContentView(R.layout.form_detalhes);

    O prximo passo mover toda a lgica do formulrio 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 formulrio do mtodo 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 implementao do nosso listener onSave para a classe FormularioDetalhes, porm retirando

    a parte que trata da insero 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 formulrio que existia e

    as abas, alm do ajuste no layout para abrigar somente a lista. O que nos resta isso:

    1

  • 2

    Aps isso, exclua a pasta layout_land e o arquivo main.xml dentro dela.

    No momento, ListaRestaurantes estende TabActivity. Porm, como modificamos a estrutura de nossa aplicao, isso

    no mais necessrio. Modifique a classe, de forma que ListaRestaurante estenda ListActivity. Em seguida,

    modifique o mtodo onCreate() para retirar os cdigos que diziam respeito s abas que utilizvamos:

    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

    criao de novos restaurantes quando na edio de restaurantes j cadastrados. Alm 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";

    Aps 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 mtodo onCreate(), precisamos encerr-lo no mtodo 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 mtodo que nos retorne o Restaurante

    com o identificador correspondente. Adicione o seguinte mtodo 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 mtodo onCreate() da classe FormularioDetalhes:

    35 idRestaurante = getIntent().getStringExtra(ListaRestaurantes._ID);

    36

    37 if (idRestaurante != null) {

    38 carregar();

    39 }

    Adicione, ento, a implementao do mtodo 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 opo 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 mdia padro do Android, e pode ser encontrado no seu diretrio de instalao do SDK, em

    platforms -> verso 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 aplicao. 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 mtodo 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, tambm, a implementao 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 mtodo para inserir um novo restaurante, mas no

    temos o mtodo para atualizar. Portanto, adicione o seguinte mtodo 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 boto Salvar no formulrio. Modifique a implementao do

    onSave na classe FormularioDetalhes para verificar se a operao de incluso ou alterao:

    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 no h cdigo duplicado, corrija os imports (Ctrl + Shift + O) e pronto! J temos nossa aplicao

    funcionando!

  • Pra simplificar, a vo 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 Preferncias Vamos adicionar ao nosso aplicativo Lista de Restaurantes a opo do usurio configurar de que forma deve ocorrer

    a listagem dos restaurantes (nome, tipo, ordem alfabtica, etc.).

    Pra comear, vamos criar um arquivo XML que tomar conta das configuraes de preferncia. Dessa forma, crie o

    arquivo preferencias.xml e coloque-o em res/xml (a pasta ainda no existe ento crie-a). O contedo 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 contedo listado a seguir:

    1

    2

    3

    4 Por Nome, Ascendente

    5 Por Nome, Descendente

    6 Por Tipo

    7 Por Endereo, Ascendente

    8 Por Endereo, Descendente

    9

    10

    11 nome ASC

    12 nome DESC

    13 tipo, nome ASC

    14 endereco ASC

    15 endereco DESC

    16

    17

    O prximo passo a criao da Activity responsvel pelas preferncias. 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 }

  • Tambm necessrio 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 opes. Primeiramente, vamos editar o

    arquivo opcao.xml, que se encontra em res/menu.

    1

    2

    3

    6

    9

    O cone referenciado padro do sistema e, como mostrado no ltimo tutorial, pode ser encontrado na prpria

    instalao da SDK. Este cone utilizado se chama ic_menu_preferences (aqui foi renomeado para

    menu_preferencias em nosso projeto).

    Agora, vamos modificar o mtodo onOptionsItemSelected na classe ListaRestaurantes para mapear esta nova opo

    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 aplicao, j poder conferir o menu:

    Agora, j que a parte visual est pronta, vamos aplicar a ordenao a nossa lista. Primeiramente, precisamos que o

    mtodo obterTodos() da classe GerenciadorRestaurantes precisa receber o mtodo de ordenao por parmetro 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 ordenao selecionada e

    aplic-la a listagem. Adicione um atributo classe chamado prefs do tipo SharedPreferences.

    26 SharedPreferences prefs = null;

    Em seguida, adicione a inicializao do atributo no mtodo onCreate(), prximo ao seu incio.

    34 prefs = PreferenceManager.getDefaultSharedPreferences(this);

  • E modifique a chamada ao mtodo obterTodos() logo em seguida:

    36 listaRestaurantes = gerenciador.obterTodos(prefs.getString("ordenacao", "nome"));

    Por fim, vamos fazer com que seja aplicada as alteraes realizadas pelo usurio em tempo de execuo, j que, por

    enquanto, necessrio fechar a aplicao para que a nova ordenao tenha efeito. Adicione esta linha ao fim do

    mtodo 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 inicializao da lista em um mtodo parte, deixando o mtodo onCreate() mais limpo.

    Crie o mtodo 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 recm-criado mtodo inicializarLista() no mtodo 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 ento, tambm fazemos uma chamada ao mtodo 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 adaptao do layout da aplicao Lista de Restaurantes tambm para o modo paisagem.

    Neste tutorial vamos fazer a rotao de uma maneira mais organizada, evitando alguns problemas.

    Primeiramente, precisamos de uma forma de armazenar os valores no caso da mudana de orientao do celular. L

    na classe FormularioDetalhes, vamos sobrescrever o mtodo onSaveInstanceState(), que armazenar os valores pra

    gente. Adicione a seguinte implementao 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 formulrio fossem salvos. Agora, vamos implementar o mtodo

    onRestoreInstanceState() que devolver os dados no formulrio.

    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, no precisamos alterar seu layout pois ele funciona bem tanto em modo retrato

    quanto paisagem.

  • Observao

    Aps a ltima atualizao do plugin ADT do Eclipse, ele acusou alguns warnings nos layouts

    XML. Por enquanto no se preocupem com isso!

  • Integrando Bibliotecas de Terceiros (Twitter) Veremos neste tutorial como integrar uma biblioteca externa ao nosso aplicativo em Android. Atravs 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 verso 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

    persistncia GerenciadorRestaurantes. Comece alterando o mtodo 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 }

    Alm disso, vamos alterar a verso do Schema do banco, para que ele seja atualizado em verses anteriores:

    12 private static final int VERSAO_SCHEMA = 2;

    Precisamos tambm atualizar o nosso mtodo onUpdate(), que antes no fazia nada. Ele ser executado se o usurio

    possuir a verso 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 modificaes nesta classe, vamos atualizar os mtodo obterTodos(), obterPorId(), inserir() e

    atualizar(). Alm disso, tambm vamos adicionar o mtodo 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 relao aos dados do aplicativo, j estamos prontos. Vamos agora ajustar o formulrio de detalhes,

    adicionando o campo Twitter em ambos. Lembre-se que, como temos duas verses deste formulrio (res/layout e

    res/layout-land), precisaremos fazer a modificao em ambos. Primeiramente o formulrio 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 modificao a adio do campo twitter e a remoo do TextView do campo anotaes,

    exibido agora como o atributo hint. A mesma coisa fazemos no formulrio 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 mtodo onCreate(), obtemos o valor do formulrio e o aplicamos ao atributo da classe:

    39 twitter = (EditText) findViewById(R.id.twitter);

    E no mtodo carregar(), configuramos o texto do EditText:

    130 twitter.setText(gerenciador.obterTwitter(c));

    Finalmente, vamos modificar as chamadas aos mtodos de persistncia 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 prximo passo adicionar a opo 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 mtodo 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 conexo de rede do celular com a Internet, precisamos verificar se essa conexo est

    disponvel. Vamos ento criar os mtodos redeDisponivel() e o mtodo 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, "Conexo com a Internet indisponvel", 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 mtodos, o Eclipse vai chiar pela ausncia da classe TwitterActivity. Calma! J j resolveremos

    isso. Tambm precisaremos adicionar ao AndroidManifest.xml que nosso aplicativo precisa de permisso para

    acessar a rede. Assim, adicione as duas linhas seguintes ao arquivo, antes do n application.

    9

    10

    Agora vamos comear a mexer com a obteno 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 boto direito sobre o projeto, selecione Properties, clique em Java Build Path, aba

    Libraries e clique no boto 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 responsvel por carregar os tweets da conta selecionada e envi-los para a Activity (carregamento na

    linha 43 e atribuio para a Activity na linha 54). Sempre lembrando de corrigir os imports com o Ctrl + Shift + O

    Prosseguindo, vamos implementar os mtodos 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 responsvel por adaptar os nossos tweets para a exibio.

    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 instncias 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 mtodos 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 contedo do Twitter. Isso foi necessrio para que

    pudssemos obter comunicao com a rede fora da thread principal do aplicativo e, portanto, evitar lentido 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 usurio. 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 aps os ns activity,

    dentro do n application.

    29

    30

    O mtodo onHandleIntent() da IntentService chamado sempre em background, razo principal de a utilizarmos.

    Vamos comear com uma implementao inicial deste mtodo l na nossa classe TwitterService, importando parte

    da lgica que tnhamos no mtodo 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 tambm o atributo da classe referenciado no mtodo, 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 comunicao, utilizaremos um

    Messenger, que servir para obtermos informaes do servio. Dessa forma, atualize a implementao do mtodo

    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 modificaes 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 mtodos anexar() e desanexar() sero mantidos para gerenciar as mudanas na configurao. J o

    mtodo doInBackground() ser removido, j que a lgica foi movida para o servio. O mtodo onTutorialExecute()

    vira handleMessage(), para pegar o objeto Message do TwitterService, chamando os mtodos atribuirTweets() ou

    atirarErro() dependendo do retorno do servio. 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 no temos mais a TarefaTwitter, no precisamos mais dele no StatusInstancia. Porm, precisamos guardar

    nosso Handler como parte de nosso status, de forma que quando o usurio 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, tambm precisaremos modificar o mtodo 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 mtodo 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 mudana deve ser percebida.

    Se nenhuma mudana percebida, por que tudo isso? Bem, pode ser que neste caso a diferena no seja visvel,

    mas imagine que em vez dos tweets fssemos baixar um vdeo. Utilizando o IntentService, a operao ocorre sem

    estar vinculada a nenhuma Activity. Ou seja, o usurio no precisa ficar esperando o download terminar para

    continuar. O IntentService far o download por si prprio 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 localizao (contando que o cadastro esteja sendo feito no prprio restaurante) e salve junto ao

    registro daquele restaurante. Interessante, no?

    Pra comear, vamos adicionar os dados de latitude e longitude em nosso modelo de dados. Primeiramente,

    modifique o mtodo 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 modificao exige que atualizemos a verso do schema do banco de dados:

    12 private static final int VERSAO_SCHEMA = 3;

    Na hora de modificarmos o mtodo 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 tambm os mtodos obterTodos() e obterPorId() para abranger tambm 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 no vamos inserir diretamente as coordenadas de latitude e longitude diretamente (pedir pro usurio digitar

    picardia, n?), no precisaremos nos preocupar com os mtodos inserir() e atualizar(). Criaremos um mtodo

    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 mtodo obviamente s funcionar para restaurantes que j existam no banco de dados, restries que iremos

    aplicar atravs da interface. Por fim, vamos criar os dois mtodos 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 poro

    considervel da tela ocupada pelo boto Salvar. Na maioria das interfaces, este boto no est presente. Assim,

    temos duas abordagens para resolver a situao:

    (1) Adicionar uma opo 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 usurio pressionar a tecla voltar ou home.

    Para isso, precisaremos nos desfazer de todas as referncias ao boto salvar na classe FormularioDetalhes. Para

    comear, vamos converter o objeto onSave para um mtodo 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 referncia ao boto no mtodo onCreate() e implemente o mtodo onPause() que faz a

    chamada ao mtodo salvar().

    48 @Override

    49 public void onPause() {

    50 salvar();

    51

    52 super.onPause();

    53 }

    O prximo passo modificar o layout do formulrio, removendo o boto salvar e adicionando a opo de menu para

    obter a localizao 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="Anotaes"

    37 android:layout_marginTop="4dip"/>

    38

    41

    E em seguida o layout para a verso 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 mtodo onCreate(), adicione a linha seguinte para obter o item do formulrio.

    40 localizacao = (TextView) findViewById(R.id.localizacao);

    Em seguida, no mtodo carregar(), obtemos os valores do GerenciadorRestaurantes e o colocamos no formulrio.

    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 }

    Tambm precisamos adicionar no menu a opo para obter a localizao. 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 permisso de acessar a localizao, atravs do arquivo AndroidManifest.xml.

    Adicione a seguinte linha junto s outras permisses:

    11

    O prximo passo obter a localizao de fato. Primeiramente, adicione o atributo locationManager na classe

    FormularioDetalhes:

    28 LocationManager locationManager = null;

    Em seguida, adicione a sua localizao no mtodo onCreate():

    50 locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

    Agora, precisamos fazer a chamada ao mtodo requestLocationUpdates() da classe LocationManager para pedir a

    localizao quando o usurio selecionar o boto no menu. Dessa forma, modifique o mtodo

    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, "Conexo com a Internet indisponvel", 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, "Localizao salva", Toast.LENGTH_LONG);

    187 }

    188

    189 public void onProviderDisabled(String provider) {

    190 // Requerido pela interface. No utilizado

    191 }

    192

    193 public void onProviderEnabled(String provider) {

    194 // Requerido pela interface. No utilizado

    195 }

    196

    197 public void onStatusChanged(String provider, int status, Bundle extras) {

    198 // Requerido pela interface. No utilizado

    199 }

    200 };

    No caso, obtemos os dados do GPS, atualizamos o banco e a interface e exibimos uma mensagem para o usurio.

    Porm, pode ocorrer do usurio sair da tela enquanto a requisio estiver sendo processada. Neste caso, sensato

    cancel-la para evitar problemas. Assim, atualize o mtodo 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 opo de obter os dados do GPS se o restaurante j tiver sido

    salvo no banco de dados. Adicione a implementao do mtodo 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 no 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

    tnhamos at ento (que fazia o aplicativo encerrar inesperadamente e no funcionava novamente caso fosse

    pressionada a tecla voltar no formulrio sem preencher nada provavelmente voc nem tenha percebido).

    Primeiramente, para alterar o cone, devemos encontrar um cone no formato PNG com dimenses de 72x72px. A

    Google possui algumas orientaes de design para a confeco deste cone. sempre bom seguir tais

    recomendaes 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 no preenchemos nada e apertamos a tecla voltar, o

    aplicativo tenta salvar valores nulos no banco de dados, o que acaba ocasionando uma exceo (exception). Para

    corrigir, basta controlar para que a gravao ou atualizao no seja feita caso os valores bsicos (nome, endereo e

    tipo) no sejam preenchidos. Assim, altere o mtodo 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. Alm disso, voc tambm precisar de uma chave de desenvolvimento para que possa ocorrer a

    integrao. Neste link, voc obtm as informaes necessrias sobre como conseguir esta chave.

    Comeando, vamos adicionar a opo para a visualizao 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 instalao do Android SDK.

    Em seguida, vamos modificar o mtodo onPrepareOptionsMenu() no FormularioDetalhes para somente habilitar

    esta opo quando j houverem as informaes bsicas do restaurante (edio).

    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 prximo passo criar uma Activity que ser responsvel por exibir o nosso mapa. Porm, antes disso, vamos

    informar ao arquivo AndroidManifest.xml que nossa aplicao 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 mtodo onCreate(), onde

    simplesmente atribuiremos seu layout, e isRouteDisplayed(), mtodo 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 prximo passo modificar o mtodo 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, Conexo com a Internet indisponvel, Toast.LENGTH_LONG

  • Alarmes Veremos como criar um alarme para nos avisar a hora do almoo (tudo a ver com o aplicativo de restaurante, no?).

    O primeiro passo criarmos uma forma para o usurio configurar o horrio em que ele deseja ser avisado do

    almoo. Poderamos definir isto em uma Activity, mas esta opo soa mais como uma configurao. 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 mtodos obterHora() e obterMinuto() servem para extrair a parte inteira do horrio que ser armazenado como

    uma string 00:00. Temos trs verses do construtor da classe, que no final sempre referenciam o terceiro. Isso

    devido superclasse. Ainda no construtor, definimos os nomes dos botes na janela de configurao de horrio.

    onCreateDialogView() devolve um objeto View com a tela criada. Poderamos aqui definirmos um layout, mas

    simplesmente devolvemos um widget TimePicker. O mtodo onBindDialogView() chamado aps o

    onCreateDialogView() encarregado de preencher a caixa de dilogo. onDialogClose(), como o prprio nome diz,

    chamado quando a janelinha encerrada. Caso o usurio pressione o boto de confirmar (condio positiveResult),

    o valor armazenado em SharedPreferences. O mtodo onGetDefaultValue() utilizado para a converso interna do

    Android para o tipo do objeto. Por ltimo, o mtodo onSetInitialValue(), como o prprio nome diz, atribui um valor

    padro. Ele verifica se h algum valor j salvo, ou padro ou ento atribui 00:00.

    O prximo passo adicionar a opo para a configurao do alarme pelo usurio. 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 opo adicionada, do tipo CheckBoxPreference no tem muito segredo a segunda, foi a que definimos

    na classe PreferenciaH0rario. Configuramos seu valor padro para 12:00 e definimos que ela depende da opo

    alarme, ou seja, ela s estar habilitada caso alarme tambm esteja habilitada.

    Neste projeto vamos utilizar o AlarmManager para gerenciar o nosso alarme. Porm, ele tem uma falha: toda vez

    que o celular desligado, ao ligar novamente os alarmes no so configurados. Para resolver isso, vamos criar a

    classe ReceptorBoot para realizar essa configurao 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 mtodo 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

    Alm disso, adicione tambm a permisso para obter o sinal de boot completo do sistema.

    12

  • Precisamos agora tratar as preferncias do usurio para configurar o alarme. Quando o usurio ativar o checkbox do

    alarme, precisamos ativar o alarme no tempo selecionado. Quando o usurio 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 mtodos 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 }

    Tambm atualize o mtodo onReceive():

    15 @Override

    16 public void onReceive(Context context, Intent intent) {

    17 configurarAlarme(context);

    18 }

    Bem, no cdigo listado acima, primeiramente, ao receber o sinal do boot (mtodo onReceive()), configuramos o

    alarme, atravs do mtodo configurarAlarme(). Neste mtodo, obtemos o AlarmManager, e obtemos as

    preferncias do usurio para o alarme (se existirem), e a montamos em um objeto do tipo Calendar. Caso alarme

    seja anterior ao horrio atual, adicionamos um dia a ele e configuramos para repeti-lo diariamente. J no mtodo

    cancelarAlarme(), cancelamos o alarme vinculado ao contexto, obtendo o AlarmManager e obtendo um objeto

    PendingIntent (como se fosse uma tarefa pendente) com o mtodo obterIntentPendente().

    No cdigo que temos at agora, o alarme s armado na inicializao do sistema. Para que ele funcione da maneira

    como desejamos, precisamos adicionar alguns mtodo 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 tambm 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 almoo!. 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 tambm 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!

  • Notificaes No tutorial anterior vimos como utilizar alarmes em nossos aplicativos utilizando o AlarmManager. Porm, o alarme

    que definimos era exibido em tela cheia e, talvez, isso nem sempre interessante. Agora veremos como adicionar a

    opo para o caso do usurio preferir uma notificao simples em vez de exibir ela em tela cheia.

    O primeiro passo adicionarmos mais essa opo no menu de opes. Assim, edite o arquivo preferencias.xml para

    adicionar um novo CheckBoxPreference.

    1

    2

    9

    13

    19

    25

    Por fim, vamos editar o mtodo onReceive da classe ReceptorAlarme para realizar a exibio da notificao, caso

    esta opo 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 Almoo!", System.currentTimeMillis());

    23 PendingIntent i = PendingIntent.getActivity(context, 0, new Intent(context, AlarmeActivity.class), 0);

    24 nota.setLatestEventInfo(context, "Lista de Restaurantes", "Hora do Almoo! 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 operao, ser necessrio adicionarmos um atributo na classe, que basicamente um nmero nico para

    diferenciar esta notificao de outras que, eventualmente, venhamos utilizar em nosso aplicativo.

    O cone referenciado no aplicativo o cone de notificao do GTalk, que pode ser obtido no diretrio de instalao

    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.

  • Internacionalizao (i18n) Veremos como traduzir nossa aplicao para que ela se torne multi-linguagem. Isso bastante interessante caso

    voc tenha a inteno de colocar sua aplicao l no Google Play.

    Bom, o grande segredo da internacionalizao consiste na pasta values do projeto. Nessa pasta, no caso do nosso

    projeto, temos hoje o arquivo arrays.xml e strings.xml. Esses arquivos iro conter valores correspondentes a cada

    um dos idiomas que nossa aplicao suportar. Hoje, temos nossa aplicao com o idioma padro Portugus. Como

    o emulador do Android 2.2 no tem o idioma portugus, vamos deix-lo como padro mesmo, e adicionar suporte

    ao idioma Espanhol1

    Para incio de conversa, vamos preparar nosso aplicativo para a internacionalizao. Modifique o arquivo strings.xml

    para que contenha os seguintes valores:

    1

    2

    3 Hello World, ListaRestaurantes!

    4 Lista de Restaurantes

    5 Nome:

    6 Endereo:

    7 Tipo:

    8 Rodzio

    9 Fast Food

    10 A Domiclio

    11 Localizao

    12 (no atribuda)

    13 Anotaes

    14 Conta do Twitter

    15 Hora do Almoo!

    16 Descartado.

    17 Conexo com a Internet indisponvel

    18 Localizao salva

    19 Definir

    20 Cancelar

    21 Hora do Almoo! Est com fome?

    22 Erro manipulando timeline Twitter

    23 Erro enviando dados para a Activity

    24 Timeline Twitter

    25 Salvar Localizao

    26 Exibir Mapa

    27 Adicionar

    28 Configuraes

    29 Modo de Listagem

    30 Escolha o modo de listagem a ser utilizado

    31 Escolha o modo de listagem

    32 Tocar Alarme no Almoo

    33 Marque se deseja ser informado sobre a hora do almoo

    34 Horrio do Alarme do Almoo

    35 Configure seu horrio desejado para o alarme

    36 Ativar Notificao

    37 Marque caso deseje um cone na barra de status, ou desmarque para a notificao em tela cheia

    38

    Basicamente, definimos alguns alias para as strings do nosso aplicativo. Porm, em alguns trechos do cdigo, ainda

    temos strings como constantes de texto. Para isso, vamos alter-los para utilizar os resources deste arquivo.

    Primeiramente, nos formulrios e menus em XML, vamos alterar os arquivos form_detalhes.xml, tanto na pasta

    layout

    1 As sentenas mostradas foram todas traduzidas com o auxlio do Google Tradutor e podem no 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

    alm 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, tambm 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 aplicao seja executada agora, ela deve rodar normalmente como rodava antes. Agora, vamos criar os

    arquivos relativos ao novo idioma. Crie um novo diretrio 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 Direccin, Ascendente

    8 Por Direccin, 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 Direccin:

    7 Tipo:

    8 Rotacin

    9 Comida Rpida

    10 Ubicacin

    11 Posicin

    12 (sin asignar)

    13 Anotaciones

    14 Cuenta de Twitter

    15 La Hora del Almuerzo!

    16 Tirado.

    17 Conexin com la Internet no est disponible

    18 Posicin salva

    19 Definir

    20 Cancelar

    21 La Hora del Almuerzo! Tienes hambre?

    22 Error de Lnea de Tiempo de Twitter

    23 Erros al enviar datos a la Activity

    24 Lnea de Tiempo de Twitter

    25 Guardar Posicin

    26 Ver Mapa

    27 Aadir

    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 informacin sobre la hora del almuerzo

    34 La Hora del Almuerzo

    35 Ajuste el tiempo deseado para el alarma

    36 Habilitar Notificacin

    37 Seleccione si desea un icono en la barra de estado o borrar la notificacin 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 Espaol.

  • Widgets Pra quem no sabe, widgets uma espcie de miniatura do aplicativo que voc pode deixar em uma das reas de

    trabalho do Android, colocando disposio do usurio informaes de maneira mais rpida e prtica. O widget

    tambm pode redirecionar o usurio para o aplicativo principal, funcionando como uma espcie 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 prximo passo criarmos uma classe para gerenciar o contedo 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 mnimas, o tempo de atualizao das informaes 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 opes so os mesmos da aplicao no menu (app_name e ic_launcher). Alm disso, definimos que o

    widget realizar operaes de atualizao e que suas propriedades esto definidas no arquivo provedor_widget

    dentro da pasta xml.

    Por fim, vamos implementar o mtodo onUpdate() para a classe WidgetApp. este mtodo 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 cdigo:

    Cria um objeto RemoteView, que nos permite modificar o widget;

    Estabelece uma conexo com o banco de dados;

    Verifica quantos restaurantes salvos existem;

    Carrega um restaurante aleatrio (por isso o uso de Math.random());

    Exibe o nome do restaurante, ou uma mensagem dizendo que no 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 portugus e em espanhol (idiomas que minha aplicao suporta).

    string.xml em portugus:

    38 Nenhum registro.

    string.xml em espanhol:

    38 Ningn registro.

    E pronto! Para ativar o widget, clique e segure sobre a rea de trabalho para aparecer o menu e a opo 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 boto que mudar o restaurante que exibido, mostrando outro aleatoriamente e;

    (2) ao tocar sobre o nome do restaurante no widget, aberto o formulrio para que voc possa ver as outras

    informaes sobre aquele restaurante.

    O primeiro passo adicionarmos o boto ao layout de nosso widget. Dessa forma, abra o arquivo widget.xml que

    est em res/layout e faa a adio da imagem do boto, do tipo ImageButton:

    1

    2

    6

    13

    19

    Os atributos, no ponto em que estamos, no devem ser nenhuma surpresa. Neste ponto, j deve ser possvel v-lo

    no layout ao recompilar a aplicao.

  • O arquivo de imagem do cone pode ser baixado aqui.

    No tutorial anterior, toda a nossa lgica de consulta ao banco estava dentro do mtodo onUpdate() do nosso widget.

    Isso funciona bem, mas, caso a lgica se expanda muito, corremos o risco da atualizao demorar mais que o

    esperado e comprometer o desempenho da aplicao, j que o mtodo chamado em nossa thread principal.

    Para melhorar isso, vamos novamente fazer uso de um IntentService que nos possibilita a realizar a consulta de

    modo assncrono, 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 lgica que estvamos

    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 lgica para o nosso WidgetService. O prximo passo adicionar esse nosso

    servio 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 faa uso do nosso servio.

    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 boto e no nome do

    restaurante. Primeiramente, vamos fazer com que um novo restaurante seja exibido caso seja acionado o boto.

    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 mtodo 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 boto for acionado, o nosso servio 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

    formulrio. Para isso, precisaremos de realizar algumas mudanas sutis no mtodo onHandleIntent(). As principais

    so que, alm do nome, precisaremos tambm do identificador, pois atravs dele iremos carregar os dados no

    formulrio. Alm disso, tambm utilizaremos um PendingIntent para acionar a exibio do formulrio.

    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 Ligaes (Chamadas) Vamos ver agora como realizar chamadas.

    Nesta ltima sesso de modificaes em nosso aplicativo, vamos adicionar um campo denominado telefone aos

    nossos restaurantes e, a partir dele, vamos possibilitar que o usurio faa uma chamada diretamente do nosso

    aplicativo.

    Comeando, temos inicialmente que atualizar o nosso modelo de dados para que armazene o novo dado necessrio

    (o nmero de telefone). Primeiramente, modifique o mtodo 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 mtodo onUpgrade() para atualizar o modelo de dados do banco, caso o usurio esteja vindo de

    uma verso 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 mtodos inserir(), atualizar(), obterTodos() e obterPorId() para adicionar o

    campo telefone a eles tambm.

    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 alteraes nesta classe, crie o mtodo obterTelefone():

    114 public String obterTelefone(Cursor c) {

    115 return c.getString(8);

    116 }

    Pronto. Com relao a persistncia, j estamos aptos a prosseguir. Vamos agora adicionar o campo telefone aos

    nossos formulrios. 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 aps o campo de endereo, no arquivo

    res/layout/form_detalhes.xml.

    14

    15

    16

    17

    Agora no res/layout-land/form_detalhes.xml. Tambm, logo depois do trecho do campo de endereo.

    16

    17

    18

    20

    Pronto. Nossas modificaes agora sero na classe FormularioDetalhes. Primeiramente adicione o atributo telefone

    classe:

  • 25 EditText telefone = null;

    Agora, precisamos atualizar os mtodos onCreate(), salvar(), carregar(), onSaveInstanceState() e

    onRestoreInstanceState(). Pode parecer bastante coisa, mas so 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 alteraes foram mnimas?

    Prosseguindo, precisamos dizer ao Android que nossa aplicao deseja realizar chamadas. Para isso, adicione a

    seguinte linha s permisses no arquivo AndroidManifest.xml.

    13

    Agora, vamos criar a opo ao menu para realizar as chamadas. Edite o arquivo res/menu/opcao_detalhes.xml para

    acomodar a nova opo ( 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 instalao do Android e devidamente

    renomeado para chamada.png.

    Agora precisamos definir as novas strings utilizadas no formulrio e no menu. Adicione-as ao arquivo

    res/values/strings.xml

    39 Telefone:

    40 Telefonar

    e no res/values-es/strings.xml.

    39 Telfono:

    40 Llamar

    Por fim, vamos fazer com que a opo de menu realize a chamada. Adicione o seguinte trecho aos encadeamentos

    de ifs no mtodo 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 aplicao para conferir as modificaes.

  • Caso queira que a ligao seja realizada diretamente, sem exibir o discador, modifique, na linha 158 da ltima

    listagem de cdigo, ACTION_DIAL por ACTION_CALL.

  • Terminamos Parabns, voc chegou ao fim desse curso avanado 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 me sabe

    Nem o FBI conseguiu descobrir

    http://flavioaf.blogspot.com :: http://financeaccess.blogspot.com :: http://financasja.blogspot.com

    https://www.facebook.com/aulaparticulardoflavio