Академический Документы
Профессиональный Документы
Культура Документы
a android.
Android a plataforma do google para dispositivos mveis que equipa um grande nmero de telefones no mercado. (g1, motorola dext, milestone, nexus one) O que preciso para comear a desenvolver para android? Uma idia e fora de vontade :). E claro, saber programar em Java. Voc NO precisa de um hardware (telefone) para isso. A grande maioria dos testes pode ser feito no emulador! Alm disso, Android uma plataforma de cdigo aberto e o desenvolvimento de programas amplamente incentivado pelo Google (e pela Motorola, como vamos ver no final do post). Por onde comear? O primeiro passo montar seu ambiente de desenvolvimento. 1) Montar o ambiente padro fornecido pelo Google. Para isso, voc precisar seguir os seguintes passos: - Instalar o Eclipse (www.eclipse.org) - Instalar o Android SDK (developer.android.com/sdk) - Instalar o ADT Plugin (developer.android.com/sdk/eclipse-adt.html) Todos os links contm as instrues para instalao dos componentes. Caso haja dvidas, coloque nos comentrios! DICA: Voc pode economizar os passos acima usando o ambiente do Motodev que basicamente a juno de todos os passos acima e mais algumas ferramentas. Para instalar o Motodev Studio v at a pgina http://developer.motorola.com/docstools/motodevstudio/ importante dizer que os aplicativos gerados pelo Motodev Studio funcionaro em todos os telefones, e no s em telefones Motorola.
Com isso terminamos o nosso primeiro passo. No prximo passo, vou mostrar como criar um projeto android no eclipse. At l!
Criando um "Android Project" Aps isso, ir aparecer a tela com as configuraes de seu projeto android. Nesta tela, voc precisa inserir os seguintes dados:
Project name - o nome do projeto no eclipse. Build Target a verso do Android para a qual o seu projeto ser direcionado. Application name o nome da sua aplicao o nome que aparecer no telefone. Package name - o package no qual sero criadas as suas classes java. Create Activity Marque este checkbox e coloque um nome na caixa de texto. frente explicarei o que uma Activity.
package br.com.felipesilveira.hello_world; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class HelloWorld extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView view = new TextView(this); view.setText("Hello, Android"); setContentView(view); } }
Para rodar nosso recm criado programa no emulador do google, v at Run>Run as Android Application. Uma instncia do emulador ser criada, com o nosso HelloWorld rodando.
esboo do layout
Criando o main.xml
Para novos projetos android, o arquivo main.xml j automaticamente criado. Ele fica no diretrio res/layout, com o contedo:
view plaincopy to clipboardprint?
1. <?xml version="1.0" encoding="utf-8"?> 2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3. android:orientation="vertical" 4. android:layout_width="fill_parent" 5. android:layout_height="fill_parent" 6. > 7. <TextView
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. (No se preocupe, falaremos sobre strings e seus ids frente nesse curso)
Para criar um layout parecido com o rascunho do incio do post, iremos inserir outros dois elementos:
EditText uma caixa de texto onde o usurio ir entrar com as anotaes; ListView uma lista de anotaes previamente submetidas.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:id="@+id/edit_box" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Nova nota..." > </EditText> <ListView android:id="@+id/notes_list" android:layout_width="fill_parent" android:layout_height="wrap_content" > </ListView> </LinearLayout>
O parmetro R.layout.main indica que o arquivo de layout a ser carregado o main.xml. (Se o se arquivo se chamar abobrinha.xml, o parmetro dever ser R.layout.abobrinha) possvel utilizar mais de um arquivo XML para uma mesma tela, para formar layouts mais sofisticados. Trataremos disso frente nesse curso. Compilando o nosso projeto e rodando no emulador, temos o seguinte resultado:
QuickNotes rodando no emulador No prximo post iremos acrescentar um boto a este layout, e iremos aprender um pouco mais sobre os parmetros de um documento XML. DICA: Existe uma ferramenta online, gratuita, para edio de arquivos de layout XML. o DroidDraw. LEITURA RECOMENDADA: Para aprender mais sobre a definio de layout de aplicaes android, visite a pgina User Interface da documentao oficial. (em ingls)
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:id="@+id/edit_box" android:layout_width="240px" android:layout_height="wrap_content" android:text="Nova nota..." > </EditText> <Button android:id="@+id/insert_button" android:layout_width="80px" android:layout_height="wrap_content"
22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32.
android:text="Inserir" > </Button> </LinearLayout> <ListView android:id="@+id/notes_list" android:layout_width="fill_parent" android:layout_height="wrap_content" > </ListView> </LinearLayout>
Novo layout da aplicao android - agora com o boto "Inserir" No prximo post veremos como controlar o boto de inserir.
Este diagrama de fundamental importncia para o correto entendimento do funcionamento de uma aplicao android. Ele introduz, implicitamente, os estados que uma Activity pode estar, os quais explico no desenho abaixo:
Estados de uma Activity Voltando ao diagrama do ciclo de vida, temos as seguintes funes:
onCreate() a primeira funo a ser executada quando uma Activity lanada. Geralmente a responsvel por carregar os layouts XML e outras operaes de inicializao. executada somente uma vez durante a vida til da Activity. onStart() chamada imediatamente aps a onCreate() e tambm quando uma Activity que estava em background volta a ter foco. onResume() Assim como a onStart(), chamada na inicializao da Activity (logo aps a prpria onStart()) e tambm quando uma Activity volta a ter foco. Qual a diferena entre as duas? A onStart() s chamada quando a Activity no estava mais visvel na tela e volta a ter o foco, enquanto a onResume() sempre chamada nas retomadas de foco. onPause() a primeira funo a ser invocada quando a Activity perde o foco (ou seja, uma outra Activity vem frente). onStop() Anloga onPause(), s chamada quando a Activity fica completamente encoberta por outra Activity (no mais visvel). onDestroy() A ltima funo a ser executada. Depois dela, a Activity considerada morta ou seja, nao pode mais ser relanada. Se o usurio voltar a requisitar essa Activity, outro objeto ser contrudo. onRestart() Chamada imediatamente antes da onStart(), quando uma Activity volta a ter o foco depois de estar em background.
E para executar outras Activities, basta usar as funes startActivity() e startActivityForResult(). No exemplo abaixo, lanamos uma segunda Activity a partir da principal, e esperamos um resultado dela como se fosse um retorno de funo.
view plaincopy to clipboardprint?
1. static final int PICK_CONTACT_REQUEST = 0; 2. 3. @Override 4. public void onCreate(Bundle savedInstanceState) { 5. super.onCreate(savedInstanceState); 6. setContentView(R.layout.main); 7. startActivityForResult( 8. new Intent(Intent.ACTION_CONTACT_REQUEST, 9. new Uri("content://contacts")), 10. CONTACT_REQUEST); 11. } 12. 13. protected void onActivityResult(int requestCode, int resultCode, 14. Intent data) { 15. if (requestCode == CONTACT_REQUEST) { 16. if (resultCode == RESULT_OK) { 17. // fazer alguma coisa... 18. } 19. }
Quando a segunda Activity terminar a sua execuo, a funo onActivityResult() ser invocada, com o resultado como parmetro. Mas como uma Activity define o seu resultado, a ser lido por aquela que a chamou? Isso feito invocando-se a funo setResult (int resultCode), como por exemplo:
1. setResult(Intent.RESULT_OK);
Algum percebeu que eu no disse nada sobre os parmetros da startActivityForResult()? Isso porque este o assunto do meu prximo post o mecanismo de Uris em Android. At l!
Configurando a Activity a ser criada Aps clicar em Finish, j haver a classe WelcomeActivity no diretrio src do nosso projeto. Com a Activity criada, o prximo passo criar o arquivo XML que definir o seu layout. Crie o arquivo welcome.xml no diretorio res/layout com o seguinte contedo:
view plaincopy to clipboardprint?
1. 2. 3. 4. 5.
6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
<TextView android:id="@+id/welcome_text_view" android:layout_width="fill_parent" android:layout_height="300dip" android:gravity="center" android:text="Bem vindo aplicao QuickNotes!\n\nEssa aplicao foi feita dur ante o curso 'Desenvolvendo para Android' do site felipesilveira.com.br" > </TextView> <Button android:id="@+id/welcome_ok_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Continuar" > </Button> </TableLayout>
Este arquivo XML define uma Activity com um texto e um boto logo abaixo, com a palavra Continuar. Aps criado o arquivo, vamos carreg-lo no mtodo onCreate() da WelcomeActivity():
1. setContentView(R.layout.welcome);
Lanando a WelcomeActivity
Para lanar a WelcomeActivity a partir da MainActivity, usaremos a funo startActivity(). Esta funo recebe como parmetro um Intent. Posteriormente iremos aprofundar nosso conhecimento sobre essa importante classe, mas por enquanto o que voc precisa saber que ela usada para fazer a comunicao entre Activities. No cdigo abaixo instanciamos um Intent cuja nica funo lanar a WelcomeActivity, e ento o usamos como parmetro para a startActivity. Dessa forma, o cdigo da MainActivity fica assim:
view plaincopy to clipboardprint?
package br.com.felipesilveira.quicknotes; import android.app.Activity; import android.os.Bundle; import android.content.Intent; public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent i = new Intent(this, WelcomeActivity.class); startActivity(i); } }
At agora, j temos a Activity secundria sendo lanada, mas o que deve acontecer quando o usurio clicar no boto 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 boto para que o mtodo finish() seja invocado ao clique do usurio. O mtodo finish() da classe Activity fora a morte desta. O cdigo da WelcomeActivity fica assim:
view plaincopy to clipboardprint?
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.
package br.com.felipesilveira.quicknotes; import import import import android.app.Activity; android.os.Bundle; android.view.View; android.widget.Button;
public class WelcomeActivity extends Activity { /** * @see android.app.Activity#onCreate(Bundle) */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.welcome); final Button button = (Button) findViewById(R.id.welcome_ok_button); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { finish(); } }); } }
WelcomeActivity sendo executada Assim, finalizamos nossa Activity de Boas Vindas. At o prximo artigo!
DEBUG logs impressos pela funo Log.d() ERROR logs impressos pela funo Log.e() INFO logs impressos pela funo Log.i() VERBOSE logs impressos pela funo Log.v() WARN logs impressos pela funo Log.w()
Todas estas funes recebem como parmetros duas strings a primeira, chamada de TAG, e a segunda que a mensagem em si. A TAG uma string que ir identificar a sua aplicao, tornando mais fcil identificar quais logs foram impressos por ela. (Todas as aplicaes imprimem o log no mesmo stream. Assim, a nica forma de separar os seus logs filtrando pela sua Tag) uma boa prtica definir a TAG como uma string constante:
view plaincopy to clipboardprint?
Dessa forma, para imprimir um log de DEBUG basta usar a linha abaixo:
view plaincopy to clipboardprint?
Lendo logs pelo logcat No ponto 1 marcado na imagem, temos os botes de filtragem de nveis. Estes botes permitem que voc escolha quais logs quer ver DEBUG, por exemplo. J no ponto 3, voc pode escrever um texto, que ser um filtro para os logs. Por exemplo, se digitar QuickNotesMainActivity, ir ver s os logs que ns colocamos no cdigo acima. Finalmente, no ponto 2 temos a mensagem de log em si. DICA: Seja cuidadoso com os logs. Eles podem interferir na performance de sua aplicao se, por exemplo, forem colocados dentro de um loop.
Content Providers
25 de maio de 2010 18 comentrios Os Content Providers so parte importantssima da arquitetura de um sistema android. responsabilidade deles prover s aplicaes o contedo que elas precisam para funcionar, ou seja, os dados.
Mas por que so realmente necessrios? As aplicaes poderiam muito bem acessar diretamente um banco de dados, por exemplo. Porm, uma boa prtica tornar o modo como os dados so gravados transparente aplicao. Dessa forma, a aplicao pode manter o foco nas interaes com o usurio. Alm disso, essa tcnica permite a criao de Shared Content Providers, que so providers pblicos que podem ser acessados por vrias aplicaes. Por exemplo, existe o content provider de SMS/MMS que permite a qualquer aplicao ler as mensagens recebidas por um telefone celular. E como feita a comunicao entre Content Providers e Aplicaes? Uri. Guarde bem este nome, pois voc ir precisar muito dele durante a sua carreira como desenvolvedor android. Toda a comunicao entre aplicaes e providers feita atravs dos mtodos da interface ContentProvider, que sempre recebem um objeto Uri como parmetro. 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 mtodos que usaremos para envi-las para o provider:
query(Uri, String[], String, String[], String)- usado
para recuperar
dados.
insert(Uri, ContentValues)
dados.
delete(Uri, String, String[]) usado para deletar dados. getType(Uri) usado para obter o MIME type de certo dado.
Intencionalmente coloquei a caixa que define como o provider ir gravar os dados para mostrar que isso irrelevante para a aplicao.
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.
package br.com.felipesilveira.quicknotes; import import import import android.content.ContentProvider; android.net.Uri; android.content.ContentValues; android.database.Cursor;
public class QuickNotesProvider extends ContentProvider { // Aqui definimos os formatos possveis de Uri que // o nosso provider ir aceitar. public static final Uri CONTENT_URI = Uri .parse("content://br.com.felipesilveira.quicknotes.quicknotesprovider"); @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
Agora, vamos registrar o nosso provider no AndroidManifest, adicionando a seguinte linha entre as tags <application > e </application>
<provider android:authorities="br.com.felipesilveira.quicknotes.quicknotesprovid er" android:name=".QuickNotesProvider"/>
E assim o nosso Content Provider est pronto para receber requisies da aplicao. Ainda no retorna nenhum resultado significativo mas isso faremos no prximo artigo, onde ensinarei como acessar um banco de dados SQLite, para fazer esse provider realmente efetivo.
APIs que as aplicaes tem disposio para usar. Eles do muito poder ao desenvolvedores, permitindo que estes faam coisas que eram impossveis em outras plataformas mveis. Um dos mais importantes mdulos 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 mtodos da classe ContentProvider que vimos no artigo passado (query(), delete(), update(), etc) para prover ao usurio os mtodos para criar, atualizar, deletar e recuperar os dados. Alm disso, usaremos a classe SQLiteOpenHelper para gerenciar a conexo 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 mtodos abaixo:
onCreate() Este mtodo chamado quando a conexo com o banco de dados for aberta pela primeira vez. aqui que criaremos o banco de dados, com o comando sql CREATE. onUpdate() Este mtodo chamado quando a verso do banco de dados muda. Por exemplo, digamos que voc criou uma nova verso de seu aplicativo que usa uma tabela a mais no banco de dados. Quando esta nova verso for instalada (em um telefone que j possuir a primeira verso) este mtodo ser chamado, ento voc poder criar apenas a nova tabela, mantendo os dados do usurio.
O cdigo
O cdigo do QuickNotesProvider fica assim, acessando o banco de dados. A seguir, eu explico algumas coisas que podem gerar dvidas.
view plaincopy to clipboardprint?
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.
package br.com.felipesilveira.quicknotes; import java.util.HashMap; import import import import import import import import import import import android.content.ContentProvider; android.content.ContentUris; android.content.Context; android.content.UriMatcher; android.net.Uri; android.provider.BaseColumns; android.content.ContentValues; android.database.Cursor; android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteOpenHelper; android.database.sqlite.SQLiteQueryBuilder;
public class QuickNotesProvider extends ContentProvider { // Authority do nosso provider, a ser usado nas Uris. public static final String AUTHORITY = "br.com.felipesilveira.quicknotes.quicknotesprovider"; // Nome do arquivo que ir conter o banco de dados. private static final String DATABASE_NAME = "quicknotes.db"; // Versao do banco de dados. // Este valor importante pois usado em futuros updates do DB. private static final int DATABASE_VERSION = 1; // Nome da tabela que ir conter as anotaes. private static final String NOTES_TABLE = "notes"; // 'Id' da Uri referente s notas do usurio. private static final int NOTES = 1; // Tag usada para imprimir os logs. public static final String TAG = "QuickNotesProvider"; // Instncia da classe utilitria private DBHelper mHelper; // Uri matcher - usado para extrair informaes das Uris private static final UriMatcher mMatcher; private static HashMap<string, string=""> mProjection; static { mProjection = new HashMap<string, string="">(); mProjection.put(Notes.NOTE_ID, Notes.NOTE_ID); mProjection.put(Notes.TEXT, Notes.TEXT); } static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(AUTHORITY, NOTES_TABLE, NOTES); }
///////////////////////////////////////////////////////////////// // Mtodos overrided de ContentProvider // ///////////////////////////////////////////////////////////////// @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = mHelper.getWritableDatabase(); int count; switch (mMatcher.match(uri)) {
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.
case NOTES: count = db.delete(NOTES_TABLE, selection, selectionArgs); break; default: throw new IllegalArgumentException( "URI desconhecida " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { switch (mMatcher.match(uri)) { case NOTES: return Notes.CONTENT_TYPE; default: throw new IllegalArgumentException( "URI desconhecida " + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { switch (mMatcher.match(uri)) { case NOTES: SQLiteDatabase db = mHelper.getWritableDatabase(); long rowId = db.insert(NOTES_TABLE, Notes.TEXT, values); if (rowId > 0) { Uri noteUri = ContentUris.withAppendedId( Notes.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange( noteUri, null); return noteUri; } default: throw new IllegalArgumentException( "URI desconhecida " + uri); } } @Override public boolean onCreate() { mHelper = new DBHelper(getContext());; return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Aqui usaremos o SQLiteQueryBuilder para construir // a query que ser feito ao DB, retornando um cursor // que enviaremos aplicao. SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); SQLiteDatabase database = mHelper.getReadableDatabase(); Cursor cursor; switch (mMatcher.match(uri)) { case NOTES: // O Builer receber dois parametros: a tabela // onde ser feita a busca, e uma projection // que nada mais que uma HashMap com os campos // que queremos recuperar do banco de dados. builder.setTables(NOTES_TABLE); builder.setProjectionMap(mProjection); break;
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. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196.
default: throw new IllegalArgumentException( "URI desconhecida " + uri); } cursor = builder.query(database, projection, selection, selectionArgs, null, null, sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = mHelper.getWritableDatabase(); int count; switch (mMatcher.match(uri)) { case NOTES: count = db.update(NOTES_TABLE, values, selection, selectionArgs) break; default: throw new IllegalArgumentException( "URI desconhecida " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } ///////////////////////////////////////////////////////////////// // Inner Classes utilitrias // ///////////////////////////////////////////////////////////////// public static final class Notes implements BaseColumns { public static final Uri CONTENT_URI = Uri.parse("content://" + QuickNotesProvider.AUTHORITY + "/notes"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/" + QuickNotesProvider.AUTHORITY; public static final String NOTE_ID = "_id"; public static final String TEXT = "text"; } private static class DBHelper extends SQLiteOpenHelper { DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } /* O mtodo onCreate chamado quando o provider executado pela * primeira vez, e usado para criar as tabelas no database */ @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + NOTES_TABLE + " (" + Notes.NOTE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Notes.TEXT + " LONGTEXT" + ");"); } /* O mtodo onUpdate invocado quando a verso do banco de dados
197. * muda. Assim, usado para fazer adequaes para a aplicao 198. * funcionar corretamente. 199. */ 200. @Override 201. public void onUpgrade(SQLiteDatabase db, 202. int oldVersion, int newVersion) { 203. // Como ainda estamos na primeira verso do DB, 204. // no precisamos nos preocupar com o update agora. 205. } 206. } 207. } 208. 209. </string,></string,>
Cursores
O primeiro conceito importante a se falar o conceito dos Cursores. Como voc deve percebido, este o tipo de retorno do mtodo query(), e no por acaso: Os cursores so apontadores de dados do banco de dados ou seja, uma interface que permite o acesso aos dados retornados pela query enviada pelo usurio.
notifyChanges()
Em todos os mtodos em que alteramos o banco de dados (inserimos, deletamos ou modificamos dados) importante chamar o mtodo modifyChanges(). Isso far com que as aplicaes que estejam utilizando este conjunto de dados sejam notificadas, permitindo a estas atualizar tambm os dados mostrados ao usurio. No prximo post iremos usar o QuickNotesProvider na nossa aplicao.
1. <!--?xml version="1.0" encoding="utf-8" ?--> 2. <linearlayout android:orientation="vertical" android:layout_height="fill_parent " android:layout_width="fill_parent" xmlns:android="http://schemas.android.com/ apk/res/android"> 3. <linearlayout android:layout_height="wrap_content" android:layout_width="fi ll_parent" xmlns:android="http://schemas.android.com/apk/res/android"> 4. <edittext android:id="@+id/edit_box" android:layout_height="wrap_conten t" android:layout_width="240px" android:text=""> 5. </edittext> 6. <button android:id="@+id/insert_button" android:layout_height="wrap_con tent" android:layout_width="80px" android:text="Inserir"> 7. </button> 8. </linearlayout> 9. <listview android:id="@android:id/list" android:layout_height="fill_parent" android:layout_width="fill_parent"> 10. </listview> 11. </linearlayout>
Depois disso, o prximo fazer a nossa classe MainActivity ser uma classe filha da ListActivity: public class MainActivity extends ListActivity E ento, inicializar a ListView no mtodo onCreate. Mas para isso precisaremos de um Adapter.
Adapters
Adapters so classes responsveis por fazer o que chamado de bind: Receber os dados de um Cursor (ou de alguma outra fonte de dados) e coloc-los nos seus respectivos lugares no layout da Activity.
Para Activitys complexas, tipicamente so criadas subclasses da classe CursorAdapter (Adapter dedicado a tratar cursores). No nosso caso, em que temos um layout bastante simples, suficiente o uso do SimpleCursorAdapter. No cdigo abaixo eu mostro como usar este adapter, com uma explicao sobre seus parmetros:
view plaincopy to clipboardprint?
1. ListAdapter adapter = new SimpleCursorAdapter( 2. // O primeiro parametro eh o context. 3. this, 4. // O segundo, o layout de cada item. 5. R.layout.list_item, 6. // O terceiro parametro eh o cursor que contem os dados 7. // a serem mostrados 8. mCursor, 9. // o quarto parametro eh um array com as colunas do 10. // cursor que serao mostradas 11. new String[] {QuickNotesProvider.Notes.TEXT}, 12. // o quinto parametro eh um array (com o mesmo 13. // tamanho do anterior) com os elementos que 14. // receberao os dados. 15. new int[] {R.id.text}); 16. 17. setListAdapter(adapter);
Um dos parmetros recebidos pelo contrutor da SimpleCursorAdapter o layout dos itens da lista, que definimos da seguinte forma:
view plaincopy to clipboardprint?
1. <!--?xml version="1.0" encoding="utf-8" ?--> 2. <linearlayout android:orientation="vertical" android:layout_height="wrap_conten t" android:layout_width="fill_parent" xmlns:android="http://schemas.android.com /apk/res/android"> 3. 4. <textview android:id="@+id/text" android:layout_height="wrap_content" andr oid:layout_width="fill_parent" android:textcolor="#FFFFFF" android:textstyle="b old" android:textsize="16sp"> 5. </textview> 6. 7. </linearlayout>
package br.com.felipesilveira.quicknotes; import import import import import import import import import import import android.app.ListActivity; android.os.Bundle; android.util.Log; android.view.View; android.view.View.OnClickListener; android.widget.Button; android.widget.EditText; android.widget.ListAdapter; android.widget.SimpleCursorAdapter; android.content.ContentValues; android.content.Intent;
14. import android.database.Cursor; 15. 16. public class MainActivity extends ListActivity { 17. 18. private static final String TAG = "QuickNotesMainActivity"; 19. private Cursor mCursor; 20. 21. /** Called when the activity is first created. */ 22. @Override 23. public void onCreate(Bundle savedInstanceState) { 24. super.onCreate(savedInstanceState); 25. 26. Log.d(TAG, "Criando a MainActivity"); 27. 28. setContentView(R.layout.main); 29. 30. Intent i = new Intent(this, WelcomeActivity.class); 31. startActivity(i); 32. 33. Button insertButton = (Button)findViewById(R.id.insert_button); 34. insertButton.setOnClickListener(mInsertListener); 35. 36. // adicionando um 'Hint' ao Editbox. 37. EditText editBox = (EditText)findViewById(R.id.edit_box); 38. editBox.setHint("Nova nota..."); 39. 40. mCursor = this.getContentResolver(). 41. query(QuickNotesProvider.Notes.CONTENT_URI, null, null, null, null); 42. 43. ListAdapter adapter = new SimpleCursorAdapter( 44. // O primeiro parametro eh o context. 45. this, 46. // O segundo, o layout de cada item. 47. R.layout.list_item, 48. // O terceiro parametro eh o cursor que contem os dados 49. // a serem mostrados 50. mCursor, 51. // o quarto parametro eh um array com as colunas do 52. // cursor que serao mostradas 53. new String[] {QuickNotesProvider.Notes.TEXT}, 54. // o quinto parametro eh um array (com o mesmo 55. // tamanho do anterior) com os elementos que 56. // receberao os dados. 57. new int[] {R.id.text}); 58. 59. setListAdapter(adapter); 60. } 61. 62. /* 63. * Definindo um OnClickListener para o boto "Inserir" 64. */ 65. private OnClickListener mInsertListener = new OnClickListener() { 66. public void onClick(View v) { 67. EditText editBox = (EditText)findViewById(R.id.edit_box); 68. addNote(editBox.getText().toString()); 69. editBox.setText(""); 70. } 71. }; 72. 73. /* 74. * Mtodo responsvel por inserir um registro no content provider 75. */ 76. protected void addNote(String text) {
Executando a Activity, temos a seguinte tela, com a lista das notas que o usurio digitou!
DICA: Tenha muito cuidado ao projetar uma lista. Lembre-se que um usurio pode inserir mil, dez mil registros! O qeu aconteceria nessa situao? A performance nesses casos um fator a ser muito trabalhado e testado. Mas isso fica para um prximo artigo. At l!
Com a classe de teste criada, vamos implement-la. A primeira coisa que uma classe de teste precisa de um construtor. No construtor padro de qualquer classe de teste que tenha como pai a classe ActivityInstrumentationTestCase2<T>, ns precisamos chamar o construtor desta
passando como parametro o pacote da classe sendo testada e uma instancia representando tal classe (.class). Para o nosso caso, o construtor ter apenas isso. Portanto:
view plaincopy to clipboardprint?
O mtodo setUp() um mtodo herdado de uma das classes pai e destinado inicializao do estado da classe de teste. Este mtodo executado ANTES DE CADA TEST CASE, sempre. No nosso caso, vamos us-lo para pegar os elementos de UI da nossa MainActivity:
view plaincopy to clipboardprint?
1. @Override 2. protected void setUp() throws Exception { 3. super.setUp(); 4. mActivity = getActivity(); // mActivity ira guardar uma instnca da MainAct ivity. 5. // Esta chamada so e possivel por causa dos parametros passados no construt or. 6. // e o parametro passado na definio da classe 7. mEditor = (EditText) mActivity.findViewById(R.id.edit_box); 8. mButton = (Button) mActivity.findViewById(R.id.insert_button); 9. mList = (ListView) mActivity.findViewById(android.R.id.list); 10. mAdapter = (SimpleCursorAdapter) mList.getAdapter(); 11. }
1. 2. 3. 4. 5.
MainActivity mActivity; EditText mEditor; Button mButton; ListView mList; SimpleCursorAdapter mAdapter;
1. // Altere este valor para o numero de insercoes de teste vc deseja executar 2. private static int NUMERO_DE_INSERCOES = 10;
Antes de comear a implementar os test cases, importante verificar se o estado da classe esta consistente. Para executar checagens, vamos usar APIs do framework do JUnit. Mtodos como assertTrue, assertEquals, etc so responsveis por passar ou falhar um determinado check.
view plaincopy to clipboardprint?
1. // Testa se os elementos da tela foram inicializados corretamente 2. private void testPreConditions() { 3. assertTrue(mActivity != null);
4. 5. 6. 7. 8. 9. }
assertTrue(mEditor != null); assertTrue(mButton != null); assertTrue(mList != null); assertTrue(mAdapter != null); assertTrue(NUMERO_DE_INSERCOES > 0);
Agora, podemos comear a escrever nossos test cases. Cada test case um mtodo publico, sem retorno (void) e sem parametros. Tambm devem ter seu nome comeado pela palavra test. Assim, o Android Test Runner (entidade responsvel por excutar os testes) saber quais metodos ele deve executar como testes. Portanto, os test cases devem ter o seguinte padro: public void test<NomeDoMetodo>() No nosso caso, vamos executar testes de insero de elementos na lista e verificar se o que foi digitado pelo usurio o que realmente aparece na tela. Para melhorar a exeperincia, altere o arquivo de xml que descreve o layout da MainActivity do QuickNotes (QuickNotes\res\layout\main.xml) e adicione o seguinte atributo ao ListView: android:transcriptMode=alwaysScroll ou seja, deixa o elemento desta forma:
view plaincopy to clipboardprint?
Isto far com que a lista role automaticamente aps uma insero, se os itens no couberem mais na tela. Portanto, nosso test case fica assim:
view plaincopy to clipboardprint?
1. public void testInsertIntoTheList() { 2. // Como nao podemos assegurar a ordem em que os testes sao executados, deve mos 3. // verificar pre-condicoes antes de cada teste. 4. testPreConditions(); 5. 6. for (int i = 0; i < NUMERO_DE_INSERCOES; i++) { 7. // Codigos de teste que interagem com elementos da UI (Views) devem rod ar na thread 8. // principal, tambem chamada de UI thread. 9. mActivity.runOnUiThread(new Runnable() { 10. @Override 11. public void run() { 12. // Vamos pedir o foco para o editor e comecar a digitar 13. mEditor.requestFocus(); 14. } 15. }); 16.
17. 18. 19. 20. 21. 22. 23. 24. 25. 26.
// Vamos "digitar" no edit box uma entrada com o conteudo // "random[numero aleatorio entre 0 e 100]", navegar para a direita // (DPAD_RIGHT) - onde esta o botao de Insert e clicar na center key // para efetuar a insercao Integer rand = (int)(Math.random()*100); char[] digits = rand.toString().toCharArray(); if (digits.length == 1) { this.sendKeys("R A N D O M "+digits[0]+" DPAD_RIGHT DPAD_CENTER");
} else { this.sendKeys("R A N D O M "+digits[0]+" "+digits[1]+" DPAD_RIGHT D PAD_CENTER"); 27. } 28. 29. // Apos digitar uma entrada, comparamos se o que foi digitado pelo usuar io e 30. // o que esta sendo de fato mostrado na tela. Lembrese que o objetivo aqui e 31. // testar a UI e nao o DB. Android oferece todo um set de classes specia l para 32. // executar test cases para operacoes envolvendo o DB. Por esta razao, n este momento, 33. // nao nos interessa o que esta guardado no cursor que foi passado para o 34. // adapter e sim o que esta sendo mostrado na tela (os dois devem bater, obviamente) 35. LinearLayout item = (LinearLayout)(mList.getChildAt(mList.getChildCount( ) - 1)); 36. TextView view = (TextView) (item.findViewById(R.id.text)); 37. String text = view.getText().toString(); 38. assertEquals("random"+rand, text); 39. } 40. }
Repare que executamos uma chamada funcao testPreConditions no inicio. muito importante frisar que a ordem em que os testes cases so executados NO ASSEGURADA! Portanto, se ns mudssemos o modificador de acesso dessa funo para public ela atenderia todos os requisitos para ser um test case (sem retorno, sem parametros e comecado pela palavra test) e seria executado pelo Android Test Runner. No entanto, ningum garante que este mtodo seria executado antes do nosso teste. Portanto, a regra : todos os test cases que escrevemos devem ser independentes entre si e no possuir nenhuma relao uns com os outros. Caso voc queira garantir uma ordem de execuo, deve fazer como fizemos neste exemplo tornar a funo private e chamla no inicio de cada test case. Veja tambm que usamos a API sendKeys da nossa classe-pai ActivityInstrumentationTestCase2<T>. Esta API envia eventos de teclas para o dispositivo como se fosse o proprio usurio que o estivesse fazendo. Neste post vamos desenvolver apenas este teste. medida que novas funcionalidades forem adicionadas aplicao, vamos desenvolver novos testes para a UI e publicar em artigos posteriores.
Executar os testes muito simples: clique com o boto direito no projeto QuickNotesTest -> Run As -> Android JUnit Test Se suas configuraes de AVD estiverem corretas, o simulador ser iniciado e o QuickNotes ser executado. Voc ver vrias entradas sendo digitadas no text field e adicionadas lista uma a uma. No Eclipse vc pode acompanhar o andamento e o resultado dos testes, como na figura abaixo.
Para efeito de demonstrao, vamos forar uma falha. Altere a seguinte linha do testcase: - assertEquals(random+rand, text); + assertEquals(test_falha+rand, text); Agora, clique no boto Rerun Test (icone verde com uma seta amarela). Voc ver o resultado como na figura abaixo. Repare que o Eclipse te fornece a stack trace de onde ocorreu a falha. Assim, voc capaz de saber exatamente onde ocorreu a falha e corrigir o problema.
Isto termina nossa introduo aos Unit Tests em Android. No prximo post, vamos aprender como testar a consistncia de estado de uma Activity (se o usurio recebe uma ligao, por exemplo, enquanto est usando sua aplicao, como saber se sua Activity vai retornar ao estado anterior depois que a ligao se encerrar?). At l!