Tutorial Android 4 #8 – Utilizando Abas

Olá pessoal! No último post da série, adicionamos uma listagem com os contatos adicionados, mostrando um ícone indicando o tipo de contato, além do seu respectivo endereço. Hoje vamos separar a listagem do formulário, colocando uma aba para cada um.

Continuar lendo

Tutorial Android 4 #7 – Aprimorando a Listagem

Olá pessoal! Hoje vamos prosseguir mais um pouco com o nosso tutorial sobre Android. No último post, criamos a listagem dos contatos que, ao serem adicionados, apareciam na parte superior da tela. Hoje, vamos melhorar a exibição deles, passando a ser exibidos com o respectivo e-mail e um ícone identificando qual tipo de contato que se trata.

Continuar lendo

Tutorial Android 4 #1 – Montando o Ambiente de Desenvolvimento

Android é um sistema operacional móvel que roda sobre o núcleo Linux. Foi inicialmente desenvolvido pela Google e posteriormente pela Open Handset Alliance. O Android permite aos desenvolvedores escreverem software na linguagem de programação Java controlando o dispositivo via bibliotecas desenvolvidas pela Google. Hoje em dia, milhões de celulares e tablets utilizam o Android como sistema operacional.

Continuar lendo

Tutorial Android #25 – Fazendo Ligações (Chamadas)

Olá pessoal! Tudo bem?

Sei que é triste a dor da partida, mas esse é o último tutorial dessa primeira série de tutoriais sobre Android. Mas como disse no último post, isso não significa que o tema não será mais abordado aqui no blog. Estou considerando seriamente a possibilidade de, em breve, voltar a falar de Android e as novidades que temos ao utilizar a versão 4.

Mas por enquanto, vamos fechar essa primeira série com chave de ouro. Como toda a série foi voltada para Android, um sistema utilizado massivamente em aparelhos celulares, vamos ver hoje como realizar chamadas.

Nesta última sessão de modificações em nosso aplicativo, vamos adicionar um campo chamado telefone aos nossos restaurantes e, a partir dele, vamos possibilitar que o usuário faça uma chamada diretamente do nosso aplicativo. Bacana, não?

Bom, começando, temos inicialmente que atualizar o nosso modelo de dados para que armazene o novo dado necessário (o número de telefone). Primeiramente, modifique o método onCreate() da classe GerenciadorRestaurantes para abrigar o novo campo:

@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);");
}
view raw snippet01.java hosted with ❤ by GitHub

Em seguida, altere o método onUpgrade() para atualizar o modelo de dados do banco, caso o usuário esteja vindo de uma versão anterior:

@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");
}
}
view raw snippet02.java hosted with ❤ by GitHub

Pronto. Agora, vamos procurar pelos métodos inserir(), atualizar(), obterTodos() e obterPorId() para adicionar o campo telefone a eles também.

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,
String twitter, String telefone) {
ContentValues valores = new ContentValues();
String[] argumentos = {id};
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().update("restaurantes", valores, "_id=?", argumentos);
}
public Cursor obterTodos(String ordenacao) {
return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " +
"anotacoes, twitter, latitude, longitude, telefone FROM restaurantes ORDER BY " +
ordenacao, null);
}
public Cursor obterPorId(String id) {
String[] argumentos = {id};
return getReadableDatabase().rawQuery(
"SELECT _id, nome, endereco, tipo, anotacoes, twitter, latitude," +
" longitude, telefone FROM restaurantes WHERE _id = ?", argumentos);
}
view raw snippet03.java hosted with ❤ by GitHub

Para concluir as alterações nesta classe, crie o método obterTelefone():

public String obterTelefone(Cursor c) {
return c.getString(8);
}
view raw snippet04.java hosted with ❤ by GitHub

Pronto. Com relação a persistência, já estamos aptos a prosseguir. Vamos agora adicionar o campo telefone aos nossos formulários. 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 após o campo de endereço, no arquivo res/layout/form_detalhes.xml.

<TableRow>
<TextView android:text="@string/telefone" />
<EditText android:id="@+id/telefone" />
</TableRow>
view raw snippet05.xml hosted with ❤ by GitHub

Agora no res/layout-land/form_detalhes.xml. Também, logo depois do trecho do campo de endereço.

<TableRow>
<TextView android:text="@string/telefone" />
<EditText
android:id="@+id/telefone"
android:layout_span="2" />
</TableRow>
view raw snippet06.xml hosted with ❤ by GitHub

Pronto. Nossas modificações agora serão na classe FormularioDetalhes. Primeiramente adicione o atributo telefone à classe:

EditText telefone = null;
view raw snippet07.java hosted with ❤ by GitHub

Agora, precisamos atualizar os métodos onCreate(), salvar(), carregar(), onSaveInstanceState() e onRestoreInstanceState(). Pode parecer bastante coisa, mas são apenas ajustes leves para adicionar o novo campo.

@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();
}
private void carregar() {
Cursor c = gerenciador.obterPorId(idRestaurante);
c.moveToFirst();
nome.setText(gerenciador.obterNome(c));
endereco.setText(gerenciador.obterEndereco(c));
telefone.setText(gerenciador.obterTelefone(c));
anotacoes.setText(gerenciador.obterAnotacoes(c));
twitter.setText(gerenciador.obterTwitter(c));
if (gerenciador.obterTipo(c).equals("rodizio")) {
tipos.check(R.id.rodizio);
} else if (gerenciador.obterTipo(c).equals("fast_food")) {
tipos.check(R.id.fast_food);
} else {
tipos.check(R.id.a_domicilio);
}
latitude = gerenciador.obterLatitude(c);
longitude = gerenciador.obterLongitude(c);
localizacao.setText(String.valueOf(gerenciador.obterLatitude(c)) +
", " + String.valueOf(gerenciador.obterLongitude(c)));
c.close();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("nome", nome.getText().toString());
outState.putString("endereco", endereco.getText().toString());
outState.putString("telefone", telefone.getText().toString());
outState.putString("anotacoes", anotacoes.getText().toString());
outState.putInt("tipo", tipos.getCheckedRadioButtonId());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
nome.setText(savedInstanceState.getString("nome"));
endereco.setText(savedInstanceState.getString("endereco"));
telefone.setText(savedInstanceState.getString("telefone"));
anotacoes.setText(savedInstanceState.getString("anotacoes"));
tipos.check(savedInstanceState.getInt("tipo"));
}
view raw snippet08.java hosted with ❤ by GitHub

Ufa! Perceberam como as alterações foram mínimas? 😀

Prosseguindo, precisamos dizer ao Android que nossa aplicação deseja realizar chamadas. Para isso, adicione a seguinte linha às permissões no arquivo AndroidManifest.xml.

<uses-permission android:name="android.permission.CALL_PHONE" />
view raw snippet09.xml hosted with ❤ by GitHub

Agora, vamos criar a opção ao menu para realizar as chamadas. Edite o arquivo res/menu/opcao_detalhes.xml para acomodar a nova opção (é… vai ficar meio espremido em telas pequenas…).

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/twitter"
android:title="@string/timeline_twitter"
android:icon="@drawable/twitter" />
<item
android:id="@+id/localizacao"
android:title="@string/salvar_localizacao"
android:icon="@drawable/gps" />
<item
android:id="@+id/mapa"
android:title="@string/exibir_mapa"
android:icon="@drawable/mapa" />
<item
android:id="@+id/chamar"
android:title="@string/chamar"
android:icon="@drawable/chamada" />
</menu>
view raw snippet10.xml hosted with ❤ by GitHub

O arquivo de ícone utilizado foi o ic_menu_call.png encontrado na pasta de instalação do Android e devidamente renomeado para chamada.png.

Agora precisamos definir as novas strings utilizadas no formulário e no menu. Adicione-as ao arquivo res/values/strings.xml

<string name="telefone">Telefone:</string>
<string name="chamar">Telefonar</string>
view raw snippet11.xml hosted with ❤ by GitHub

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

<string name="telefone">Teléfono:</string>
<string name="chamar">Llamar</string>
view raw snippet12.xml hosted with ❤ by GitHub

Por fim, vamos fazer com que a opção de menu realize a chamada. Adicione o seguinte trecho aos encadeamentos de ifs no método onOptionsItemSelected().

} else if (item.getItemId() == R.id.chamar) {
String numero = "tel:" + telefone.getText().toString();
if (numero.length() > 4) {
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(numero)));
}
}
view raw snippet13.java hosted with ❤ by GitHub

Execute a aplicação para conferir as modificações.

Caso queira que a ligação seja realizada diretamente, sem exibir o discador, modifique, na linha 158 do trecho ali em cima, ACTION_DIAL por ACTION_CALL.

Bom pessoal, é isso! Como não podia deixar de ser, para baixar o projeto, basta clicar aqui.

Agradeço a todos que vem acompanhando o blog. Continuem sempre por aqui, pois sempre tem coisa nova! Comentem, critiquem, cliquem nos banners e enviem sugestões!

Até logo!!!

Tutorial Android #24 – Widgets (II)

Olá pessoal! Como estão?

No último post, vimos como criar um widget simples com o nome do restaurante. Hoje, vamos incrementá-lo em dois pontos principais: (1) ele terá um botão que mudará o restaurante que é exibido, mostrando outro aleatoriamente e; (2) ao tocar sobre o nome do restaurante no widget, é aberto o formulário para que você possa ver as outras informações sobre aquele restaurante.

Então, mãos à massa!

O primeiro passo é adicionarmos o botão ao layout de nosso widget. Dessa forma, abra o arquivo widget.xml que está em res/layout e faça a adição da imagem do botão, do tipo ImageButton:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/frame">
<TextView
android:id="@+id/nome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:textSize="10pt"
android:textColor="#FFFFFF" />
<ImageButton
android:id="@+id/proximo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:src="@drawable/proximo" />
</RelativeLayout>
view raw snippet01.xml hosted with ❤ by GitHub

Os atributos, no ponto em que estamos, não devem ser nenhuma surpresa. Neste ponto, já deve ser possível vê-lo no layout ao recompilar a aplicação.

O arquivo de imagem do ícone pode ser baixado junto com o projeto no final deste post.

No post anterior, toda a nossa lógica de consulta ao banco estava dentro do método onUpdate() do nosso widget. Isso funciona bem, mas, caso a lógica se expanda muito, corremos o risco da atualização demorar mais que o esperado e comprometer o desempenho da aplicação, já que o método é chamado em nossa thread principal.

Para melhorar isso, vamos novamente fazer uso de um IntentService que nos possibilita a realizar a consulta de modo assíncrono, sem comprometer o sistema. Portanto, crie uma classe chamada WidgetService e coloque-a no pacote net.rafaeltoledo.restaurante. Esta classe, que estenderá IntentService abrigará a lógica que estávamos executando no widget.

package net.rafaeltoledo.restaurante;
import android.app.IntentService;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.database.Cursor;
import android.widget.RemoteViews;
public class WidgetService extends IntentService {
public WidgetService() {
super("WidgetService");
}
@Override
protected void onHandleIntent(Intent intent) {
ComponentName cn = new ComponentName(this, WidgetAplicativo.class);
RemoteViews atualizarFrame = new RemoteViews("net.rafaeltoledo.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));
c.close();
} else {
atualizarFrame.setTextViewText(R.id.nome, getString(R.string.vazio));
}
} finally {
gerenciador.close();
}
mgr.updateAppWidget(cn, atualizarFrame);
}
}
view raw snippet02.java hosted with ❤ by GitHub

Basicamente o que fizemos foi passar a lógica para o nosso WidgetService. O próximo passo é adicionar esse nosso serviço lá no arquivo AndroidManifest.xml para que ele possa funcionar. Assim, adicione ao fim do nó application a seguinte linha:

<service android:name=".WidgetService" />
view raw snippet03.xml hosted with ❤ by GitHub

Agora, vamos atualizar a classe WidgetAplicativo para que faça uso do nosso serviço.

package net.rafaeltoledo.restaurante;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
public class WidgetAplicativo extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.startService(new Intent(context, WidgetService.class));
}
}
view raw snippet04.java hosted with ❤ by GitHub

Pronto. A parte mais “complicada” está feita. Agora precisamos apenas gerenciar os toques no botão e no nome do restaurante. Primeiramente, vamos fazer com que um novo restaurante seja exibido caso seja acionado o botão. 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 método onHandleEvent() da classe WidgetService:

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);
view raw snippet05.java hosted with ❤ by GitHub

Com isso, toda vez que o botão for acionado, o nosso serviço 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 formulário. Para isso, precisaremos de realizar algumas mudanças sutis no método onHandleIntent(). As principais são que, além do nome, precisaremos também do identificador, pois através dele iremos carregar os dados no formulário. Além disso, também utilizaremos um PendingIntent para acionar a exibição do formulário.

@Override
protected void onHandleIntent(Intent intent) {
ComponentName cn = new ComponentName(this, WidgetAplicativo.class);
RemoteViews atualizarFrame = new RemoteViews("net.rafaeltoledo.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);
}
view raw snippet06.java hosted with ❤ by GitHub

E… prontinho! É isso. Como sempre, para baixar o código do aplicativo, só clicar aqui.

O próximo tutorial de Android muito provavelmente será o último. Não porque eu ache que não há mais o que ser tratado, mas porque a ideia dos tutoriais era mostrar uma visão geral, com alguns conceitos básicos. 25 tutoriais é um bom número. E isso também me permitirá abordar outros assuntos. (estou com ideia de pelo menos mais 6 séries de tutoriais!)

Então… aguardem pelo gran finale Android! Até lá! 🙂

Tutorial Android #23 – Widgets (I)

Olá pessoal! No post de hoje vamos ver a primeira parte sobre como criarmos widgets no Android.

Pra quem não sabe, widgets é uma espécie de miniatura do aplicativo que você pode deixar em uma das áreas de trabalho do Android, colocando à disposição do usuário informações de maneira mais rápida e prática. O widget também pode redirecionar o usuário para o aplicativo principal, funcionando como uma espécie de “atalho”.

Bom, vamos então começar a colocar a mão na massa!

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:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/frame">
<TextView android:id="@+id/nome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:textSize="10pt"
android:textColor="#FFFFFF" />
</RelativeLayout>
view raw snippet01.xml hosted with ❤ by GitHub

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 junto com o projeto no fim do post. Por que este 9? Porque a imagem é uma NinePatch, ideal para compor fundos de frames. Entenda melhor como funciona aqui.

O próximo passo é criarmos uma classe para gerenciar o conteúdo do widget. Inicialmente, apenas crie uma classe chamada WidgetAplicativo dentro do pacote net.rafaeltoledo.restaurante, estendendo AppWidgetProvider.

package net.rafaeltoledo.restaurante;
import android.appwidget.AppWidgetProvider;
public class WidgetAplicativo extends AppWidgetProvider {
}
view raw snippet02.java hosted with ❤ by GitHub

Continuando, vamos agora definir algumas propriedades do widget em um arquivo XML. Crie dentro da pasta res/xml o arquivo provedor_widget.xml.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="300dip"
android:minHeight="79dip"
android:updatePeriodMillis="1800000"
android:initialLayout="@layout/widget" />
view raw snippet03.xml hosted with ❤ by GitHub

Basicamente definimos a largura e altura mínimas, o tempo de atualização das informações 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.

<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>
view raw snippet04.xml hosted with ❤ by GitHub

Neste trecho, definimos que a classe que representa o widget é a WidgetApp, que o nome e o ícone a serem exibidos nas opções são os mesmos da aplicação no menu (app_name e ic_launcher). Além disso, definimos que o widget realizará operações de atualização e que suas propriedades estão definidas no arquivo provedor_widget dentro da pasta xml.

Por fim, vamos implementar o método onUpdate() para a classe WidgetApp. É este método que fará a busca em nosso banco de dados para exibir o nome de um restaurante.

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
ComponentName cn = new ComponentName(context, WidgetApp.class);
RemoteViews atualizarFrame = new RemoteViews("net.rafaeltoledo.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 LIMIT 1 OFFSET ?", args);
c.moveToFirst();
atualizarFrame.setTextViewText(R.id.nome, c.getString(0));
} else {
atualizarFrame.setTextViewText(R.id.nome, context.getString(R.string.vazio));
}
} finally {
gerenciador.close();
}
appWidgetManager.updateAppWidget(cn, atualizarFrame);
}
view raw snippet05.java hosted with ❤ by GitHub

Resumidamente, esse trecho de código:

  • Cria um objeto RemoteView, que nos permite modificar o widget;
  • Estabelece uma conexão com o banco de dados;
  • Verifica quantos restaurantes salvos existem;
  • Carrega um restaurante aleatório (por isso o uso de Math.random());
  • Exibe o nome do restaurante, ou uma mensagem dizendo que não existem restaurantes cadastrados;
  • Atualiza o widget propriamente dito.

A mensagem “vazio” (R.string.vazio) deve ser definida nos seus arquivos string.xml nas pastas values que você tem. No meu caso, vou defini-la em português e em espanhol (idiomas que minha aplicação suporta).

<string name="vazio">Nenhum registro.</string>
view raw snippet06.xml hosted with ❤ by GitHub
<string name="vazio">Ningún registro.</string>
view raw snippet07.xml hosted with ❤ by GitHub

E pronto! Para ativar o widget, clique e segure sobre a área de trabalho para aparecer o menu e a opção de inserir widget.

Pra baixar o projeto, só clicar aqui.

Bom, é isso pessoal! No próximo post vamos melhorar esse widget! Até lá!

Tutorial Android #20 – Alarmes

Olá pessoal! Aqui estamos nós com mais um tutorial de Android para alegria geral da nação. No episódio tutorial de hoje, vamos ver como criar um alarme para nos avisar a hora do almoço (tudo a ver com o aplicativo de restaurante, não?).

O primeiro passo é criarmos uma forma para o usuário configurar o horário em que ele deseja ser avisado do almoço. Poderíamos definir isto em uma Activity, mas esta opção soa mais como uma configuração. Dessa forma, vamos criar a classe PreferenciaHorario no pacote net.rafaeltoledo.restaurante, estendendo a classe DialogPreference. Logo em seguida explicarei os conceitos principais dela.

package net.rafaeltoledo.restaurante;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;
public class PreferenciaHorario extends DialogPreference {
private int ultimaHora = 0;
private int ultimoMinuto = 0;
private TimePicker picker = null;
public static int obterHora(String tempo) {
String[] fragmentos = tempo.split(":");
return Integer.parseInt(fragmentos[0]);
}
public static int obterMinuto(String tempo) {
String[] fragmentos = tempo.split(":");
return Integer.parseInt(fragmentos[1]);
}
public PreferenciaHorario(Context contexto) {
this(contexto, null);
}
public PreferenciaHorario(Context contexto, AttributeSet atributos) {
this(contexto, atributos, 0);
}
public PreferenciaHorario(Context contexto, AttributeSet atributos, int estilo) {
super(contexto, atributos, estilo);
setPositiveButtonText("Definir");
setNegativeButtonText("Cancelar");
}
@Override
protected View onCreateDialogView() {
picker = new TimePicker(getContext());
return picker;
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
picker.setCurrentHour(ultimaHora);
picker.setCurrentMinute(ultimoMinuto);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
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);
}
}
view raw snippet01.java hosted with ❤ by GitHub

Bastante coisa, não? Vamos por partes.

Os métodos obterHora() e obterMinuto() servem para extrair a parte inteira do horário que será armazenado como uma string “00:00“. Temos três versões do construtor da classe, que no final sempre referenciam o terceiro. Isso é devido à superclasse. Ainda no construtor, definimos os nomes dos botões na janela de configuração de horário. onCreateDialogView() devolve um objeto View com a tela criada. Poderíamos aqui definirmos um layout, mas simplesmente devolvemos um widget TimePicker. O método onBindDialogView() é chamado após o onCreateDialogView() é encarregado de preencher a caixa de diálogo. onDialogClose(), como o próprio nome diz, é chamado quando a janelinha é encerrada. Caso o usuário pressione o botão de confirmar (condição positiveResult), o valor é armazenado em SharedPreferences. O método onGetDefaultValue() é utilizado para a conversão interna do Android para o tipo do objeto. Por último, o método onSetInitialValue(), como o próprio nome diz, atribui um valor padrão. Ele verifica se há algum valor já salvo, ou padrão ou então atribui 00:00.

O próximo passo é adicionar a opção para a configuração do alarme pelo usuário. Dessa forma, edite o arquivo preferencias.xml da seguinte forma:

<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 Almoço"
android:summary="Marque se deseja ser informado sobre a hora do almoço" />
<net.rafaeltoledo.restaurante.PreferenciaHorario
android:key="horario_alarme"
android:title="Horário do Alarme do Almoço"
android:defaultValue="12:00"
android:summary="Configure seu horário desejado para o alarme"
android:dependency="alarme" />
</PreferenceScreen>
view raw snippet02.xml hosted with ❤ by GitHub

A primeira opção adicionada, do tipo CheckBoxPreference não tem muito segredo… a segunda, foi a que definimos na classe PreferenciaH0rario. Configuramos seu valor padrão para 12:00 e definimos que ela depende da opção alarme, ou seja, ela só estará habilitada caso alarme também esteja habilitada.

Neste projeto vamos utilizar o AlarmManager para gerenciar o nosso alarme. Porém, ele tem uma falha: toda vez que o celular é desligado, ao ligar novamente os alarmes não são configurados. Para resolver isso, vamos criar a classe ReceptorBoot para realizar essa configuração toda vez que o sistema for ligado. Crie-a no pacote net.rafaeltoledo.restaurante.

package net.rafaeltoledo.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) {
}
}
view raw snippet03.java hosted with ❤ by GitHub

A tarefa do ReceptorBoot será realizado no método 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.

<receiver android:name=".ReceptorBoot" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
view raw snippet04.xml hosted with ❤ by GitHub

Além disso, adicione também a permissão para obter o sinal de boot completo do sistema.

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
view raw snippet05.xml hosted with ❤ by GitHub

Precisamos agora tratar as preferências do usuário para configurar o alarme. Quando o usuário ativar o checkbox do alarme, precisamos ativar o alarme no tempo selecionado. Quando o usuário 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 métodos na classe ReceptorBoot. Para corrigir os imports, só lembrar do Ctrl + Shift + O.

public static void configurarAlarme(Context contexto) {
AlarmManager gerenciador = (AlarmManager) contexto.getSystemService(Context.ALARM_SERVICE);
Calendar cal = Calendar.getInstance();
SharedPreferences preferencias = PreferenceManager.getDefaultSharedPreferences(contexto);
String horario = preferencias.getString("horario_alarme", "12:00");
cal.set(Calendar.HOUR_OF_DAY, PreferenciaHorario.obterHora(horario));
cal.set(Calendar.MINUTE, PreferenciaHorario.obterMinuto(horario));
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
if (cal.getTimeInMillis() < System.currentTimeMillis()) {
cal.add(Calendar.DAY_OF_YEAR, 1);
}
gerenciador.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, obterIntentPendente(contexto));
}
public static void cancelarAlarme(Context contexto) {
AlarmManager gerenciador = (AlarmManager) contexto.getSystemService(Context.ALARM_SERVICE);
gerenciador.cancel(obterIntentPendente(contexto));
}
private static PendingIntent obterIntentPendente(Context contexto) {
Intent i = new Intent(contexto, ReceptorAlarme.class);
return PendingIntent.getBroadcast(contexto, 0, i, 0);
}
view raw snippet06.java hosted with ❤ by GitHub

Também atualize o método onReceive():

@Override
public void onReceive(Context context, Intent intent) {
configurarAlarme(context);
}
view raw snippet07.java hosted with ❤ by GitHub

Bem, no código listado acima, primeiramente, ao receber o sinal do boot (método onReceive()), configuramos o alarme, através do método configurarAlarme(). Neste método, obtemos o AlarmManager, e obtemos as preferências do usuário para o alarme (se existirem), e a montamos em um objeto do tipo Calendar. Caso alarme seja anterior ao horário atual, adicionamos um dia a ele e configuramos para repeti-lo diariamente. Já no método cancelarAlarme(), cancelamos o alarme vinculado ao contexto, obtendo o AlarmManager e obtendo um objeto PendingIntent (como se fosse uma tarefa pendente) com o método obterIntentPendente().

No código que temos até agora, o alarme só é armado na inicialização do sistema. Para que ele funcione da maneira como desejamos, precisamos adicionar alguns método a classe EdicaoPreferencias:

@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);
}
}
};
view raw snippet08.java hosted with ❤ by GitHub

Lembre-se também de adicionar o membro privado da classe chamado preferencias:

SharedPreferences preferencias = null;
view raw snippet09.java hosted with ❤ by GitHub

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:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hora do almoço!"
android:textSize="30sp"
android:textStyle="bold" />
view raw snippet10.xml hosted with ❤ by GitHub

Bastante simples, ele simplesmente exibirá bem grande na tela Hora do almoço!. Agora vamos criar a Activity que exibirá o aviso propriamente dito. Crie a classe AlarmeActivity no pacote net.rafaeltoledo.restaurante:

package net.rafaeltoledo.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);
}
}
view raw snippet11.java hosted with ❤ by GitHub

Crie também uma classe chamada ReceptorAlarme que será encarregada de iniciar a AlarmeActivity.

package net.rafaeltoledo.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);
}
}
view raw snippet12.java hosted with ❤ by GitHub

Encerrando (ufa!), falta somente adicionarmos esse último receptor no AndroidManifest.xml. Adicione-o no fim do nó application.

<receiver android:name=".ReceptorAlarme" />
view raw snippet13.xml hosted with ❤ by GitHub

E é isso!

Como sempre, pra baixar o projeto, só clicar aqui.

Até logo!