terça-feira, 1 de maio de 2012

Criando uma calculadora usando BroadcastReceiver

Hoje, estudei um pouco sobre BroadcastReceiver com auxilio do livro "Google Android de R. Lecheta". Para reforçar aproveitei para usar alguns outros conceitos já estudados nesta pequena aplicação de teste que será explicada neste post.

A aplicação desenvolvida é algo muito simples e no mundo real ninguém criaria uma calculadora como esta. Lembre-se, o aplicativo foi criado apenas para estudar alguns conceitos (Principalmente o BroadcastReceiver).

Mas de que se trata o BroadcastReceiver?
Assim como as Activities podem ser chamadas por outras Activities através da utilização de Intents, Receivers também podem ser "chamados".

A principal diferença entre uma Activity e um Receiver é que o Receiver é executado em background e geralmente não deve realizar interação com o usuário (para evitar interrompe-lo). Além disso, um receiver possui um tempo máximo para executar alguma tarefa determinado em 10 segundos. Caso sua tarefa ultrapasse este tempo o Receiver é interrompido pelo sistema operacional.

Para as Activities que disponibilizamos, geralmente utilizamos um IntentFilter que diz o nome da Action e Category que permitem ao android encontrar nossa aplicação.

O exemplo clássico de um IntentFilter, é o que já é declarado quando criamos uma aplicação de HelloWorld no eclipse:
  <activity
      android:name=".LivroAndroidCap7Activity"
      android:label="@string/app_name" >
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>
Para um Receiver responder à uma Intent, o mesmo também precisa ser declarado com um IntentFilter. Um Receiver pode atender a várias Actions nativas como BOOT, recebimento de SMS, eventos relacionados à bateria além de poder atender a Actions customizadas (strings definidas pelo próprio desenvolvedor).
Criando a calculadora
Bom, então vamos logo para a criação da calculadora. A idéia aqui é criar um aplicativo que possui uma tela inicial que mostra apenas um botão com o rótulo Calcular conforme mostrado na figura abaixo:
Ao pressionar este botão, o usuário receberá um formulário com 3 campos dentro de um AlertDialog. O formulário poderia ter sido criado na mesma tela que o botão calcular, mas eu quis complicar um pouco mais só para brincar um pouco mais com outras funcionalidades do android (neste caso usar um AlertDialog).

Caso o usuário selecione o botão Calcular neste formulário, os dados para o cálculo são enviados para o sistema operacional através de uma Intent definida com o seguinte intent filter:
  <intent-filter>
      <action android:name="CALCULO" />
      <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
Considerando este IntentFilter, outros aplicativos podem ser criados para enviar um broadcast com dados para CALCULO. Outros Receivers também podem ser criados para receber dados para calcular e efetuar estes cálculos de forma diferente (por exemplo algum tipo de cálculo alienígena).
A primeira coisa que criei neste projeto foram os layouts definidos no diretório res/layout do projeto.
main.xml (layout que possui apenas o botão calcular - tela inicial)
  <?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" >

      <TextView
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="@string/calculadora" />
 
   <Button 
       android:id="@+id/btCalcular" 
       android:layout_width="wrap_content" android:layout_height="wrap_content" 
       android:text="@string/calcular" />
 
  </LinearLayout>
layout_form.xml (layout que possui os campos do formulário - usado no AlertDialog)
  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical" >
      
      <TextView 
          android:layout_width="fill_parent" android:layout_height="wrap_content"
          android:text="@string/primeiro_numero"
           />
      
   <EditText 
       android:id="@+id/numero1"
       android:inputType="number"
       android:layout_width="fill_parent" android:layout_height="wrap_content" 
       />
 
      <TextView 
          android:layout_width="fill_parent" android:layout_height="wrap_content"
          android:text="@string/segundo_numero"
           />
      
   <EditText 
       android:id="@+id/numero2"
       android:inputType="number"
       android:layout_width="fill_parent" android:layout_height="wrap_content" 
       />

   <TextView 
          android:layout_width="fill_parent" android:layout_height="wrap_content"
          android:text="@string/operacao"
           />
 
   <Spinner 
       android:id="@+id/operacao" 
       android:layout_width="fill_parent" android:layout_height="wrap_content"/>    

  </LinearLayout>
Após definir os layouts, criei as classes para a Activity e uma para o Receiver. CalculoReceiver.java
  package br.com.livro.cap9;

  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.os.Bundle;
  import android.util.Log;

  public class CalculoReceiver extends BroadcastReceiver {

   @Override
   public void onReceive(Context ctx, Intent intent) {
  
    // recupera os dados para calculo que qualquer Intent  
    // recebida para este fim precisa enviar
    Bundle extras = intent.getExtras();
    if (extras != null) {
     String n1 = extras.getString("numero1");
     String n2 = extras.getString("numero2");
     String op = extras.getString("operacao");
   
     // recebe os valores
     // faz uma pequena validação
     // e então decide qual operaçao executar guardando o valor em resultado
     if (!isEmpty(n1) && !isEmpty(n2) && !isEmpty(op)) {
      int in1 = Integer.parseInt(n1);
      int in2 = Integer.parseInt(n2);
      int iop = Integer.parseInt(op);
      int resultado = 0;
    
      switch (iop) {
       case 0: // soma
        resultado = in1 + in2;
        break;
       case 1: // subtracao
        resultado = in1 - in2;
        break;
       case 2: // multiplicacao
        resultado = in1 * in2;
        break;
       case 3: // divisao
        resultado = in1 / in2;
        break;
      }
    
      // após efetuar o cálculo apenas gera um log 
      // que poderá ser verificado pelo LogCat no eclipse.
      Log.i("CALCULO", "resultado do calculo: " + resultado);
     }
    }
   }

   private boolean isEmpty(String v) {
    return (v == null || v.length() == 0);
   }
  }
CalculadoraActivity.java
  package br.com.livro.cap9;

  import android.app.Activity;
  import android.app.AlertDialog;
  import android.content.Context;
  import android.content.DialogInterface;
  import android.content.Intent;
  import android.os.Bundle;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.View.OnClickListener;
  import android.widget.AdapterView;
  import android.widget.AdapterView.OnItemSelectedListener;
  import android.widget.ArrayAdapter;
  import android.widget.Button;
  import android.widget.EditText;
  import android.widget.Spinner;
  import android.widget.Toast;

  public class CalculadoraActivity extends Activity implements OnItemSelectedListener, OnClickListener {

   protected static final int SOMA = 0;
   private int codigoOperacao = SOMA;
 
   // lista de operações disponíveis
   private String[] operacoes = {
     "Soma", "Subtração", "Multiplicação", "Divisão"
   };
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
  
    // adicionando o comportamento para o botão calcular
    Button btCalcular = (Button) findViewById(R.id.btCalcular);
    btCalcular.setOnClickListener(this);
  
   }

   /**
    * Ao selecionar um item no Spinner (famoso combobox em outras plataformas),
    * a posição da operação selecionada é guardada em codigoOperacao que podera ser
    * encaminhada através da Intent posteriormente quando o usuário solicitar o calculo.
    */
   public void onItemSelected(AdapterView adapterView, View view,
    int position, long id) {
    codigoOperacao = position;
   }

   @Override
   public void onNothingSelected(AdapterView arg0) {
    codigoOperacao = SOMA; // operação padrao é a soma
   }

   @Override
   public void onClick(View v) {
  
    // criando a janela de dialogo
    AlertDialog.Builder alert = new AlertDialog.Builder(this);
  
    // Uma das coisas interessantes em utilizar este AlertDialog no exemplo, foi 
    // utilizar também o LayoutInflater conforme mostrado abaixo.
    // O LayoutInflater cria uma instancia de View baseando-se
    // no layout informado em inflater.inflate
    LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    final View viewForm = inflater.inflate(R.layout.layout_form, null);
  
    // Desta forma é possivel definir o conteúdo do AlertDialog
    // como uma View ao invés de uma mensagem texto.
    alert.setView(viewForm);
  
    // Criamos entao os botoes para uma açao positiva e uma açao negativa  
    // utilizando os devidos Listeners para click nos botoes.
    // Repare que a classe OnClickListener está dentro de DialogInterface 
    // e não dentro de View como é utilizado para Buttons.
    alert.setPositiveButton("Calcular", new DialogInterface.OnClickListener() {
   
     public void onClick(DialogInterface dialog, int which) {
      // aqui dentro recuperamos os dados registrados na view do formulário
      // por isso, para recuperar cada EditText, foi utilizado o viewForm.findViewById.
      // Pois apenas esta view possui os componentes registrados.
      // Tente utilizar findViewbyId apenas para ver o que acontece =P
      EditText txtNumero1 = (EditText) viewForm.findViewById(R.id.numero1);
      EditText txtNumero2 = (EditText) viewForm.findViewById(R.id.numero2);
    
      Bundle extras = new Bundle();
      extras.putString("numero1", txtNumero1.getText().toString());
      extras.putString("numero2", txtNumero2.getText().toString());
      extras.putString("operacao", Integer.toString(codigoOperacao));
    
      // Este é o ponto principal para o estudo do BroadcastReceiver.
      // É aqui que criamos uma intent para calculo que será 
      // recebida pelo nosso Receiver CalculoReceiver.
      Intent itCalculo = new Intent();
      itCalculo.setAction("CALCULO");
      itCalculo.putExtras(extras);
      sendBroadcast(itCalculo);
    
      Toast.makeText(CalculadoraActivity.this, "calculo enviado para broadcast", Toast.LENGTH_SHORT).show();
     }
    });
  
    alert.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {

     public void onClick(DialogInterface dialog, int which) {
      Toast.makeText(CalculadoraActivity.this, "calculo cancelado", Toast.LENGTH_SHORT).show();
     }
    });
  
    // carregando a lista de operações utilizando um ArrayAdapter
    // Existe muito conteúdo na net explicando o conceito de adapter para utilização em listas.
    ArrayAdapter adapter = new ArrayAdapter(
     this, android.R.layout.simple_spinner_item, operacoes);
    Spinner spnOperacoes = (Spinner) viewForm.findViewById(R.id.operacao);
    spnOperacoes.setAdapter(adapter);
  
    // guarda a posicao da operacao pois deve ser enviado para o broadcast receiver
    // o código da operação a ser realizada e não a descrição
    spnOperacoes.setOnItemSelectedListener(this);

    // exibindo o alert com o formulario para o usuário.
    alert.show();
   }
 
  }
Para entender o código, leia os comentários colocados no próprio código. Não vou detalhar o código aqui para não ficar repetitivo. Agora que criamos tudo que é necessário, é preciso registrar a Activity e o Receiver dentro no arquivo AndroidManifest.xml
  <?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="br.com.livro.cap9"
      android:versionCode="1"
      android:versionName="1.0" >

      <uses-sdk android:minSdkVersion="8" />

      <application
          android:icon="@drawable/ic_launcher"
          android:label="@string/app_name" >
          <activity android:name="CalculadoraActivity" android:label="calculadora">
              <intent-filter>
                  <action android:name="android.intent.action.MAIN"/>
                  <category android:name="android.intent.category.LAUNCHER"/>
              </intent-filter>
          </activity>
          
          <receiver android:name="CalculoReceiver" android:label="calculo receiver">
              <intent-filter>
                  <action android:name="CALCULO" />
                  <category android:name="android.intent.category.DEFAULT" />
              </intent-filter>
          </receiver>
      </application>

  </manifest>
Ao executar a aplicação, teremos a seguinte saída no emulador:

E teremos a seguinte saída no LogCat:


Bom, é isso aí... o post acaba por aqui e espero que alguém também possa aprender algo com este exemplo simples.
Até a próxima.

Um comentário: