Professional Documents
Culture Documents
Este post é o primeiro de uma série em que vou ensinar, passo a passo, como criar um
aplicativo para a plataforma android.
Android é a plataforma do google para dispositivos móveis que equipa um grande número de
telefones no mercado. (g1, motorola dext, milestone, nexus one…)
Uma idéia e força de vontade. E claro, saber programar em Java. Você NÃO precisa de um
hardware (telefone) para isso. A grande maioria dos testes pode ser feito no emulador!
1) Montar o ambiente padrão fornecido pelo Google. Para isso, você precisará seguir os
seguintes passos:
Todos os links contém as instruções para instalação dos componentes. Caso haja dúvidas,
coloque nos comentários!
DICA: Você pode “economizar” os passos acima usando o ambiente do Motodev – que é
basicamente a junção de todos os passos acima e mais algumas ferramentas. Para instalar o
Motodev Studio vá até a página http://developer.motorola.com/docstools/motodevstudio/
É importante dizer que os aplicativos gerados pelo Motodev Studio funcionarão em todos os
telefones, e não só em telefones Motorola.
Criando um projeto Android (Helloworld!)
No artigo da semana passada vimos como montar o ambiente de desenvolvimento android.
Caso seu ambiente ainda não esteja funcionando, volte lá e veja o que faltou.
Após isso, irá aparecer a tela com as configurações de seu projeto android.
1. package br.com.felipesilveira.hello_world;
2.
3. import android.app.Activity;
4. import android.os.Bundle;
5. import android.widget.TextView;
6.
7. public class HelloWorld extends Activity {
8. /** Called when the activity is first created. */
9. @Override
10. public void onCreate(Bundle savedInstanceState) {
11. super.onCreate(savedInstanceState);
12. TextView view = new TextView(this);
13. view.setText("Hello, Android");
14. setContentView(view);
15.
16. }
17. }
Para rodar nosso recém criado programa no emulador do google, vá até “Run”>Run as
“Android Application”. Uma instância do emulador será criada, com o nosso “HelloWorld”
rodando.
Uma Activity é basicamente uma classe gerenciadora de UI (Interface com o usuário). Todo
aplicativo android começa por uma Activity. Para saber mais, veja a documentação da classe
Activity. Nos próximos artigos falaremos bastante sobre ela, suas características, seu ciclo de
vida e como manipulá-la corretamente.
DICA: Além de rodar a aplicação, você pode explorar um pouco o emulador, para conhecer
o sistema operacional Android, caso ainda não conheça. Durante o desenvolvimento, o
emulador será seu melhor amigo, então essa é a oportunidade para conhecê-lo bem.
Para exemplificar os estudos que faremos a partir de agora, usaremos uma aplicação de
exemplo, que construiremos juntos, a partir dos próximos posts.
A idéia é fazer uma aplicação onde o usuário poderá entrar com algumas anotações, e
visualizar as últimas entradas.
Criando o main.xml
Neste arquivo temos contato com os primeiros elementos de um arquivo de layout XML:
• LinearLayout, que é apenas um container.
• TextView, que é um elemento de texto. Nesse caso está imprimindo a string cujo id é
@string/hello. (Não se preocupe, falaremos sobre strings e seus ids à frente nesse
curso)
Para criar um layout parecido com o rascunho do início do post, iremos inserir outros dois
elementos:
• EditText – uma caixa de texto onde o usuário irá entrar com as anotações;
• ListView – uma lista de anotações previamente submetidas.
Para que a nossa aplicação tenha o layout definido pelo arquivo XML, é preciso carregá-lo.
É possível utilizar mais de um arquivo XML para uma mesma tela, para formar layouts mais
sofisticados. Trataremos disso à frente nesse curso.
No próximo post iremos acrescentar um botão a este layout, e iremos aprender um pouco
mais sobre os parâmetros de um documento XML.
DICA: Existe uma ferramenta online, gratuita, para edição de arquivos de layout XML. É o
DroidDraw.
Queremos que ele fique ao lado esquerdo da caixa de texto. Como fazer isso?
Uma Activity é basicamente uma classe gerenciadora de UI (Interface com o usuário). Todo
aplicativo android começa por uma Activity.
Ou seja, quando uma aplicação android é executada, na verdade é a sua Activity principal que
é lançada.
Uma das coisas que é importante conhecer sobre a Activity é o seu ciclo de vida. E para
explicá-lo, nada melhor do que o seguinte diagrama*:
Ciclo de vida de uma Activity
Já sabemos que quando a sua aplicação é executada, a Activity definida como padrão (na
criação do projeto) é lançada. Mas eu posso criar outras Activities?
Mas como uma Activity define o seu resultado, a ser lido por aquela que a chamou?
Isso é feito invocando-se a função setResult (int resultCode), como por exemplo:
1. setResult(Intent.RESULT_OK);
No post passado vimos como lançar uma Activity a partir de outra, usando as funções
startActivity() e startActivityForResult().
Hoje usaremos esta técnica para mostrar ao usuário uma tela de “Boas Vindas” na nossa
aplicação de exemplo, o QuickNotes.
Para criar essa nova Activity, usaremos alguma funções do Motodev. Se você não está
usando a IDE da Motorola, não tem problema – é só criar os arquivos manualmente. Porém
recomendo o uso da IDE, por facilitar bastante a nossa vida.
Vá até o menu “MOTODEV” >”New” > “New Android Activity”. Na tela de configuração,
entre com o nome da Activity a ser criada:
Com a Activity criada, o próximo passo é criar o arquivo XML que definirá o seu layout.
Crie o arquivo ‘welcome.xml’ no diretorio res/layout com o seguinte conteúdo:
Este arquivo XML define uma Activity com um texto e um botão logo abaixo, com a palavra
“Continuar”.
1. setContentView(R.layout.welcome);
Lançando a WelcomeActivity
1. package br.com.felipesilveira.quicknotes;
2.
3. import android.app.Activity;
4. import android.os.Bundle;
5. import android.content.Intent;
6.
7. public class MainActivity extends Activity {
8. /** Called when the activity is first created. */
9. @Override
10. public void onCreate(Bundle savedInstanceState) {
11. super.onCreate(savedInstanceState);
12. setContentView(R.layout.main);
13.
14. Intent i = new Intent(this, WelcomeActivity.class);
15. startActivity(i);
16. }
17. }
Tratando os eventos de um botão
Até agora, já temos a Activity secundária sendo lançada, mas o que deve acontecer quando o
usuário clicar no botão “Continuar”?
A WelcomeActivity deve morrer - Dessa forma, a última Activity instanciada será mostrada
novamente – que por sinal é a nossa MainAcitivity!
Para fazer isso, devemos adicionar um listener ao botão para que o método finish() seja
invocado ao clique do usuário. O método finish() da classe Activity força a “morte” desta.
1. package br.com.felipesilveira.quicknotes;
2.
3. import android.app.Activity;
4. import android.os.Bundle;
5. import android.view.View;
6. import android.widget.Button;
7.
8. public class WelcomeActivity extends Activity {
9. /**
10. * @see android.app.Activity#onCreate(Bundle)
11. */
12. @Override
13. protected void onCreate(Bundle savedInstanceState) {
14. super.onCreate(savedInstanceState);
15. setContentView(R.layout.welcome);
16.
17. final Button button = (Button) findViewById(R.id.welcome_ok_button);
18.
19. button.setOnClickListener(new View.OnClickListener() {
20. public void onClick(View v) {
21. finish();
22. }
23. });
24. }
25. }
Hoje irei falar sobre um mecanismo simples porém muito útil para uma aplicação android: os
logs.
Para este fim, android tem um classe específica: A classe Log (android.util.Log).
Para criar os log, temos à disposição as funções Log.v(), Log.d(), Log.i(), Log.w(), r Log.e().
Todas estas funções recebem como parâmetros duas strings – a primeira, chamada de TAG, e
a segunda que é a mensagem em si. A TAG é uma string que irá identificar a sua aplicação,
tornando mais fácil identificar quais logs foram impressos por ela. (Todas as aplicações
imprimem o log no mesmo stream. Assim, a única forma de separar os seus logs é filtrando
pela sua Tag)
Dessa forma, para imprimir um log de DEBUG basta usar a linha abaixo:
Para visualizar os logs de nossa aplicação, usaremos o DDMS. Abrindo a aba “Logcat”,
temos a seguinte tela:
DICA: Seja cuidadoso com os logs. Eles podem interferir na performance de sua aplicação
se, por exemplo, forem colocados dentro de um loop.
Content Providers
Os Content Providers são parte importantíssima da arquitetura de um sistema android. É
responsabilidade deles prover às aplicações o conteúdo que elas precisam para funcionar, ou
seja, os dados.
As aplicações poderiam muito bem acessar diretamente um banco de dados, por exemplo.
Porém, é uma boa prática tornar o modo como os dados são gravados transparente à
aplicação. Dessa forma, a aplicação pode manter o foco nas interações com o usuário.
Além disso, essa técnica permite a criação de Shared Content Providers, que são providers
“públicos” que podem ser acessados por várias aplicações. Por exemplo, existe o content
provider de SMS/MMS que permite a qualquer aplicação ler as mensagens recebidas por um
telefone celular.
Uri. Guarde bem este nome, pois você irá precisar muito dele durante a sua carreira como
desenvolvedor android.
Toda a comunicação entre aplicações e providers é feita através dos métodos da interface
ContentProvider, que sempre recebem um objeto Uri como parâmetro. O formato da Uri
é definido pelo content provider. Por exemplo, a Uri content://sms/inbox acessa as mensagens
de inbox no Content Provider de SMS. Falaremos um pouco mais sobre as Uris a seguir, mas
primeiro, vamos conhecer os métodos que usaremos para enviá-las para o provider:
Iremos criar um content provider para o QuickNotes, que servirá para gravar e recuperar as
anotações do usuário, da seguinte forma:
content://<authority>/<parametro1>/<parametro2>/…/<parametroN>
Onde authority é o “nome” do provider, e os parâmetros são aqueles definidos pelo provider.
Por exemplo, a seguinte Uri:
content://sms/conversations/10
1. package br.com.felipesilveira.quicknotes;
2.
3. import android.content.ContentProvider;
4. import android.net.Uri;
5. import android.content.ContentValues;
6. import android.database.Cursor;
7.
8. public class QuickNotesProvider extends ContentProvider {
9. // Aqui definimos os formatos possíveis de Uri que
10. // o nosso provider irá aceitar.
11. public static final Uri CONTENT_URI = Uri
12. .parse("content://br.com.felipesilveira.quicknotes.quicknotesprovider");
13.
14. @Override
15. public int delete(Uri uri, String selection, String[] selectionArgs) {
16. return 0;
17. }
18.
19. @Override
20. public String getType(Uri uri) {
21. return null;
22. }
23.
24. @Override
25. public Uri insert(Uri uri, ContentValues values) {
26. return null;
27. }
28.
29. @Override
30. public boolean onCreate() {
31. return false;
32. }
33.
34. @Override
35. public Cursor query(Uri uri, String[] projection, String selection,
36. String[] selectionArgs, String sortOrder) {
37. return null;
38. }
39.
40. @Override
41. public int update(Uri uri, ContentValues values, String selection,
42. String[] selectionArgs) {
43. return 0;
44. }
45. }
<provider
android:authorities="br.com.felipesilveira.quicknotes.quicknotesprovider"
android:name=".QuickNotesProvider"/>
E assim o nosso Content Provider está pronto para receber requisições da aplicação. Ainda
não retorna nenhum resultado significativo – mas isso faremos no próximo artigo, onde
ensinarei como acessar um banco de dados SQLite, para fazer esse provider realmente
efetivo.
DICA: Usando o MOTODEV Studio, a tarefa de criar um content provider fica muito mais
fácil. Basta acessar New > Android Content Provider, que um template será criado, com
todos os métodos! daí é só implementar a lógica deles.
Um dos mais importantes módulos é o SQLite. Sim, amigos, já temos um SGDB (Sistema
gerenciador de bancos de dados) instalado e pronto para usar! E é exatamente o que faremos
no artigo de hoje.
No artigo anterior vimos como criar um Content Provider. Usaremos este provider para
acessar o banco de dados.
Para fazer isso, precisamos implementar os métodos da classe ContentProvider que vimos no
artigo passado (query(), delete(), update(), etc…) para prover ao usuário os métodos para
criar, atualizar, deletar e recuperar os dados. Além disso, usaremos a classe
SQLiteOpenHelper para gerenciar a conexão com o banco de dados.
A classe SQLiteOpenHelper
A classe SQLiteOpenHelper, como dito anteriormente, será usada para gerenciar o banco de
dados. Para usá-la, é preciso criar uma subclasse implementando os métodos abaixo:
• onCreate() – Este método é chamado quando a conexão com o banco de dados for
aberta pela primeira vez. É aqui que criaremos o banco de dados, com o comando sql
CREATE.
• onUpdate() – Este método é chamado quando a versão do banco de dados muda. Por
exemplo, digamos que você criou uma nova versão de seu aplicativo que usa uma
tabela a mais no banco de dados. Quando esta nova versão for instalada (em um
telefone que já possuir a primeira versão) este método será chamado, então você
poderá criar apenas a nova tabela, mantendo os dados do usuário.
O código
1. package br.com.felipesilveira.quicknotes;
2.
3. import java.util.HashMap;
4.
5. import android.content.ContentProvider;
6. import android.content.ContentUris;
7. import android.content.Context;
8. import android.content.UriMatcher;
9. import android.net.Uri;
10. import android.provider.BaseColumns;
11. import android.content.ContentValues;
12. import android.database.Cursor;
13. import android.database.sqlite.SQLiteDatabase;
14. import android.database.sqlite.SQLiteOpenHelper;
15. import android.database.sqlite.SQLiteQueryBuilder;
16.
17. public class QuickNotesProvider extends ContentProvider {
18.
19. // Authority do nosso provider, a ser usado nas Uris.
20. public static final String AUTHORITY =
21. "br.com.felipesilveira.quicknotes.quicknotesprovider";
22.
23. // Nome do arquivo que irá conter o banco de dados.
24. private static final String DATABASE_NAME = "quicknotes.db";
25.
26. // Versao do banco de dados.
27. // Este valor é importante pois é usado em futuros updates do DB.
28. private static final int DATABASE_VERSION = 1;
29.
30. // Nome da tabela que irá conter as anotações.
31. private static final String NOTES_TABLE = "notes";
32.
33. // 'Id' da Uri referente às notas do usuário.
34. private static final int NOTES = 1;
35.
36. // Tag usada para imprimir os logs.
37. public static final String TAG = "QuickNotesProvider";
38.
39. // Instância da classe utilitária
40. private DBHelper mHelper;
41.
42. // Uri matcher - usado para extrair informações das Uris
43. private static final UriMatcher mMatcher;
44.
45. private static HashMap<string, string=""> mProjection;
46.
47. static {
48. mProjection = new HashMap<string, string="">();
49. mProjection.put(Notes.NOTE_ID, Notes.NOTE_ID);
50. mProjection.put(Notes.TEXT, Notes.TEXT);
51. }
52.
53. static {
54. mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
55. mMatcher.addURI(AUTHORITY, NOTES_TABLE, NOTES);
56. }
57.
58. /////////////////////////////////////////////////////////////////
59. // Métodos overrided de ContentProvider //
60. /////////////////////////////////////////////////////////////////
61. @Override
62. public int delete(Uri uri, String selection, String[] selectionArgs) {
63. SQLiteDatabase db = mHelper.getWritableDatabase();
64. int count;
65. switch (mMatcher.match(uri)) {
66. case NOTES:
67. count = db.delete(NOTES_TABLE, selection, selectionArgs);
68. break;
69. default:
70. throw new IllegalArgumentException(
71. "URI desconhecida " + uri);
72. }
73.
74. getContext().getContentResolver().notifyChange(uri, null);
75. return count;
76. }
77.
78. @Override
79. public String getType(Uri uri) {
80. switch (mMatcher.match(uri)) {
81. case NOTES:
82. return Notes.CONTENT_TYPE;
83. default:
84. throw new IllegalArgumentException(
85. "URI desconhecida " + uri);
86. }
87. }
88.
89. @Override
90. public Uri insert(Uri uri, ContentValues values) {
91. switch (mMatcher.match(uri)) {
92. case NOTES:
93. SQLiteDatabase db = mHelper.getWritableDatabase();
94. long rowId = db.insert(NOTES_TABLE, Notes.TEXT, values);
95. if (rowId > 0) {
96. Uri noteUri = ContentUris.withAppendedId(
97. Notes.CONTENT_URI, rowId);
98. getContext().getContentResolver().notifyChange(
99. noteUri, null);
100. return noteUri;
101. }
102. default:
103. throw new IllegalArgumentException(
104. "URI desconhecida " + uri);
105. }
106. }
107.
108. @Override
109. public boolean onCreate() {
110. mHelper = new DBHelper(getContext());;
111. return true;
112. }
113.
114. @Override
115. public Cursor query(Uri uri, String[] projection, String selection,
116. String[] selectionArgs, String sortOrder) {
117. // Aqui usaremos o SQLiteQueryBuilder para construir
118. // a query que será feito ao DB, retornando um cursor
119. // que enviaremos à aplicação.
120. SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
121. SQLiteDatabase database = mHelper.getReadableDatabase();
122. Cursor cursor;
123. switch (mMatcher.match(uri)) {
124. case NOTES:
125. // O Builer receberá dois parametros: a tabela
126. // onde será feita a busca, e uma projection -
127. // que nada mais é que uma HashMap com os campos
128. // que queremos recuperar do banco de dados.
129. builder.setTables(NOTES_TABLE);
130. builder.setProjectionMap(mProjection);
131. break;
132.
133. default:
134. throw new IllegalArgumentException(
135. "URI desconhecida " + uri);
136. }
137.
138. cursor = builder.query(database, projection, selection,
139. selectionArgs, null, null, sortOrder);
140.
141. cursor.setNotificationUri(getContext().getContentResolver(), uri);
142. return cursor;
143. }
144.
145. @Override
146. public int update(Uri uri, ContentValues values, String selection,
147. String[] selectionArgs) {
148. SQLiteDatabase db = mHelper.getWritableDatabase();
149. int count;
150. switch (mMatcher.match(uri)) {
151. case NOTES:
152. count = db.update(NOTES_TABLE, values,
153. selection, selectionArgs);
154. break;
155. default:
156. throw new IllegalArgumentException(
157. "URI desconhecida " + uri);
158. }
159.
160. getContext().getContentResolver().notifyChange(uri, null);
161. return count;
162. }
163.
164. /////////////////////////////////////////////////////////////////
165. // Inner Classes utilitárias //
166. /////////////////////////////////////////////////////////////////
167. public static final class Notes implements BaseColumns {
168. public static final Uri CONTENT_URI = Uri.parse("content://"
169. + QuickNotesProvider.AUTHORITY + "/notes");
170.
171. public static final String CONTENT_TYPE =
172. "vnd.android.cursor.dir/" + QuickNotesProvider.AUTHORITY;
173.
174. public static final String NOTE_ID = "_id";
175.
176. public static final String TEXT = "text";
177. }
178.
179. private static class DBHelper extends SQLiteOpenHelper {
180.
181. DBHelper(Context context) {
182. super(context, DATABASE_NAME, null, DATABASE_VERSION);
183. }
184.
185. /* O método onCreate é chamado quando o provider é executado pela
186. * primeira vez, e usado para criar as tabelas no database
187. */
188. @Override
189. public void onCreate(SQLiteDatabase db) {
190. db.execSQL("CREATE TABLE " + NOTES_TABLE + " (" +
191. Notes.NOTE_ID + " INTEGER PRIMARY KEY AUTOINCRE
MENT," +
192. Notes.TEXT + " LONGTEXT" + ");");
193. }
194.
195. /* O método onUpdate é invocado quando a versão do banco de dados
196. * muda. Assim, é usado para fazer adequações para a aplicação
197. * funcionar corretamente.
198. */
199. @Override
200. public void onUpgrade(SQLiteDatabase db,
201. int oldVersion, int newVersion) {
202. // Como ainda estamos na primeira versão do DB,
203. // não precisamos nos preocupar com o update agora.
204. }
205. }
206. }
207. </string,></string,>
Cursores
O primeiro conceito importante a se falar é o conceito dos Cursores. Como você deve
percebido, este é o tipo de retorno do método query(), e não é por acaso: Os cursores são
“apontadores de dados” do banco de dados – ou seja, uma interface que permite o acesso aos
dados retornados pela query enviada pelo usuário.
notifyChanges()
Vamos começar inserindo uma anotação do usuário no banco de dados. Para fazer isso, o
primeiro passo é adicionar um Listener ao botão ‘Inserir’, da seguinte forma:
E agora, criando o objeto mInsertListener. Ele precisa ser um objeto que implementa a
interface OnClickListener,. Assim, precisamos implementar o método onClick(), que será
chamado assim que o usuário pressionar o botão.
No código acima eu fiz uma chamada a um método que ainda não está implementado – o
método addNote(), que recebe um String que será inserida no banco de dados. Ele será o
método responsável por efetivamente “conversar” com o content provider. Vamos
implementá-lo:
view plaincopy to clipboardprint?
1. /*
2. * Método responsável por inserir um registro no content provider
3. */
4. protected void addNote(String text) {
5. ContentValues values = new ContentValues();
6. values.put(QuickNotesProvider.Notes.TEXT, text);
7.
8. getContentResolver().insert(
9. QuickNotesProvider.Notes.CONTENT_URI, values);
10. }
1. package br.com.felipesilveira.quicknotes;
2.
3. import android.app.Activity;
4. import android.os.Bundle;
5. import android.view.View;
6. import android.view.View.OnClickListener;
7. import android.widget.Button;
8. import android.widget.EditText;
9. import android.content.ContentValues;
10. import android.content.Intent;
11.
12. public class MainActivity extends Activity {
13.
14. private static final String TAG = "QuickNotesMainActivity";
15.
16. /** Called when the activity is first created. */
17. @Override
18. public void onCreate(Bundle savedInstanceState) {
19. super.onCreate(savedInstanceState);
20. setContentView(R.layout.main);
21.
22. Intent i = new Intent(this, WelcomeActivity.class);
23. startActivity(i);
24.
25. Button insertButton = (Button)findViewById(R.id.insert_button);
26. insertButton.setOnClickListener(mInsertListener);
27.
28. // adicionando um 'Hint' ao Editbox.
29. EditText editBox = (EditText)findViewById(R.id.edit_box);
30. editBox.setHint("Nova nota...");
31. }
32.
33. // Definindo um OnClickListener para o botão "Inserir"
34. private OnClickListener mInsertListener = new OnClickListener() {
35. public void onClick(View v) {
36. EditText editBox = (EditText)findViewById(R.id.edit_box);
37. addNote(editBox.getText().toString());
38. editBox.setText("");
39. }
40. };
41.
42. /*
43. * Método responsável por inserir um registro no content provider
44. */
45. protected void addNote(String text) {
46. ContentValues values = new ContentValues();
47. values.put(QuickNotesProvider.Notes.TEXT, text);
48.
49. getContentResolver().insert(
50. QuickNotesProvider.Notes.CONTENT_URI, values);
51. }
52. }
É importante salientar que estamos apenas inserindo o dado no banco de dados – não estamos
lendo-o em nenhum lugar na nossa aplicação, ainda.
Vou aproveitar esta pergunta para mostrar como acessar o banco de dados pelo shell do
android. Isso é muito útil para auxiliar no desenvolvimento de aplicações que lidam com
banco de dados.
Para acessar o banco de dados pelo shell, iremos iniciar uma sessão com o comando adb
shell e então usar o comando sqlite3 <caminho-do-db>. A partir daí, basta usar comandos
SQL normais. Para ver a estrutura do banco de dados, o comando .schema deve ser usado.
Veja no exemplo abaixo:
$ adb shell
# sqlite3 /data/data/br.com.felipesilveira.quicknotes/databases/quicknotes.db
SQLite version 3.5.9
Enter “.help” for instructions
sqlite> .schema
.schema
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE notes (_id INTEGER PRIMARY KEY AUTOINCREMENT,text
LONGTEXT);
sqlite> select * from notes;
select * from notes;
1|teste
Como podemos ver, a primeira entrada do nosso DB está lá, então é sinal que o
QuickNotesProvider está funcionando corretamente!
No próximo artigo, iremos usar o provider para ler os dados já gravados no DB para mostrá-
los ao usuário. Até lá!