domingo, 25 de outubro de 2009

Intents nativas do Android

Olá povo,

Um grande benefício que a plataforma Android trouxe para nós desenvolvedores foi a capacidade de nos comunicar com aplicações "nativas" através de intenções (Intents).
Sendo assim, resolvi publicar algumas das intents nativas mais comuns do Android. Segue abaixo o código-fonte.


public class ExemploIntents extends ListActivity {

private static final String[] OPCOES = {
"Browser",
"Realizando uma chamada",
"Visualizar contato",
"Todos os contatos",
"Mapa",
"Tocar música",
"SMS",
"Sair"
};

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
ArrayAdapter<String> adapter =
new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
OPCOES);

setListAdapter(adapter);
}

@Override
protected void onListItemClick(ListView l, View v,
int position, long id) {

super.onListItemClick(l, v, position, id);
Uri uri = null;
Intent intent = null;

switch (position) {
// Abrindo uma URL
case 0:
uri = Uri.parse("http://nglauber.blogspot.com");
intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
break;

// Realiza uma chamada
case 1:
uri = Uri.parse("tel:99887766");
intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);
break;

// Visualiza um contato específico
// da lista de contatos
case 2:
uri = Uri.parse(
"content://contacts/people/5");
intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
break;

// Visualiza todos os contatos e permite
// selecionar um através do resultado
// da Activity
case 3:
uri = Uri.parse(
"content://contacts/people/");
intent = new Intent(Intent.ACTION_PICK, uri);
startActivityForResult(intent, 0);
break;

// Pesquisa uma posição do mapa
// !Seu AVD deve estar usando Google APIs!
case 4:
uri = Uri.parse(
"geo:0,0?q=Rua+Amelia,Recife");
intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
break;

// Executa uma música do SDcard
case 5:
uri = Uri.parse(
"file:///sdcard/musica.mp3");
intent = new Intent();
intent.setDataAndType(uri,"audio/mp3");
startActivity(intent);
break;

// Abrindo o editor de SMS
case 6:
uri = Uri.parse("sms:12345");
intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra("sms_body", "Corpo do SMS");
startActivity(intent);
break;

default:
finish();
}
}

@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {

super.onActivityResult(requestCode,
resultCode, data);

// Mostra um contato selecionado (case 3)
// em um Toast
if (data == null){
Toast.makeText(this, "Nenhum contato",
Toast.LENGTH_SHORT).show();
} else {
Uri uri = data.getData();
Toast.makeText(this, "Contato: "+ uri,
Toast.LENGTH_SHORT).show();
}
}
}


Vamos explicar o que foi feito acima. Criamos uma lista com várias opções de intents nativas. E quando o usuário clicar em qualquer uma dessas opções, a respectiva Intent será disparada.
Em todos os casos, utilizamos o objeto Uri para criar a Intent. Esse objeto indica um recurso do aparelho queremos acessar, e baseado no protocolo informado, ele irá disparar a atividade desejada.
Em conjunto com a Uri, associamos uma ação que queremos realizar sobre o recurso. Vamos exemplificar como o Android entende a solicitação, usando o caso do Browser: "O usuário está com a intenção (Intent) de visualizar (ACTION_VIEW) o recurso (Uri) http://nglauber.blogspot.com". Nesse momento o Android faz uma busca interna pra saber quem pode tratar essa intenção.

No exemplo de discar um número, utilizamos a ação ACTION_DIAL e o protocolo "tel:" seguido do número do telefone a ser discado. Notem que aqui há uma diferença entre "discar" (ACTION_DIAL) e "chamar" (ACTION_CALL). A primeira opção apenas disca o número não chama. Se quiséssemos chamar o número ao invés de apenas discá-lo, deveríamos utilizar a ACTION_CALL e adicionar a permissão abaixo no manifest:

<uses-permission name="android.permission.CALL_PHONE"/>

Na lista de contatos temos dois casos. O primeiro visualiza um contato utilizando o protocolo "content://" - que identifica um ContentProvider - passando o caminho do provider de contatos. Já o segundo caso está usando a ação ACTION_PICK para recuperar a Uri de um contato específico. Para tal, é utilizado o método startActivityForResult que iniciará a tela de contatos permitindo que selecionemos um da lista. Para tratar o retorno da atividade, utilizamos o método onActivityResult.
Para abrir a aplicação de Mapas em um local específico utilizamos o protocolo "geo:" seguido do do endereço do local desejado. É possível passar também a longitude e latitude (geo:lat,long).
Para executarmos o MP3 Player padrão do aparelho passando uma música, utilizamos o método setDataAndType e passamos a Uri com o protocolo "file://" e informando o MIME type do arquivo.
Mais informações aqui.

4br4ç05,
nglauber

terça-feira, 13 de outubro de 2009

Hospedagem grátis: Java + MySql

Olá povo,

Aqui vai uma boa dica para quem precisa hospedar, para testes, sites em Java . O site EATJ permite que você hospede sua aplicação de graça com as seguintes configurações:
  • Java 1.5 ou 1.6;
  • Servidor Tomcat 5.5 ou 6.0 (permitindo o acesso ao Manager e o Administrator);
  • Banco de dados MySql 4.1 ou 5.0 com acesso através do PHPMyAdmin.
Tudo isso através de uma interface bem simples. Para fazer o deploy basta fazer o upload do seu arquivo WAR (de até 50MB para conta gratuita) e pronto! Sua aplicação já está no ar.

A URL fica no seguinte formato: http://seuusuario.s156.eatj.com/
Um ponto negativo que eu verifiquei, é que as contas gratuitas tem seus servidores reiniciados a cada 6 horas. Mas como utilizei para testes, é uma boa opção.

4br4ç05,
nglauber

terça-feira, 6 de outubro de 2009

Android Widgets

Olá povo,

Todo mundo vem reparando a grande quantidade de inovação trazida pelo Android para as aplicações mobile. Entre essas inovações, temos a possibilidade de criar pequenas aplicações que podem ser adicionadas na tela principal do aparelho. A essas aplicações chamamos de widgets.

Procurando um pouco na internet você acha esse exemplo no site do próprio Android.

Porém, ele não é muito trivial nem didático para quem tá querendo aprender os conceitos sobre o assunto. Sendo assim, vou apresentar um exemplo bem simples de widget baseado no tutorial em inglês de Norbert Möhring)

Vamos lá! Crie um novo projeto Android no Eclipse. Dê um nome ao seu projeto; na opção "Build target" coloque a versão 1.5 (os widgets surgiram a partir dessa versão); o pacote que coloquei foi ngvl.android.widget; desmarque a caixa para criar uma atividade, pois não precisamos.

Altere o arquivo res/layout/main.xml, para que fique da seguinte forma:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:orientation="vertical"
android:background="@drawable/widget_bg_normal"
android:layout_gravity="center"
android:layout_height="wrap_content">

<TextView android:id="@+id/widget_textview"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal|center"
android:layout_marginTop="5dip"
android:padding="10dip"
android:textColor="@android:color/black"/>
</LinearLayout>


Notem que eu utilizei uma imagem como background do LinearLayout. Clique aqui para baixá-la, e depois coloque na pasta res/drawable do seu projeto. Essa imagem está no formato Nine Patch. O Android dá suporte a esse padrão de imagem que estabelece como uma imagem deve ser redimensionada. Mais informações sobre esse formato clique aqui.

O nosso widget não será uma Activity, mas sim um BroadcastReceiver que será notificado quando nosso widget receber atualizações (que serão configuradas mais na frente).


package ngvl.android.widget;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.widget.RemoteViews;

public class MeuWidget extends AppWidgetProvider {

private Handler handler;
private MyTime time;

@Override
public void onUpdate(Context ctx,
AppWidgetManager appWidgetManager,
int[] appWidgetIds) {

time = new MyTime(ctx, appWidgetManager)
handler = new Handler();
handler.post(time);
}

private class MyTime implements Runnable {
RemoteViews remoteViews;
AppWidgetManager appWidgetManager;
ComponentName thisWidget;
DateFormat dateFormat;

public MyTime(Context context,
AppWidgetManager appWidgetManager) {

this.appWidgetManager = appWidgetManager;
this.remoteViews = new RemoteViews(
context.getPackageName(),
R.layout.main);

this.thisWidget = new ComponentName(
context, MeuWidget.class);

this.dateFormat = SimpleDateFormat.
getTimeInstance(
SimpleDateFormat.MEDIUM,
Locale.getDefault());
}

@Override
public void run() {
remoteViews.setTextViewText(
R.id.widget_textview,
"Time "+ dateFormat.format(
new Date()));
appWidgetManager.updateAppWidget(
thisWidget, remoteViews);
handler.postDelayed(this, 1000);
}
}
}


Como podemos ver, nossa classe herda de android.appwidget.AppWidgetProvider que por sua vez, herda de android.content.BroadcastReceiver e devemos implementar o método onUpdate. Esse método é chamado toda vez o o widget precisar ser atualizado. Nesse método estou utilizando um Handler para atualizar o o conteúdo com a data/hora atual.
A classe MyTime recebe uma referência para o contexto da aplicação e um objeto do tipo AppWidgetManager. É a partir dele que obtemos as refências para as views utilizadas no layout. No método run dessa classe atualizamos o TextView com a data/hora atuais.

Agora vamos criar uma pasta chamada xml dentro da pasta res e criar o arquivo meuwidget_provider.xml com o seguinte conteúdo:


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dip"
android:minHeight="72dip"
android:initialLayout="@layout/main"
android:updatePeriodMillis="1000"/>


Nesse arquivo, definimos o tamanho mínimo do nosso componente, bem como o layout e o intervalo de atualizações do mesmo.

Agora, vamos atualizar o nosso AndroidManifest.xml:


<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="ngvl.android.widget"
android:versionCode="1"
android:versionName="1.0">

<application
android:icon="@drawable/icon"
android:label="@string/app_name">

<receiver
android:label="@string/app_name"
android:name=".MeuWidget">

<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/meuwidget_provider"
/>
</receiver>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>


Pronto! Agora mande rodar a aplicação. Depois que a aplicaçao for instalada (olhe no Console view ou no Logcat), vá até a Home Screen (a tela onde fica o relógio) e pressione Menu / Add / Widget e voilà! Nosso widget apareceu na lista. Selecione-o na lista e ele ficará na sua tela principal. Para removê-lo, basta pressioná-lo e arrasta-lo para lixeira (na parte inferior).



Espero que tenha sido útil. Qualquer dúvida, entrem em contato.

4br4ç05,
nglauber

quarta-feira, 16 de setembro de 2009

Curso de Android em Recife


Olá povo,

Estou lançando em parceria com a Especializa Treinamentos o curso de desenvolvimento de aplicações para a plataforma Android. As aulas serão ministradas por mim e o conteúdo do curso será baseado no livro do Ricardo Lecheta.
No curso, estudaremos tudo sobre essa revolucionária plataforma de desenvolvimento para smartphones. Os conceitos de Activities, Handles, ContentProvider e Services serão aplicados na prática. Vamos ver como armazenar informações no banco de dados do celular com SQLite. Comunicação com WebServices e com o GoogleMaps. Envio de SMS. E muito mais.

Quem tiver interesse, entre em contato com a Especializa ou comigo através do meu e-mail.

4br4ç05,
nglauber

terça-feira, 15 de setembro de 2009

Google Maps com Android 1.5

Olá povo,

Aqui vai um passo-a-passo pra quem for utilizar a API do GoogleMaps no Android.

Pré requisitos:
Android SDK 1.5
Eclipse Ganymede
Plugin ADT (Android Developer Tools) 0.9.1
JDK 1.5 ou superior com o diretório bin na variável de ambiente PATH.

Passo 1:
Obtenha o código do certificado digital que é usado para assinar as aplicações. Toda vez que o Eclipse executa uma aplicação Android ele gera um APK (Android PacKage) assinado. Essa assinatura é gerada a partir de um certificado digital de testes chamado debug.keystore que fica na pasta home do seu usuário. No meu caso, no Windows XP o caminho foi: C:\Documents and Settings\ngvl\.android\debug.keystore.

Para obter o código do certificado digital, utilizaremos a ferramenta keytool que vem com o JDK. Abra um terminal/prompt de comando e digite:

keytool -list -alias androiddebugkey -keystore "caminho do seu debug.keystore“

Será solicitado usuário e senha, digite "android" para ambos. Será gerado um resultado semelhante a esse:

androiddebugkey, 30/08/2009, PrivateKeyEntry,
Certificate fingerprint (MD5): AB:DB:39:2F:30:FC:7E:28:2B:7F:BF:54:7B:44:DF:C4


Passo 2:
Acesse o site http://code.google.com/android/maps-api-signup.html (é necessário ter uma conta do Google) digite o Certificate fingerprint no local indicado. Clique em "Generate API key". Será gerada uma chave parecida como a de baixo:

0CjoPthXnVvE9Uz3TW1d-ng82vlrdeTVBda4VPw


Pronto! agora você tem uma chave para o GoogleMaps.

Passo 3:
Vamos configurar nossa aplicação. Primeira coisa que devemos fazer é configurar na nossa aplicação o build target como "GoogleAPIs" e criar um AVD para esse target.

Depois, devemos colocar essa linha no arquivo AndroidManifest.xml, dentro da tag <application>


<uses-library
android:name="com.google.android.maps"/>


E essas duas fora da tag <application>, mas dentro da tag <manifest>.


<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission
android:name="android.permission.INTERNET" />


Agora só falta nós criarmos a classe que mostrará o mapa na tela:


import android.os.Bundle;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
public class MapaSimples extends MapActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MapView mapView = new MapView(this,
"0CjoPthXnVvE9Uz3TW1d-ng82vlrdeTVBda4VPw");
setContentView(mapView);
}

protected boolean isRouteDisplayed() {
return false;
}
}


Se rodarmos nossa aplicação teremos nossa aplicação exibindo um mapa na tela do aparelho.

Depois pretendo editar esse post ou adicionar novos mostrando como manipular o mapa no Android.

4br4ç05,
nglauber

quinta-feira, 10 de setembro de 2009

domingo, 30 de agosto de 2009

Utilizando Banco de Dados SQLite no Android

Olá povo,

Entre as muitas coisas legais que o Android trouxe, o suporte nativo ao banco de dados SQLite foi uma das mais importantes, comercialmente falando. Com ele podemos utilizar os bons e famosos comandos SQL (SELECT, INSERT, UPDATE, DELETE, etc) no mundo mobile. Quem programou em J2ME sabe o quão complicado é implementar um repositório "digno" que suporte uma boa quantidade de registros nessa plataforma (não me falem de RMS... :)

Nesse POST, vou colocar trechos de código que utilizam esses conceitos e tentarei explicá-los. A parte da interface gráfica, vou explicar apenas o que for relevante para o uso com banco de dados.

Para criarmos um banco de dados SQLite, podemos:
  • utilizar a ferramenta SQLite Expert Personal e colocar o arquivo do banco de dados na pasta /data/data/pacote_da_minha_aplicacao/databases;
  • ou via código, utilizando uma classe "helper" do SQLite.
Utilizaremos a segunda abordagem, pois na vida real não queremos colocar esse arquivo manualmente no aparelho. Pois, caso seja necessária uma atualização, teremos que atualizar N aparelhos.

Mãos à obra! Vamos criar uma classe que herda de android.database.sqlite.SQLiteOpenHelper. Ela será responsável por criar o banco quando a aplicação for instalada (método onCreate) e atualizá-lo para novas versões da aplicação (método onUpgrade).

public class SQLiteHelper extends SQLiteOpenHelper {
  private String scriptCreate;
  private String scriptDelete;

  public SQLiteHelper(Context ctx, String nomeBd, 
      int versaoBanco, String scriptCreate, 
      String scriptDelete) {

      super(ctx, nomeBd, null, versaoBanco);
      this.scriptCreate = scriptCreate;
      this.scriptDelete = scriptDelete;
  }

  public void onCreate(SQLiteDatabase db) {
      db.execSQL(scriptCreate);
  }

  public void onUpgrade(SQLiteDatabase db, 
      int oldVersion, int newVersion) {

      db.execSQL(scriptDelete);
      onCreate(db);
  }
}


Agora vamos criar a classe que realizará as operações de abrir, selecionar, inserir, atualizar e excluir registos no banco de dados:

public class RepositorioCarro {
  private SQLiteDatabase db;
  private SQLiteHelper dbHelper;
  private static final String SCRIPT_DB_DELETE = 
    "DROP TABLE IF EXISTS carros";
  private static final String SCRIPT_DB_CREATE =
    "create table carros (_id integer primary "+
    "key autoincrement, nome text not null, "+
    "placa text not null, ano text not null);";

  public RepositorioCarro (Context ctx){
    dbHelper = new SQLiteHelper(ctx, "curso", 1, 
      SCRIPT_DB_CREATE, SCRIPT_DB_DELETE);
  }

  private long inserir(Carro c){
    ContentValues cv = new ContentValues();
    cv.put("nome", c.getNome())
    cv.put("placa", c.getPlaca());
    cv.put("ano", c.getAno());
    db = dbHelper.getWritableDatabase();
    long id = db.insert("carros", null, cv);
    db.close();
    return id;
  }

  private long atualizar(Carro c){
    ContentValues cv = new ContentValues();
    cv.put("nome", c.getNome());
    cv.put("placa", c.getPlaca());
    cv.put("ano", c.getAno());

    db = dbHelper.getWritableDatabase();
    long rows = db.update("carros", cv, "_id = ?", 
       new String[]{ String.valueOf(c.getId())});
    db.close();
    return rows; // qtde. de linhas afetadas 
  } 

  public int excluir(int id){
    db = dbHelper.getWritableDatabase();
    int rows = db.delete("carros", "_id = ?", 
      new String[]{ String.valueOf(id) });
    return rows; // qtde. de linhas afetadas
  }

  public List<Carro> buscarCarroPorNome(String nome){
    List<Carro> lista = new ArrayList<Carro>();

    String[] columns = new String[]{
       "_id", "nome", "placa", "ano"};
    String[] args = new String[]{nome+"%"};

    db = dbHelper.getWritableDatabase();
    Cursor c = db.query("carros", columns, 
       "nome like ?", args, null, null, "nome");

    c.moveToFirst();
    while(!c.isAfterLast()){
      Carro carro = fillCarro(c);
      lista.add(carro);
      c.moveToNext();
    }
    c.close();
    db.close();
    return lista;
  }

  private Carro fillCarro(Cursor c) {
    Carro carro = new Carro();
    carro.setId((int)c.getLong(0));
    carro.setNome(c.getString(1));
    carro.setPlaca(c.getString(2));
    carro.setAno(c.getString(3));
    return carro;
  }
}


Como vocês podem ver, estou usando uma classe Carro também. Não vou listá-la aqui por se tratar de um POJO com os campos: id, nome, placa e ano do veículo.

Na classe acima, o construtor recebe uma referência do contexto da aplicação. Ela é repassada para nossa classe SQLHelper, que por sua vez, se encarregará de criar o nosso banco de dados se ele não existir. A classe android.database.sqlite.SQLiteOpenHelper (superclasse do nosso helper) tem o método getWritableDatabase() que retorna uma instância da classe android.database.sqlite.SQLiteDatabase, que será nosso canal de comunicação com o banco de dados.

As operações de inserir e atualizar utilizam: uma string que representa o nome da tabela no banco de dados; e um objeto da classe ContentValues, que nada mais é do que um conjunto par/valor com os parâmetros do comando SQL. Mas notem que o método update recebe ainda uma String que representa a cláusula WHERE do comando UPDATE (do SQL), bem como os parâmetros da mesma, que estão mapeados via caracter '?'.
"Por tabela", o método excluir vocês já devem ter entendido :)

O último método é o buscarCarroPorNome(String). Toda consulta SQL, retorna um objeto do tipo Cursor. Ele é bem parecido com o ResultSet do JDBC. Os parâmetros do método query são, respectivamente: String nome da tabela, String[] colunasDaTabela, String clausulaWhere, String[] paramsWhere, String groupBy, String having, String orderBy.

Para exibir os dados, precisaremos criar um Adapter, que converterá um Carro em uma linha de uma lista. Crie o arquivo de layout list_item.xml e coloque-o na pasta res/layout:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout android:layout_height="wrap_content" 
xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="fill_parent" 
android:id="@+id/carroRow">

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:id="@+id/txtNome" 
android:layout_weight="1" 
android:textSize="20dp"/>

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:id="@+id/txtAno" 
android:layout_marginRight="10px"/>

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:id="@+id/txtPlaca"  
android:layout_marginRight="10px"/>

</LinearLayout>


E agora, vamos criar nosso adapter.

public class CarroListAdapter extends BaseAdapter {
  private Context context;
  private List<Carro> lista;

  public CarroListAdapter(Context ctx, 
    List<Carro> carros) {

    context = ctx;
    lista = carros;
  }

  public int getCount() {
    return lista.size(); 
  }

  public Object getItem(int arg0) {
    return lista.get(arg0);
  }

  public long getItemId(int arg0) {
    return lista.get(arg0).getId();
  }

  public View getView(int pos, View convertView, 
    ViewGroup parent) {

    Carro c = lista.get(pos);
    LayoutInflater inflater = 
      (LayoutInflater)context.getSystemService(
        Context.LAYOUT_INFLATER_SERVICE);

    View view = inflater.inflate(
      R.layout.list_item, null);

    ((TextView)view.findViewById(R.id.txtNome)).
      setText(c.getNome());

    ((TextView)view.findViewById(R.id.txtAno)).
      setText(c.getAno());

    ((TextView)view.findViewById(R.id.txtPlaca)).
      setText(c.getPlaca());

    return view;
  }
}


Crie uma classe que herde de ListActivity e no onCreate, digite o seguinte código:
RepositorioCarro carrosDB = new RepositorCarro(this);
List<Carro>lista = carrosDB.buscarCarroPorNome("");
setAdapter(new CarroListAdapter(this, lista));

Para utilizar os demais métodos é facinho, não é? Basta criar objetos do tipo Carro, no qual os valores serão preenchidos por widgets (componentes de UI) e passá-los como parâmetros para os métodos do RepositorioCarro.

Espero que tenham entendido e gostado :)

Editado em 03/11/2011
Sempre que fizermos uma operação no banco de dados é importante que encerremos a conexão com ele. Tinha faltado isso nesse post, e agora lembrei de corrirgir.

4br4ç05
nglauber