Escolar Documentos
Profissional Documentos
Cultura Documentos
Android Avanado
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
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\<Seu Usurio>). 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 Platformtools. 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\<usurio>\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.
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.blogspot.flavioaf.restaurante;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class GerenciadorRestaurantes extends SQLiteOpenHelper {
private static final String NOME_BANCO = "restaurantes.db";
private static final int VERSAO_SCHEMA = 1;
public GerenciadorRestaurantes(Context context) {
super(context, NOME_BANCO, null, VERSAO_SCHEMA);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
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
19
20
21
22
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);");
}
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;
40
67
68
69
70
71
@Override
public void onDestroy() {
super.onDestroy();
gerenciador.close();
}
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
28
29
30
31
32
33
34
35
36
public void inserir(String nome, String endereco, String tipo, String anotacoes) {
ContentValues valores = new ContentValues();
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
valores.put("anotacoes", anotacoes);
getWritableDatabase().insert("restaurantes", "nome", valores);
}
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
40
41
42
Precisaremos tambm de mtodos que nos forneam acesso a determinados campos do Cursor. Dessa forma,
adicione estes mtodos classe GerenciadorRestaurantes:
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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
54
55
56
listaRestaurantes = gerenciador.obterTodos();
startManagingCursor(listaRestaurantes);
adaptador = new AdaptadorRestaurante(listaRestaurantes);
lista.setAdapter(adaptador);
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
icone.setImageResource(R.drawable.entrega);
}
}
}
Por fim, vamos modificar todas as referncias ao ArrayList que tnhamos no nosso onListClick.
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
com.blogspot.flavioaf.restaurante.model.Restaurante;
android.app.TabActivity;
android.content.Context;
android.database.Cursor;
android.os.Bundle;
android.view.LayoutInflater;
android.view.View;
android.view.View.OnClickListener;
android.view.ViewGroup;
android.widget.AdapterView;
android.widget.AdapterView.OnItemClickListener;
android.widget.Button;
android.widget.CursorAdapter;
android.widget.EditText;
android.widget.ImageView;
android.widget.ListView;
android.widget.RadioGroup;
android.widget.TabHost.TabSpec;
android.widget.TextView;
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
}
getTabHost().setCurrentTab(1);
}
};
private OnClickListener onSave = new OnClickListener() {
public void onClick(View arg0) {
String tipo = null;
switch (tipos.getCheckedRadioButtonId()) {
case R.id.rodizio:
tipo = "rodizio";
break;
case R.id.fast_food:
tipo = "fast_food";
break;
case R.id.a_domicilio:
tipo = "a_domicilio";
break;
}
gerenciador.inserir(nome.getText().toString(), endereco.getText()
.toString(), tipo, anotacoes.getText().toString());
listaRestaurantes.requery();
}
};
class AdaptadorRestaurante extends CursorAdapter {
AdaptadorRestaurante(Cursor c) {
super(ListaRestaurantes.this, c);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag();
armazenador.popularFormulario(cursor, gerenciador);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = getLayoutInflater();
View linha = inflater.inflate(R.layout.linha, parent, false);
ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha);
linha.setTag(armazenador);
return linha;
}
}
static class ArmazenadorRestaurante {
private TextView nome = null;
private TextView endereco = null;
private ImageView icone = null;
ArmazenadorRestaurante(View linha) {
nome = (TextView) linha.findViewById(R.id.titulo);
endereco = (TextView) linha.findViewById(R.id.endereco);
icone = (ImageView) linha.findViewById(R.id.icone);
}
void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) {
nome.setText(gerenciador.obterNome(c));
endereco.setText(gerenciador.obterEndereco(c));
if (gerenciador.obterTipo(c).equals("rodizio")) {
icone.setImageResource(R.drawable.rodizio);
} else if (gerenciador.obterTipo(c).equals("fast_food")) {
icone.setImageResource(R.drawable.fast_food);
} else {
162
163
164
165
166
icone.setImageResource(R.drawable.entrega);
}
}
}
}
e GerenciadorRestaurantes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
android.content.ContentValues;
android.content.Context;
android.database.Cursor;
android.database.sqlite.SQLiteDatabase;
android.database.sqlite.SQLiteOpenHelper;
59
60
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.blogspot.flavioaf.restaurante;
import android.app.Activity;
import android.os.Bundle;
public class FormularioDetalhes extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
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
3
4
5
6
7
8
9
10
11
12
13
14
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
81
82
83
84
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
11
12
13
14
Agora, copie a busca aos widgets no formulrio do mtodo onCreate() do ListaRestaurantes para o
FormularioDetalhes, no mesmo local.
22
23
24
25
26
27
28
29
30
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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:
2
3
4
5
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
34
35
36
37
38
39
40
41
42
43
44
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gerenciador = new GerenciadorRestaurantes(this);
listaRestaurantes = gerenciador.obterTodos();
startManagingCursor(listaRestaurantes);
adaptador = new AdaptadorRestaurante(listaRestaurantes);
setListAdapter(adaptador);
}
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
Aps isso, vamos mudar o objeto onListClick para um onListItemClick(), onde vamos passar o valor do id para a outra
Activity:
53
54
55
56
57
58
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Intent i = new Intent(ListaRestaurantes.this, FormularioDetalhes.class);
i.putExtra(_ID, String.valueOf(id));
startActivity(i);
}
18
Este atributo ser nulo se estivermos adicionando um novo restaurante, ou o identificador, caso estejamos editando
um restaurante.
36
37
38
39
40
@Override
public void onDestroy() {
super.onDestroy();
gerenciador.close();
}
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
62
63
64
65
66
67
35
36
37
38
39
idRestaurante = getIntent().getStringExtra(ListaRestaurantes._ID);
if (idRestaurante != null) {
carregar();
}
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
4
5
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
63
64
65
66
67
@Override
public boolean onCreateOptionsMenu(Menu menu) {
new MenuInflater(this).inflate(R.menu.opcao, menu);
return super.onCreateOptionsMenu(menu);
}
70
71
72
73
74
75
76
77
78
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.adicionar) {
startActivity(new Intent(ListaRestaurantes.this, FormularioDetalhes.class));
return true;
}
return super.onOptionsItemSelected(item);
}
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
41
42
43
44
45
46
47
48
49
50
public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes)
{
ContentValues valores = new ContentValues();
String[] argumentos = {id};
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
valores.put("anotacoes", anotacoes);
getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos);
}
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Verifique se no h cdigo duplicado, corrija os imports (Ctrl + Shift + O) e pronto! J temos nossa aplicao
funcionando!
Restaurante.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.blogspot.flavioaf.restaurante.model;
public class Restaurante {
private
private
private
private
String
String
String
String
nome = "";
endereco = "";
tipo = "";
anotacoes = "";
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
}
public String getAnotacoes() {
return anotacoes;
}
public void setAnotacoes(String anotacoes) {
this.anotacoes = anotacoes;
}
@Override
public String toString() {
return getNome();
}
}
FormularioDetalhes.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
import
import
import
android.app.Activity;
android.database.Cursor;
android.os.Bundle;
android.view.View;
android.view.View.OnClickListener;
android.widget.Button;
android.widget.EditText;
android.widget.RadioGroup;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
GerenciadorRestaurantes.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
android.content.ContentValues;
android.content.Context;
android.database.Cursor;
android.database.sqlite.SQLiteDatabase;
android.database.sqlite.SQLiteOpenHelper;
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public void inserir(String nome, String endereco, String tipo, String anotacoes) {
ContentValues valores = new ContentValues();
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
valores.put("anotacoes", anotacoes);
getWritableDatabase().insert("restaurantes", "nome", valores);
}
public void atualizar(String id, String nome, String endereco, String tipo, String
anotacoes) {
ContentValues valores = new ContentValues();
String[] argumentos = {id};
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
valores.put("anotacoes", anotacoes);
getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos);
}
public Cursor obterTodos() {
return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " +
"anotacoes FROM restaurantes ORDER BY nome", null);
}
public String obterNome(Cursor c) {
return c.getString(1);
}
public String obterEndereco(Cursor c) {
return c.getString(2);
}
public String obterTipo(Cursor c) {
return c.getString(3);
}
public String obterAnotacoes(Cursor c) {
return c.getString(4);
}
public Cursor obterPorId(String id) {
String[] argumentos = {id};
return getReadableDatabase().rawQuery(
"SELECT _id, nome, endereco, tipo, anotacoes " +
"FROM restaurantes WHERE _id = ?", argumentos);
}
}
ListaRestaurantes.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
android.app.ListActivity;
android.content.Context;
android.content.Intent;
android.database.Cursor;
android.os.Bundle;
android.view.LayoutInflater;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.view.View;
android.view.ViewGroup;
android.widget.CursorAdapter;
android.widget.ImageView;
android.widget.ListView;
android.widget.TextView;
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
super(ListaRestaurantes.this, c);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag();
armazenador.popularFormulario(cursor, gerenciador);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = getLayoutInflater();
View linha = inflater.inflate(R.layout.linha, parent, false);
ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha);
linha.setTag(armazenador);
return linha;
}
}
static class ArmazenadorRestaurante {
private TextView nome = null;
private TextView endereco = null;
private ImageView icone = null;
ArmazenadorRestaurante(View linha) {
nome = (TextView) linha.findViewById(R.id.titulo);
endereco = (TextView) linha.findViewById(R.id.endereco);
icone = (ImageView) linha.findViewById(R.id.icone);
}
void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) {
nome.setText(gerenciador.obterNome(c));
endereco.setText(gerenciador.obterEndereco(c));
if (gerenciador.obterTipo(c).equals("rodizio")) {
icone.setImageResource(R.drawable.rodizio);
} else if (gerenciador.obterTipo(c).equals("fast_food")) {
icone.setImageResource(R.drawable.fast_food);
} else {
icone.setImageResource(R.drawable.entrega);
}
}
}
}
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
3
4
5
6
7
8
9
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:key="listagem"
android:title="Modo de Listagem"
android:summary="Escolha o modo de listagem a ser utilizado"
android:entries="@array/nomes_ordenacao"
android:entryValues="@array/opcoes_ordenacao"
android:dialogTitle="Escolha o modo de listagem" />
</PreferenceScreen>
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
5
6
7
8
9
10
11
12
13
14
15
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
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.blogspot.flavioaf.restaurante;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class EdicaoPreferencias extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferencias);
}
}
Tambm necessrio atualizar o arquivo AndroidManifest.xml, j que adicionamos uma nova Activity ao nosso
projeto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
4
5
6
7
8
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
59
60
61
62
63
64
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.adicionar) {
startActivity(new Intent(ListaRestaurantes.this, FormularioDetalhes.class));
return true;
} else if (item.getItemId() == R.id.prefs) {
startActivity(new Intent(this, EdicaoPreferencias.class));
65
66
67
68
69
return true;
}
return super.onOptionsItemSelected(item);
}
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
53
54
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
34
prefs = PreferenceManager.getDefaultSharedPreferences(this);
36
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);
1
2
3
4
5
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
78
79
80
81
82
83
84
85
86
87
30
31
32
33
34
35
36
37
38
39
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
gerenciador = new GerenciadorRestaurantes(this);
inicializarLista();
prefs.registerOnSharedPreferenceChangeListener(prefListener);
}
E ento, tambm fazemos uma chamada ao mtodo inicializarLista() dentro do nosso prefListener:
86
87
88
89
90
91
92
93
94
100
101
102
103
104
105
106
107
108
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("nome", nome.getText().toString());
outState.putString("endereco", endereco.getText().toString());
outState.putString("anotacoes", anotacoes.getText().toString());
outState.putInt("tipo", tipos.getCheckedRadioButtonId());
}
Pronto. J fizemos com que os valores do formulrio fossem salvos. Agora, vamos implementar o mtodo
onRestoreInstanceState() que devolver os dados no formulrio.
110
111
112
113
114
115
116
117
118
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
nome.setText(savedInstanceState.getString("nome"));
endereco.setText(savedInstanceState.getString("endereco"));
anotacoes.setText(savedInstanceState.getString("anotacoes"));
tipos.check(savedInstanceState.getInt("tipo"));
}
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
android:text="Fast Food"/>
<RadioButton android:id="@+id/a_domicilio"
android:text="A Domiclio"/>
</RadioGroup>
<TextView android:text="Anotaes:"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<EditText android:id="@+id/anotacoes"
android:singleLine="false"
android:gravity="top"
android:lines="4"
android:scrollHorizontally="false"
android:maxLines="4"
android:maxWidth="140sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/salvar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Salvar"/>
</LinearLayout>
</TableRow>
</TableLayout>
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!
18
19
20
21
22
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT);");
}
Alm disso, vamos alterar a verso do Schema do banco, para que ele seja atualizado em verses anteriores:
12
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
25
26
27
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT");
}
Para concluir as modificaes nesta classe, vamos atualizar os mtodo obterTodos(), obterPorId(), inserir() e
atualizar(). Alm disso, tambm vamos adicionar o mtodo obterTwitter():
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public void inserir(String nome, String endereco, String tipo, String anotacoes, String
twitter) {
ContentValues valores = new ContentValues();
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
valores.put("anotacoes", anotacoes);
valores.put("twitter", twitter);
getWritableDatabase().insert("restaurantes", "nome", valores);
}
public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes,
String twitter) {
ContentValues valores = new ContentValues();
String[] argumentos = {id};
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
valores.put("anotacoes", anotacoes);
valores.put("twitter", twitter);
getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos);
}
public Cursor obterTodos(String ordenacao) {
return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " +
"anotacoes, twitter FROM restaurantes ORDER BY " + ordenacao, null);
}
public String obterTwitter(Cursor c) {
return c.getString(5);
}
public Cursor obterPorId(String id) {
String[] argumentos = {id};
return getReadableDatabase().rawQuery(
"SELECT _id, nome, endereco, tipo, anotacoes, twitter " +
"FROM restaurantes WHERE _id = ?", argumentos);
}
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
android:layout_height="wrap_content"
android:text="Salvar"/>
</TableLayout>
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/layoutland/form_detalhes.xml):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
24
39
130
twitter.setText(gerenciador.obterTwitter(c));
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
4
5
6
android:icon="@drawable/twitter"/>
</menu>
92
93
94
95
96
97
@Override
public boolean onCreateOptionsMenu(Menu menu) {
new MenuInflater(this).inflate(R.menu.opcao_detalhes, menu);
return super.onCreateOptionsMenu(menu);
};
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@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).show();
}
return true;
}
return super.onOptionsItemSelected(item);
}
private boolean redeDisponivel() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
return (info != null);
}
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
<activity android:name=".TwitterActivity">
</activity>
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
22
23
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
140
141
142
22
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
status = (StatusInstancia) getLastNonConfigurationInstance();
if (status == null) {
status = new StatusInstancia();
status.tarefa = new TarefaTwitter(this);
status.tarefa.execute(getIntent().getStringExtra(PERFIL));
} else {
if (status.tarefa != null) {
status.tarefa.anexar(this);
}
if (status.tweets != null) {
atribuirTweets(status.tweets);
}
}
}
@Override
public Object onRetainNonConfigurationInstance() {
if (status.tarefa != null) {
status.tarefa.desanexar();
}
return status;
}
private void atribuirTweets(List<Status> tweets) {
status.tweets = tweets;
setListAdapter(new AdaptadorTweets(tweets));
}
21
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.blogspot.flavioaf.restaurante;
import android.app.IntentService;
import android.content.Intent;
public class TwitterService extends IntentService {
public TwitterService() {
super("TwitterService");
}
@Override
protected void onHandleIntent(Intent intent) {
}
}
Em seguida, vamos adicionar um novo n service l no arquivo AndroidManifest.xml logo aps os ns activity,
dentro do n application.
29
30
<service android:name=".TwitterService">
</service>
18
19
20
21
22
23
24
25
26
27
@Override
protected void onHandleIntent(Intent intent) {
Twitter t = new TwitterFactory().getInstance();
try {
List<Status> resultado = t.getUserTimeline(intent.getStringExtra(PERFIL_EXTRA));
} catch (Exception ex) {
Log.e("ListaRestaurantes", "Erro manipulando timeline twitter", ex);
}
}
Adicione tambm o atributo da classe referenciado no mtodo, que servir para obter o perfil do Twitter que
obteremos os tweets.
14
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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Override
protected void onHandleIntent(Intent intent) {
Twitter t = new TwitterFactory().getInstance();
Messenger messenger = (Messenger) intent.getExtras().get(MESSENGER_EXTRA);
Message msg = Message.obtain();
try {
List<Status> resultado = t.getUserTimeline(intent.getStringExtra(PERFIL_EXTRA));
msg.arg1 = Activity.RESULT_OK;
msg.obj = resultado;
} catch (Exception ex) {
Log.e("ListaRestaurantes", "Erro manipulando timeline twitter", ex);
msg.arg1 = Activity.RESULT_CANCELED;
msg.obj = ex;
}
try {
messenger.send(msg);
} catch (Exception ex) {
Log.w("ListaRestaurantes", "Erro enviando dados para a Activity", ex);
}
}
18
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
}
void desanexar() {
activity = null;
}
@Override
public void handleMessage(Message msg) {
if (msg.arg1 == RESULT_OK) {
activity.atribuirTweets((List<Status>) msg.obj);
} else {
activity.atirarErro((Exception) msg.obj);
}
}
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
123
124
125
44
45
46
47
48
49
50
@Override
public Object onRetainNonConfigurationInstance() {
if (status.handler != null) {
status.handler.desanexar();
}
return status;
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
status = (StatusInstancia) getLastNonConfigurationInstance();
if (status == null) {
status = new StatusInstancia();
status.handler = new HandlerTwitter(this);
Intent i = new Intent(this, TwitterService.class);
i.putExtra(TwitterService.PERFIL_EXTRA, getIntent().getStringExtra(PERFIL));
i.putExtra(TwitterService.MESSENGER_EXTRA, new Messenger(status.handler));
startService(i);
} else {
if (status.handler != null) {
42
43
44
45
46
47
48
49
status.handler.anexar(this);
}
if (status.tweets != null) {
atribuirTweets(status.tweets);
}
}
}
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
19
20
21
22
23
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT," +
" latitude REAL, longitude REAL);");
}
12
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
26
27
28
29
30
31
32
33
34
35
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT");
}
if (oldVersion < 3) {
db.execSQL("ALTER TABLE restaurantes ADD COLUMN latitude REAL");
db.execSQL("ALTER TABLE restaurantes ADD COLUMN longitude REAL");
}
}
Precisaremos atualizar tambm os mtodos obterTodos() e obterPorId() para abranger tambm os novos campos.
62
63
64
65
66
67
68
69
70
71
72
73
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
63
64
65
66
67
68
69
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
99
100
101
102
103
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
tipo, anotacoes.getText().toString(),
twitter.getText().toString());
} else {
gerenciador.atualizar(idRestaurante,
nome.getText().toString(),
endereco.getText().toString(),
tipo, anotacoes.getText().toString(),
twitter.getText().toString());
}
finish();
}
Remova as linhas que fazem referncia ao boto no mtodo onCreate() e implemente o mtodo onPause() que faz a
chamada ao mtodo salvar().
48
49
50
51
52
53
@Override
public void onPause() {
salvar();
super.onPause();
}
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
android:maxLines="2"
android:maxWidth="200sp"
android:hint="Anotaes"
android:layout_marginTop="4dip"/>
<EditText android:id="@+id/twitter"
android:layout_span="2"
android:hint="Conta do Twitter" />
</TableLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
</LinearLayout>
</TableRow>
</TableLayout>
24
40
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
4
5
6
7
8
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
50
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@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).show();
}
return true;
} else if (item.getItemId() == R.id.localizacao) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0, 0, onLocationChange);
}
return super.onOptionsItemSelected(item);
}
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
String.valueOf(location.getLongitude()));
locationManager.removeUpdates(onLocationChange);
Toast.makeText(FormularioDetalhes.this, "Localizao salva", Toast.LENGTH_LONG);
}
public void onProviderDisabled(String provider) {
// Requerido pela interface. No utilizado
}
public void onProviderEnabled(String provider) {
// Requerido pela interface. No utilizado
}
public void onStatusChanged(String provider, int status, Bundle extras) {
// Requerido pela interface. No utilizado
}
};
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
56
57
58
59
60
61
@Override
public void onPause() {
salvar();
locationManager.removeUpdates(onLocationChange);
super.onPause();
}
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
70
71
72
73
74
75
76
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (idRestaurante == null) {
menu.findItem(R.id.localizacao).setEnabled(false);
}
return super.onPrepareOptionsMenu(menu);
}
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:
Onde <latitude> e <longitude> devem ser os valores desejados (voc consegue pegar os valores de um lugar
facilmente usando o Google Maps).
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
break;
case R.id.fast_food:
tipo = "fast_food";
break;
case R.id.a_domicilio:
tipo = "a_domicilio";
break;
}
if (tipo != null && endereco.getText().toString() != null &&
nome.getText().toString() != null) {
if (idRestaurante == null) {
gerenciador.inserir(nome.getText().toString(),
endereco.getText().toString(),
tipo, anotacoes.getText().toString(),
twitter.getText().toString());
} else {
gerenciador.atualizar(idRestaurante,
nome.getText().toString(),
endereco.getText().toString(),
tipo, anotacoes.getText().toString(),
twitter.getText().toString());
}
}
finish();
}
Pronto!
1
2
3
4
5
6
7
8
9
10
11
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
76
77
78
79
80
81
82
83
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (idRestaurante == null) {
menu.findItem(R.id.localizacao).setEnabled(false);
menu.findItem(R.id.mapa).setEnabled(false);
}
return super.onPrepareOptionsMenu(menu);
}
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
<uses-library android:name="com.google.android.maps"/>
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.blogspot.flavioaf.restaurante;
import android.os.Bundle;
import com.google.android.maps.MapActivity;
public class MapaRestaurante extends MapActivity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.mapa);
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
import
android.content.Context;
android.content.res.TypedArray;
android.preference.DialogPreference;
android.util.AttributeSet;
android.view.View;
android.widget.TimePicker;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
if (positiveResult) {
ultimaHora = picker.getCurrentHour();
ultimoMinuto = picker.getCurrentMinute();
String tempo = String.valueOf(ultimaHora) + ":" + String.valueOf(ultimoMinuto);
if (callChangeListener(tempo)) {
persistString(tempo);
}
}
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue,
Object defaultValue) {
String tempo = null;
if (restorePersistedValue) {
if (defaultValue == null) {
tempo = getPersistedString("00:00");
} else {
tempo = getPersistedString(defaultValue.toString());
}
} else {
tempo = defaultValue.toString();
}
ultimaHora = obterHora(tempo);
ultimoMinuto = obterMinuto(tempo);
}
}
1
2
3
4
5
6
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:key="listagem"
android:title="Modo de Listagem"
android:summary="Escolha o modo de listagem a ser utilizado"
android:entries="@array/nomes_ordenacao"
7
8
9
10
11
12
13
14
15
16
17
18
19
android:entryValues="@array/opcoes_ordenacao"
android:dialogTitle="Escolha o modo de listagem" />
<CheckBoxPreference
android:key="alarme"
android:title="Tocar Alarme no Almoo"
android:summary="Marque se deseja ser informado sobre a hora do almoo"/>
<com.blogspot.flavioaf.restaurante.PreferenciaHorario
android:key="horario_alarme"
android:title="Horrio do Alarme do Almoo"
android:defaultValue="12:00"
android:summary="Configure seu horrio desejado para o alarme"
android:dependency="alarme"/>
</PreferenceScreen>
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
2
3
4
5
6
7
8
9
10
11
12
13
package com.blogspot.flavioaf.restaurante;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class ReceptorBoot extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
A tarefa do ReceptorBoot ser realizado no mtodo onReceive(). Por enquanto, coloque-o pra descansar. J j
voltamos nele.
Prosseguindo, precisamos adicionar o n <receiver> no arquivo AndroidManifest.xml para que ele possa atuar no
boot. Adicione-o ao final do n application.
39
40
41
42
43
44
<receiver android:name=".ReceptorBoot"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
Alm disso, adicione tambm a permisso para obter o sinal de boot completo do sistema.
12
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
15
16
17
18
@Override
public void onReceive(Context context, Intent intent) {
configurarAlarme(context);
}
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
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Override
protected void onResume() {
super.onResume();
preferencias = PreferenceManager.getDefaultSharedPreferences(this);
preferencias.registerOnSharedPreferenceChangeListener(onChange);
}
@Override
protected void onPause() {
preferencias.unregisterOnSharedPreferenceChangeListener(onChange);
super.onPause();
}
OnSharedPreferenceChangeListener onChange = new
SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if ("alarme".equals(key)) {
boolean habilitado = preferencias.getBoolean(key, false);
int flag = (habilitado ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
ComponentName componente = new ComponentName(EdicaoPreferencias.this,
ReceptorBoot.class);
getPackageManager().setComponentEnabledSetting(componente, flag,
PackageManager.DONT_KILL_APP);
if (habilitado) {
ReceptorBoot.configurarAlarme(EdicaoPreferencias.this);
} else {
ReceptorBoot.cancelarAlarme(EdicaoPreferencias.this);
}
} else if ("horario_alarme".equals(key)) {
ReceptorBoot.cancelarAlarme(EdicaoPreferencias.this);
ReceptorBoot.configurarAlarme(EdicaoPreferencias.this);
}
}
};
13
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
3
4
5
6
7
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
2
3
4
5
6
7
8
9
10
11
12
13
package com.blogspot.flavioaf.restaurante;
import android.app.Activity;
import android.os.Bundle;
public class AlarmeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alarme);
}
}
Crie tambm uma classe chamada ReceptorAlarme que ser encarregada de iniciar a AlarmeActivity.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.blogspot.flavioaf.restaurante;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class ReceptorAlarme extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, AlarmeActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
Para terminarmos falta somente adicionarmos esse ltimo receptor no AndroidManifest.xml. Adicione-o no fim do
n application.
45
46
<receiver android:name=".ReceptorAlarme">
</receiver>
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:key="listagem"
android:title="Modo de Listagem"
android:summary="Escolha o modo de listagem a ser utilizado"
android:entries="@array/nomes_ordenacao"
android:entryValues="@array/opcoes_ordenacao"
android:dialogTitle="Escolha o modo de listagem" />
<CheckBoxPreference
android:key="alarme"
android:title="Tocar Alarme no Almoo"
android:summary="Marque se deseja ser informado sobre a hora do almoo"/>
<com.blogspot.flavioaf.restaurante.PreferenciaHorario
android:key="horario_alarme"
android:title="Horrio do Alarme do Almoo"
android:defaultValue="12:00"
android:summary="Configure seu horrio desejado para o alarme"
android:dependency="alarme"/>
<CheckBoxPreference
android:key="usar_notificacao"
android:title="Ativar Notifocao"
android:defaultValue="true"
android:summary="Marque caso deseje um cone na barra de status, ou desmarque para a
notificao em tela cheia"
android:dependency="alarme"/>
</PreferenceScreen>
Por fim, vamos editar o mtodo onReceive da classe ReceptorAlarme para realizar a exibio da notificao, caso
esta opo esteja selecionada.
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences preferencias = PreferenceManager.getDefaultSharedPreferences(context);
boolean usarNotificacao = preferencias.getBoolean("usar_notificacao", true);
if (usarNotificacao) {
NotificationManager gerenciador = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification nota = new Notification(R.drawable.notificacao, "Hora do Almoo!",
System.currentTimeMillis());
PendingIntent i = PendingIntent.getActivity(context, 0, new Intent(context,
AlarmeActivity.class), 0);
nota.setLatestEventInfo(context, "Lista de Restaurantes", "Hora do Almoo! Est com
fome?", i);
nota.flags |= Notification.FLAG_AUTO_CANCEL;
gerenciador.notify(ID_NOTIFICACAO, nota);
} else {
Intent i = new Intent(context, AlarmeActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
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
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
As sentenas mostradas foram todas traduzidas com o auxlio do Google Tradutor e podem no estar totalmente corretas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<RadioButton android:id="@+id/fast_food"
android:text="@string/fast_food"/>
<RadioButton android:id="@+id/a_domicilio"
android:text="@string/a_domicilio"/>
</RadioGroup>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<EditText android:id="@+id/anotacoes"
android:singleLine="false"
android:gravity="top"
android:lines="4"
android:scrollHorizontally="false"
android:maxLines="4"
android:maxWidth="140sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/anotacoes" />
<EditText android:id="@+id/twitter"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/conta_twitter"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView android:text="@string/localizacao"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/localizacao"
android:text="@string/nao_atribuido"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</TableRow>
</TableLayout>
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
10
11
12
android:title="@string/exibir_mapa"
android:icon="@drawable/mapa"/>
</menu>
Nas nossas classes, tambm temos alguns trechos que precisam ser adaptados. Primeiramente na classe
FormularioDetalhes
61
136
219
na classe PreferenciaHorario
37
38
setPositiveButtonText(getContext().getString(R.string.definir));
setNegativeButtonText(getContext().getString(R.string.cancelar));
na classe ReceptorAlarme
23
25
nota.setLatestEventInfo(context, context.getString(R.string.app_name),
context.getString(R.string.notificacao), i);
e na classe TwitterService
36
44
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
5
6
7
8
9
10
11
12
13
14
15
16
17
</string-array>
<string-array name="opcoes_ordenacao">
<item>nome ASC</item>
<item>nome DESC</item>
<item>tipo, nome ASC</item>
<item>endereco ASC</item>
<item>endereco DESC</item>
</string-array>
</resources>
e strings.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
3
4
5
6
7
8
9
10
11
12
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
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
2
3
4
5
6
package com.blogspot.flavioaf.restaurante;
import android.appwidget.AppWidgetProvider;
public class WidgetAplicativo extends AppWidgetProvider {
}
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
3
4
5
6
7
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
47
48
49
50
51
52
53
54
<receiver android:name=".WidgetAplicativo"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/provedor_widget" />
</receiver>
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
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
ComponentName cn = new ComponentName(context, WidgetApp.class);
RemoteViews atualizarFrame = new RemoteViews("com.blogspot.flavioaf.restaurante",
R.layout.widget);
GerenciadorRestaurantes gerenciador = new GerenciadorRestaurantes(context);
try {
Cursor c = gerenciador.getReadableDatabase().rawQuery("SELECT COUNT(*) FROM
restaurantes", null);
c.moveToFirst();
int count = c.getInt(0);
c.close();
if (count > 0) {
int offset = (int) (count * Math.random());
String args[] = {String.valueOf(offset)};
c = gerenciador.getReadableDatabase().rawQuery("SELECT nome FROM restaurantes
29
30
31
32
33
34
35
36
37
38
39
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
string.xml em espanhol:
38
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.blogspot.flavioaf.restaurante;
import
import
import
import
import
import
import
android.app.IntentService;
android.app.PendingIntent;
android.appwidget.AppWidgetManager;
android.content.ComponentName;
android.content.Intent;
android.database.Cursor;
android.widget.RemoteViews;
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
<service android:name=".WidgetService"/>
Agora, vamos atualizar a classe WidgetAplicativo para que faa uso do nosso servio.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.blogspot.flavioaf.restaurante;
import
import
import
import
android.appwidget.AppWidgetManager;
android.appwidget.AppWidgetProvider;
android.content.Context;
android.content.Intent;
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
51
52
53
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
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Override
protected void onHandleIntent(Intent intent) {
ComponentName cn = new ComponentName(this, WidgetAplicativo.class);
RemoteViews atualizarFrame = new RemoteViews("com.blogspot.flavioaf.restaurante",
R.layout.widget);
GerenciadorRestaurantes gerenciador = new GerenciadorRestaurantes(this);
AppWidgetManager mgr = AppWidgetManager.getInstance(this);
try {
Cursor c = gerenciador.getReadableDatabase().rawQuery("SELECT COUNT(*) FROM
restaurantes", null);
c.moveToFirst();
int contador = c.getInt(0);
c.close();
if (contador > 0) {
int deslocamento = (int) (contador * Math.random());
String args[] = {String.valueOf(deslocamento)};
c = gerenciador.getReadableDatabase().rawQuery("SELECT _id, nome FROM
restaurantes LIMIT 1 OFFSET ?", args);
c.moveToFirst();
atualizarFrame.setTextViewText(R.id.nome, c.getString(1));
Intent i = new Intent(this, FormularioDetalhes.class);
i.putExtra(ListaRestaurantes._ID, c.getString(0));
PendingIntent pi = PendingIntent.getActivity(this, 0, i,
PendingIntent.FLAG_UPDATE_CURRENT);
atualizarFrame.setOnClickPendingIntent(R.id.nome, pi);
c.close();
} else {
atualizarFrame.setTextViewText(R.id.nome, getString(R.string.vazio));
}
} finally {
gerenciador.close();
}
Intent i = new Intent(this, WidgetService.class);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
atualizarFrame.setOnClickPendingIntent(R.id.proximo, pi);
mgr.updateAppWidget(cn, atualizarFrame);
}
E pronto!
1
2
3
4
5
6
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT," +
" latitude REAL, longitude REAL, telefone TEXT);");
}
Em seguida, altere o mtodo onUpgrade() para atualizar o modelo de dados do banco, caso o usurio esteja vindo de
uma verso anterior:
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT");
}
if (oldVersion < 3) {
db.execSQL("ALTER TABLE restaurantes ADD COLUMN latitude REAL");
db.execSQL("ALTER TABLE restaurantes ADD COLUMN longitude REAL");
}
if (oldVersion < 4) {
db.execSQL("ALTER TABLE restaurantes ADD COLUMN telefone TEXT");
}
}
Pronto. Agora, vamos procurar pelos mtodos inserir(), atualizar(), obterTodos() e obterPorId() para adicionar o
campo telefone a eles tambm.
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public void inserir(String nome, String endereco, String tipo, String anotacoes, String
twitter,
String telefone) {
ContentValues valores = new ContentValues();
valores.put("nome", nome);
valores.put("endereco", endereco);
valores.put("tipo", tipo);
valores.put("anotacoes", anotacoes);
valores.put("twitter", twitter);
valores.put("telefone", telefone);
getWritableDatabase().insert("restaurantes", "nome", valores);
}
public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes,
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
114
115
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
<TableRow>
<TextView android:text="@string/telefone"/>
<EditText android:id="@+id/telefone"/>
</TableRow>
16
17
18
19
20
<TableRow>
<TextView android:text="@string/telefone"/>
<EditText android:id="@+id/telefone"
android:layout_span="2" />
</TableRow>
Pronto. Nossas modificaes agora sero na classe FormularioDetalhes. Primeiramente adicione o atributo telefone
classe:
25
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.form_detalhes);
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
gerenciador = new GerenciadorRestaurantes(this);
nome = (EditText) findViewById(R.id.nome);
endereco = (EditText) findViewById(R.id.end);
telefone = (EditText) findViewById(R.id.telefone);
anotacoes = (EditText) findViewById(R.id.anotacoes);
twitter = (EditText) findViewById(R.id.twitter);
tipos = (RadioGroup) findViewById(R.id.tipos);
localizacao = (TextView) findViewById(R.id.localizacao);
idRestaurante = getIntent().getStringExtra(ListaRestaurantes._ID);
if (idRestaurante != null) {
carregar();
}
}
private void salvar() {
String tipo = null;
switch (tipos.getCheckedRadioButtonId()) {
case R.id.rodizio:
tipo = "rodizio";
break;
case R.id.fast_food:
tipo = "fast_food";
break;
case R.id.a_domicilio:
tipo = "a_domicilio";
break;
}
if (tipo != null && endereco.getText().toString() != null &&
nome.getText().toString() != null) {
if (idRestaurante == null) {
gerenciador.inserir(nome.getText().toString(),
endereco.getText().toString(),
tipo, anotacoes.getText().toString(),
twitter.getText().toString(), telefone.getText().toString());
} else {
gerenciador.atualizar(idRestaurante,
nome.getText().toString(),
endereco.getText().toString(),
tipo, anotacoes.getText().toString(),
twitter.getText().toString(), telefone.getText().toString());
}
}
finish();
}
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
Prosseguindo, precisamos dizer ao Android que nossa aplicao deseja realizar chamadas. Para isso, adicione a
seguinte linha s permisses no arquivo AndroidManifest.xml.
13
<uses-permission android:name="android.permission.CALL_PHONE"/>
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
4
5
6
7
8
9
10
11
12
13
14
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
40
<string name="telefone">Telefone:</string>
<string name="chamar">Telefonar</string>
e no res/values-es/strings.xml.
39
40
Telfono:
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
156
157
158
159
160
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
https://www.facebook.com/aulaparticulardoflavio