Tutorial Android #24 – Widgets (II)

Padrão

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>

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);
	}
}

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"/>

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));
	}
}

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);

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);
}

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á! 🙂

4 comentários sobre “Tutorial Android #24 – Widgets (II)

  1. Mano, Parabéns pelo trabalho… ótimos artigos!

    Gostaria de ver um pouco mais sobre integração do Android com PHP. sobre autenticação, envio de formulários, seria bem bacana… Abraços

Deixe uma resposta