quarta-feira, 28 de setembro de 2011

AlertDialog Personalizado no Android

Olá povo,

Ultimamente estou escrevendo post curtos, com soluções para problemas que as equipes onde eu trabalho/trabalhei têm/tiveram, e que ao meu ver poderiam ter sido feitas de outra forma. A última foi como personalizar o AlertDialog do Android, dependendo da versão do Android (e até do fabricante) o aspecto visual das mensagens que são exibidas para o usuário tem um visual diferente. Por causa disso, o pessoal de UX (User eXperience) resolveu padronizar isso. Só que o pessoal estava usando uma Activity com o tema de diálogo.

Eu não acho essa uma boa abordagem, uma vez que você mexe com o ciclo de vida da activity (métodos onCreate, onStart, onPause, ...) para exibir um simples dialog. Esse tutorial do site do Android Developers mostra como criar um dialog com o conteúdo personalizado.

Sim, mas e se eu quiser mudar o background do título e dos botões? Nesse post vou mostrar como fazer isso. O código abaixo mostra como personalizar as 3 áreas de um AlertDialog: a barra de título, a área de conteúdo e parte inferior onde ficam os botões.
// O parâmetro temTitulo deve ser informado,
// pois não há como saber se o Dialog tem
// título para mudar a imagem de background
// do topo e do meio.
public void mostraDialog(
AlertDialog dialog, boolean temTitulo) {

dialog.show();
Resources res = dialog.getContext().getResources();

// Pega a View do AlertDialog toda
ViewGroup contentView = (ViewGroup)
dialog.findViewById(android.R.id.content);

// Pega a ViewGroup com os filhos
// (title, textView, customView e barra dos botões)
ViewGroup contentChilds =
(ViewGroup)contentView.getChildAt(0);

// Barra de título
if (temTitulo)
contentChilds.getChildAt(0).setBackgroundDrawable(
res.getDrawable(R.drawable.box_top));

// Layout onde fica o texto (mensagem) do dialog
contentChilds.getChildAt(1).setBackgroundDrawable(
res.getDrawable(
temTitulo?
R.drawable.box_middle :
R.drawable.box_top));

// Layout onde fica a View personalizada (quando usada)
contentChilds.getChildAt(2).setBackgroundDrawable(
res.getDrawable(
temTitulo?
R.drawable.box_middle :
R.drawable.box_top));

// Layout dos botões
// Se não colocar wrap_content, o botão fica cortado
contentChilds.getChildAt(3).setLayoutParams(
new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));

contentChilds.getChildAt(3).setBackgroundDrawable(
res.getDrawable(R.drawable.box_bottom));

// Alterando botões
Button btn = (Button)dialog.findViewById(
android.R.id.button1);
if (btn != null){
btn.setTextColor(Color.WHITE);
btn.setBackgroundResource(
R.drawable.selector_botao_verde);
}

btn = (Button)dialog.findViewById(
android.R.id.button2);
if (btn != null){
btn.setTextColor(Color.WHITE);
btn.setBackgroundResource(
R.drawable.selector_botao_vermelho);
}
btn = (Button)dialog.findViewById(
android.R.id.button3);
if (btn != null){
btn.setTextColor(Color.WHITE);
btn.setBackgroundResource(
R.drawable.selector_botao_vermelho);
}
}

Como todos devem/deveriam saber tudo que é exibido no Android contém objetos da classe View. A classe ViewGroup é uma implementação do padrão de projeto Composite, e como tal, herda de View e é composto de várias Views. Dessa forma, após exibir o Alert, pegamos a ViewGroup que representa o AlertDialog e obtemos cada um dos seus "filhos". O Alert é composto de 4 filhos: o primeiro contém a view com o título e o ícone; a segunda tem o texto do Alert; o terceiro pode conter uma view personalizada associada ao Alert; e o último contém os botões (no máximo 3: positivo, negativo e neutro). Nesse exemplo, pegamos cada filho e alteramos o seu background.

As imagens de background utilizadas estão no formato 9.png que falei nesse post aqui. Mas um detalhe interessante, é que o background do botão é definido utilizando os drawable selector_botao_verde e selector_botao_vermelho. Esses dois arquivos, apesar de ficarem na pasta res/drawable, não são imagens. Na verdade eles são arquivos XML que descrevem as imagens que serão utilizadas pelos botões dependendo do seu estado. Abaixo temos o XML que descreve o botão verde.
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/btn_verde"
android:state_pressed="false"/>
<item
android:drawable="@drawable/btn_verde_pressionado"
android:state_pressed="true" />
</selector>

Notem que devemos ter (por uma questão de usabilidade) duas imagens: uma imagem para o botão não pressionado, e outra para ele pressionado. Isso dará a sensação que o usuário estará pressionando no botão. Essa prática pode ser adotada para todos os botões da sua aplicação (inclusive para o botão vermelho usado nesse post).

Para utilizar o AlertDialog personalizado, basta criá-lo normalmente e passá-lo para o método que criamos anteriormente.
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle("Titulo")
.setMessage("Mensagem")
.setPositiveButton("Sim", null)
.setNegativeButton("Não", null);
AlertDialog d = builder.create();
mostraDialog(d, true);

Abaixo temos um exemplo do nosso Alert em funcionamento.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 26 de setembro de 2011

Máscara no EditText do Android

Olá povo,

Certo dia, um colega me perguntou se existia um EditText com máscara no Android. Apesar de ser uma coisa bem comum em aplicações Desktop, eu não conheço um meio fácil de obter o mesmo comportamento em Android. Na época, dei uma pesquisada e não achei o queria.
Durante as aulas de Android na Unibratec um aluno me perguntou novamente, e fui pesquisar. Como não achei nada tão fácil, o jeito foi implementar um listener (observer) para quando o texto do EditText é alterado. A interface que faz isso no Android é a TextWatcher.
O exemplo abaixo, mostra como fazer uma máscara para um campo de CEP. Passei como exercício para a turma, estender esse exemplo para CPF e CNPJ. Eles fizeram. E vocês?


final EditText edt =
(EditText)findViewById(R.id.editText1);

edt.addTextChangedListener(new TextWatcher() {

boolean isUpdating;

@Override
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {
}

@Override
public void onTextChanged(
CharSequence s, int start, int before, int after) {

// Quando o texto é alterado o onTextChange é chamado
// Essa flag evita a chamada infinita desse método
if (isUpdating){
isUpdating = false;
return;
}

// Ao apagar o texto, a máscara é removida,
// então o posicionamento do cursor precisa
// saber se o texto atual tinha ou não, máscara
boolean hasMask =
s.toString().indexOf('.') > -1 ||
s.toString().indexOf('-') > -1;

// Remove o '.' e '-' da String
String str = s.toString()
.replaceAll("[.]", "")
.replaceAll("[-]", "");

// as variáveis before e after dizem o tamanho
// anterior e atual da String, se after > before
// é pq está apagando
if (after > before) {
// Se tem mais de 5 caracteres (sem máscara)
// coloca o '.' e o '-'
if (str.length() > 5) {
str =
str.substring(0, 2) + '.' +
str.substring(2, 5) + '-' +
str.substring(5);

// Se tem mais de 2, coloca só o ponto
} else if (str.length() > 2) {
str =
str.substring(0, 2) + '.' +
str.substring(2);
}
// Seta a flag pra evitar chamada infinita
isUpdating = true;
// seta o novo texto
edt.setText(str);
// seta a posição do cursor
edt.setSelection(edt.getText().length());

} else {
isUpdating = true;
edt.setText(str);
// Se estiver apagando posiciona o cursor
// no local correto. Isso trata a deleção dos
// caracteres da máscara.
edt.setSelection(
Math.max(0, Math.min(
hasMask ? start - before : start,
str.length() ) ) );
}
}

@Override
public void afterTextChanged(Editable s) {
}
});


4br4ç05,
nglauber

quarta-feira, 21 de setembro de 2011

Android e o formato 9-Patch

Por: Manuella Nejaim @manux | Nelson Glauber @nglauber

Olá povo,

Nossa experiência em projetos Android nos mostrou que não são apenas os engenheiros de sistema (ou programadores) que precisam saber do que a plataforma é capaz. Os designers também precisam conhecer as facilidades que a plataforma da Google traz.
É bastante comum em uma interface, termos um botão que aparece em vários contextos com diferentes dimensões. Nestes casos, os designers criavam várias imagens semelhantes, diferindo apenas o tamanho. Isso fazia com que a aplicação ficasse com o tamanho maior (em bytes) e muitas vezes causando problemas de memória devido ao tamanho e quantidade de imagens utilizadas.

Uma técnica que foi amplamente utilizada em aplicações Java ME foi a 9-slice, devido à escassez de recursos dos aparelhos dessa plataforma. Essa abordagem divide uma imagem maior em 9 imagens menores, conforme abaixo:
Imagem cortada para o 9-slice

Essa forma é bem interessante, pois as imagens (exceto as das quatro quinas) são idealizadas de modo a serem esticadas sem perder a qualidade. Entretanto, tinha-se o inconveniente gerenciar, para cada imagem “de botão”, nove imagens.

O Android adota uma forma similar para aperfeiçoar a utilização do 9-slice chamada de 9-patch. Essa abordagem utiliza marcações feitas na imagem, indicando a área “esticável”, assim como as margens que devem ser preservadas.

Indicação das áreas de marcação de uma imagem 9-patch

Os benefícios da utilização deste recurso são claros: diminuição da quantidade de imagens, diminuição do peso da própria imagem, otimização para o porting, flexibilidade para a interface, dentre vários outros. Além disso, representa ganho de desempenho e qualidade no produto final, beneficiando engenheiros, designers e principalmente o usuário.

As imagens precisam ser produzidas de forma adequada para adotar esse recurso, então existe a necessidade de colaboração dos designers de interface com os engenheiros para fazer uso dessa solução. Gerar imagens para o 9patch requer um mínimo de esforço, e a inclusão de uns dois passos a mais no trabalho de layout.
Vale à pena salientar que esta solução se aplica elementos da interface como boxes, botões e inputs que de alguma forma apresentam um padrão gráfico que pode ser distorcido na horizontal e/ou vertical.
Para gerar a imagem “9patched”, é preciso primeiro criá-la na ferramenta gráfica (como o Photoshop) e observar dois pontos:

O primeiro ponto é que, a imagem gerada não pode ter degradês diagonais, apenas em 90 graus.

Degradê do botão com ângulo de 90 graus

O segundo ponto, é gerar a imagem do botão nas dimensões mínimas que conservem a sua aparência, conforme a imagem abaixo:

Aparência do botão normal

Imagem exportada do botão cortado para fazer o 9patch

Uma vez criada a imagem, utilizaremos o draw9patch, uma ferramenta que vem com o Android SDK, onde podemos realizar as marcações para tornar uma imagem comum em uma imagem “9-patched”. Ao baixar e instalar o SDK do Android, a ferramenta encontra-se no diretório tools. Dentro dessa pasta, basta executar o arquivo draw9patch.bat (no Windows, no Linux e Mac o nome é similar). Após executar a aplicação, basta arrastar a imagem original para dentro do programa (ou carrega-la a partir do menu File).
A imagem exportada é aberta no draw9patch e ao lado direito serão apresentadas 3 variações dela esticada na horizontal e na vertical.

Tela do draw9patch

A imagem ganha automaticamente uma margem externa de 1 pixel de cada lado. Nessa área é que fazemos a marcação na imagem “pintando-a” com o clique. Para remover a marcação de uma área, pressione a tecla Shift enquanto pinta.
A linha do topo refere-se à largura do botão, indicando os limites pra distorcer o botão horizontalmente. A margem lateral esquerda refere-se à altura do botão, onde ele esticará verticalmente. A margem inferior e direita serve para indicar o padding, ou seja, margem interna do botão, onde entra o conteúdo. E por fim, no lado direito temos o padding vertical da imagem.

Na parte inferior da tela do draw9patch encontramos as opções de zoom para a área de marcação e para o preview das distorções. Existem também botões Show lock para acionar o “travamento” da imagem em si (marcado por padrão), Show content para exibição da área de conteúdo definida pelo padding (desmarcada por padrão) e a opção Show patches que mostra os “cortes” que formam a imagem por completo (desmarcada por padrão).

Após realizar as alterações desejadas, o arquivo que será salvo terá a extensão *.9.png. e pode ser usado como imagem de background de componentes do Android como Button, EditText, etc.

4br4ç05,
manux | nglauber

segunda-feira, 19 de setembro de 2011

PageScroll

Olá povo,

Certo dia, assisti uma apresentação de Silvio Meira em que ele falou que nós, como desenvolvedores, tínhamos sempre que olhar para uma aplicação e pensar: "como esses caras [desenvolvedores] fizeram isso?". Às vezes, utilizamos a aplicação sem notarmos coisas simples que poderíamos incorporar aos nossos próprios aplicativos (cuidado com as patentes :p).

Pois bem. Eu achei interessante na aplicação do TweetDeck, a forma com que ele alternava entre a Timeline e as Mentions, apenas rolando da tela na horizontal. Similar ao que é feito na home screen do próprio Android para alternar entre os "desktops".

Achava que o Android já tivesse um componente para fazer isso, mas não achei. Tentei fazer com um Gallery, mas o resultado não ficou legal. Finalmente, achei um componente chamado HorizontalPager, disponível em http://code.google.com/p/deezapps-widgets/. Distribuído sobre licença Apache, ele realiza o comportamento que eu queria e pode ser incorporado ao seu projeto sem culpa :)

Disponibilizei um projeto que utiliza "esse carinha" neste link. E Abaixo temos um screenshot do mesmo.



EDITADO
Depois de publicar esse post, recebi a dica de @acvilarim que existe uma outra forma de obter esse mesmo comportamento com a API do próprio Android: utilizando as classes ViewPager e PagerAdapter. Entretanto essas classes estão em uma biblioteca separada que provê compatibilidade da versão 1.6 em diante com o Honeycomb (3.x). Para instalá-la, você pode seguir esse tutorial aqui.
Meu colega Marcelo Alves me indicou esse artigo para começar a mexer com o ViewPager e o PagerAdapter. De qualquer forma, postei aqui um exemplo que fiz idêntico ao anterior, mas usando essa API.

4br4ç05,
nglauber

quinta-feira, 8 de setembro de 2011

Twitter API no Android

Olá povo,

Nunca me interessei em testar APIs para redes sociais por dois motivos: não gosto muito dessa febre; e pensava que era bem trivial ao ponto de ter milhões de tutoriais por aí. Grande engano. Quando fui adicionar suporte ao Twitter em uma aplicação que estou trabalhando, notei que existem algumas etapas 'chatinhas' para realizar essa integração. Não sei se a forma que estou utilizando aqui é a melhor, inclusive, se vocês tiverem sugestões, deixem seus comentários :)

Sem mais "delongas", vamos começar! Nesse post, vou mostrar como realizar o processo de autenticação no Twitter e 'twitar' uma mensagem. Para começar, obviamente você deve ter uma conta comum no Twitter. Para ter acesso aos recursos da API, você (como desenvolvedor) deverá cadastrar sua aplicação no site https://dev.twitter.com. Esse cadastro servirá para a equipe do Twitter saber qual aplicação (e consequentemente o login) responsável pelo cliente que está acessando o Twitter. Isso é similar ao que é feito quando utilizamos a API de Mapas do Google, onde você deve usar sua conta do Google para obter uma chave, para então utilizar o serviço.



O cadastro é bem simples, basta informar: o nome da aplicação, descrição da mesma, URL para contato e uma URL de callback. Este último é mais importante para aplicações web, onde após efetuar o login, a chave de acesso será direcionada.
Uma vez cadastrada, serão geradas alguma informações que serão utilizadas para integração da nossa aplicação com o Twitter. Nós precisaremos da "Consumer Key" e "Consumer Secret".

Como funciona a autenticação no Twitter?
Até meados do segundo semestre de 2010, o acesso ao twitter funcionava basicamente passando o login e a senha em texto plano. Por motivos óbvios de segurança, essa forma foi descontinuada, e passou-se a utilizar a forma de autenticação OAuth. Esse processo é ilustrado na figura abaixo:

Não vou entrar em muitos detalhes sobre esse protocolo, mas basicamente o cliente envia um solicitação de acesso utilizando a consumerKey e consumerSecret para o servidor, o servidor então retorna uma URL para que o usuário se autentique. Nesse momento o usuário insere seu usuário e senha do Twitter. Uma vez passadas essas informações, o servidor valida esses dados e gera um token de acesso, que será usada para toda a comunicação com o Twitter. Esse tone deve ser salvo no cliente para que ele possa se comunicar com o servidor sem ter passar o login e a senha a cada transacção.

Mãos à obra!
De posse do conceito sobre a autenticação do Twitter, vamos implementar nosso exemplo. Ele constará de apenas uma tela, onde o usuário poderá fazer o login/logout no Twitter e atualizar o seu status a partir da mesma. A tela da aplicação é exibida abaixo:


Você pode implementar a comunicação com o Twitter do zero, utilizando JSON. Mas existem algumas APIs prontas que facilitam nosso trabalho. A que utilizarei aqui é a Twitter4J. Baixe a última versão estável, descompacte em algum local do seu HD. Crie um novo projeto Android, e copie o arquivo twitter4j-core-android-X.X.X.jar que está na pasta lib do Twitter4J para a pasta lib do seu projeto Android (essa pasta não é criada por padrão, então crie na raiz do seu projeto). Em seguida, adicione esse JAR no Build-Path do Eclipe clicando com o botão direito sobre o arquivo, e depois selecionando Build Path > Add to Build path. Seu projeto deve ficar como na figura abaixo.



Vamos ao código :) Vou começar pelo AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="ngvl.android.twitter"
android:versionCode="1"
android:versionName="1.0">

<uses-sdk android:minSdkVersion="10" />
 
<uses-permission android:name="android.permission.INTERNET"/>

<application
 android:icon="@drawable/icon"
 android:label="@string/app_name">
 <activity
   android:name=".ExemploTwitterActivity"
   android:label="@string/app_name"
   android:noHistory="true">
   <intent-filter>
     <action 
      android:name="android.intent.action.MAIN"/>
     <category 
       android:name="android.intent.category.LAUNCHER"/>
   </intent-filter>
   <intent-filter>
    <action 
      android:name="android.intent.action.VIEW"/>
     <category 
       android:name="android.intent.category.DEFAULT"/>
     <category 
       android:name="android.intent.category.BROWSABLE"/>
     <data android:scheme="nglauber-android"/>
   </intent-filter>
 </activity>
</application>
</manifest>

Aqui nesse arquivo temos algumas coisas muito importantes:
- A tag uses-permission para nossa aplicação poder acessar internet;
- Foi adicionada a propriedade andróide:noHistory à tag activity, para que quando o Browser seja aberto para o usuário fazer o login, ao voltar, ele não crie outra instância dessa activity.
- Essa activity tem dois intent-filter. O primeiro é pra execução normal da aplicação e o segundo é usado pela página do Twitter. Após a autenticação, ele redirecionará para o endereço nglauber-android://?oauthverifier=sua_chave. Estamos informando que essa atividade trata esse "protocolo", dessa forma nossa activity será re-executada.

Agora vamos ver o código da Activity.
package ngvl.android.twitter;

import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class ExemploTwitterActivity extends Activity {

  private final String 
    consumerKey = "SUA CONSUMER KEY";
  private final String 
    consumerSecret = "SUA CONSUMER SECRET";
  private final String 
    CALLBACKURL = "nglauber-android:///";

  private Twitter twitter;

No código acima, as constantes cosumerKey e consumerSecret devem ser preenchidas com as informações do cadastro da sua aplicação no site do Twitter. Já a constante CALLBACKURL deve estar igual a que foi declarada no AndroidManifest.xml.
O atributo twitter proverá acesso ao processo de login e de atualização do status.

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  
  twitter = new TwitterFactory().getInstance();
  twitter.setOAuthConsumer(
    consumerKey, consumerSecret);
}

No onCreate apenas inicializamos o layout da tela, obtemos a instância do Twitter através da classe TwitterFactory e setamos a consumer key e secret.
 public void clickLogin(View v) {
  try {
    AccessToken accessToken = loadAccessToken();
    if (accessToken == null) {
      twitter = new TwitterFactory().getInstance();
      twitter.setOAuthConsumer(
        consumerKey, consumerSecret);
    
      RequestToken requestToken = 
        twitter.getOAuthRequestToken(CALLBACKURL);
 
      String url = requestToken.getAuthenticationURL();
      Intent it = new Intent(
        Intent.ACTION_VIEW, Uri.parse(url));
      it.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
      startActivity(it);
    
      saveRequestData(requestToken.getToken(), 
        requestToken.getTokenSecret());
   
    } else {
      twitter.setOAuthAccessToken(accessToken);
    }
  } catch (Exception e) {
    e.printStackTrace();
    showToast(e.getMessage());
  }
} 

O método acima, iniciará o processo de login e será chamado ao clicar no botão de login. Através do método loadAccessToken (definido mais adiante), ele tenta obter um objeto AccessToken salvo anteriormente. Caso não exista, o processo de login inicia com a obtenção da url para fazermos o login no Twitter. Notem que é passada a constante CALLBACKURL como parâmetro, isso indica qual endereço será chamado quando o login for concluído. Essa é uma URL "fake" que será chamada quando o login for efetuado. Neste momento, o browser redireciona a chave para nglauber-android://?oauthverifier=sua_chave, e o "protocolo" nglauber-android está sendo tratado por nossa atividade, como definido no AndroidManifest.xml.
A flag FLAG_ACTIVITY_NO_HISTORY evita que, ao pressionar back após o login, o browser seja exibido novamente. Após chamar o browser para o login, o token e o token secret da requisição são salvos pelo método saveRequestData (veremos esse método mais adiante).

Ao clicar no botão de login, será exibida a tela de autenticação do Twitter. Aqui é solicitado autorização para que a aplicação que cadastramos no começo desse post tenha acesso a sua conta do Twitter. Digite seu login e senha e clique em "Sign In".


Após realizar o login, a página do Twitter envia a chave para nossa Activity. Tratamos o recebimento dessa chave no método onResume conforme abaixo:
 @Override
protected void onResume() {
  super.onResume();
  
  Uri uri = getIntent().getData();
  if (uri != null) {
    String oauthVerifier = 
      uri.getQueryParameter("oauth_verifier");
 
    try {
      RequestToken requestToken = loadRequestToken();
      AccessToken at = twitter.getOAuthAccessToken(
        requestToken, oauthVerifier);

      saveAccessToken(
        at.getToken(), at.getTokenSecret());

    } catch (TwitterException e) {
      e.printStackTrace();
      showToast(e.getMessage());
    }
  }
}

Quando a atividade é chamada pela página do twitter, uma chave de verificação da requisição de acesso é retornada. Com a requisição e a chave de verificação podemos criar um objeto AccessToken para acessar os recursos do Twitter. O token e o token secret do objeto AccessToken são salvos no método saveAccessToken (definido mais adiante) e, como vimos anteriormente podem ser recuperados quando clicamos no botão de login.

Abaixo temos o método que é chamado ao clicar no botão Tweet.

public void clickTweet(View v) {
  try {
    if (loadAccessToken() != null){
      EditText edt = 
        (EditText) findViewById(R.id.editText1);
      String tweet = edt.getText().toString();

      twitter.updateStatus(tweet);
      showToast("Status atualizado com sucesso!");

    } else {
      showToast("Faça o login antes de Twittar");
    }
  } catch (TwitterException e) {
      e.printStackTrace();
      showToast(e.getMessage());
  }
}

Nada de mais aqui. Apenas pega o conteúdo da caixa de texto e manda alterar o status do objeto Twitter.

Abaixo temos método auxiliares que utilizamos no nosso código. O primeiro exibe um Toast na tela.
private void showToast(String s){
  Toast.makeText(this, s, Toast.LENGTH_LONG).show();
}

Os métodos que salvam e recuperam as chaves de solicitação (RequestToken) e de acesso (AccessToken) estão definidos abaixo. Para ambos são utilizados SharedPreferences.
 
private RequestToken loadRequestToken(){
  SharedPreferences prefs = PreferenceManager.
    getDefaultSharedPreferences(this);
  String reqToken = 
    prefs.getString("request_token", null);
  String reqTokenSecret = 
    prefs.getString("request_tokensecret", null);
  
  return new RequestToken(reqToken, reqTokenSecret);
}
 
private void saveRequestData(
  String requestToken, String requestTokenSecret){

  SharedPreferences prefs = PreferenceManager.
    getDefaultSharedPreferences(this);
  SharedPreferences.Editor editor = prefs.edit();

  editor.putString(
    "request_token", requestToken);
  editor.putString(
    "request_tokensecret", requestTokenSecret);

  editor.commit();  
}
 
private AccessToken loadAccessToken() {
  SharedPreferences prefs = PreferenceManager.
    getDefaultSharedPreferences(this);
  String acToken = 
    prefs.getString("access_token", null);
  String acTokenSecret = 
    prefs.getString("access_tokensecret", null);

  if (acToken != null || acTokenSecret != null){
    return new AccessToken(acToken, acTokenSecret);
  }
  return null;
}
 
private void saveAccessToken(
  String accessToken, String accessTokenSecret) {

  SharedPreferences prefs =
    PreferenceManager.getDefaultSharedPreferences(this);

  SharedPreferences.Editor editor = prefs.edit();

  editor.putString(
    "access_token", accessToken);
  editor.putString(
    "access_tokensecret", accessTokenSecret);

  editor.commit();
}

Por fim, o método que faz o logout, limpa os valores da sharedpreference de AccessToken, o que vai forçar a realização de um novo login.
public void clickLogout(View v) {
  saveAccessToken(null, null);
}


É isso pessoal, qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

sexta-feira, 2 de setembro de 2011

Android: Dicas 4

Olá povo,

Segue mais um POST da série "dicas de Android".

Dica 1 - Obter o espaço disponível no aparelho

File path = Environment.getDataDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
long ocupedBlocks =
stat.getBlockCount() - availableBlocks;

String s1 = Formatter.formatFileSize(
this, availableBlocks * blockSize);
String s2 = Formatter.formatFileSize(
this, ocupedBlocks * blockSize);
System.out.println("---------------");
System.out.println("s = "+ s1);
System.out.println("s = "+ s2);

Dica 2 - Teclado Virtual Sobre o Layout
Ao exibir o teclado virtual, o Android automaticamente redimensiona a tela do aparelho para exibir todos os elementos. Então se você tiver, por exemplo uma barra com botões na parte inferior, eles irão aparecer imediatamente em cima do teclado virtual. Para evitar isso, basta colocar a seguinte configuração na declaração da sua Activity no AndroidManifest.xml.

<activity
android:name=".ui.MainActivity"
android:windowSoftInputMode="adjustPan"
/>

Dica 3 - Evitando abrir a mesma atividade 2 vezes
Os engenheiros de teste com frequência tentam "destruir" a aplicação de algum jeito :) E um bem comum é clicar rapidamente em um botão. Se esse botão chamar uma Activity serão abertas duas em sequência. Para evitar isso, basta colocar a seguinte configuração na declaração da sua Activity no AndroidManifest.xml.


<activity
android:name=".ui.MainActivity"
android:launchMode="singleTop"
/>

Isso faz com que só seja possível apenas uma instância no topo da pilha de atividades.

Dica 4 - Alterando a linha abaixo da TabWidget
Essa dica é um complemento da dica sobre como mudar a imagem das abas da TabWidget. O código abaixo mostra como mudar a linha que fica na parte inferior da TabWidget. Ela tem um aspecto interessante, pois a API só disponibilizou um método "digno" pra isso a partir do Android 2.2, para as versões anteriores, essa operação tem que ser feita via Reflection.


// Esse código deve ser chamado dentro de
// uma TabActivity
TabWidget tabWidget = getTabWidget();

if (android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.FROYO){
// For Android 2.2
tabWidget.setLeftStripDrawable(
R.drawable.barra_menu);
tabWidget.setRightStripDrawable(
R.drawable.barra_menu);
} else {
// For Android 2.1 or before
try {
Field stripLeft = tabWidget.getClass().
getDeclaredField("mBottomLeftStrip");
Field stripRight = tabWidget.getClass().
getDeclaredField("mBottomRightStrip");

if (!stripLeft.isAccessible())
stripLeft.setAccessible(true);
if (!stripRight.isAccessible())
stripRight.setAccessible(true);

stripLeft.set(tabWidget,
getResources().getDrawable(
R.drawable.barra_menu));

stripRight.set(tabWidget,
getResources().getDrawable(
R.drawable.barra_menu));

} catch (Exception e) {
e.printStackTrace();
}
}

Nesse exemplo, estou usando a API level 8 (Android 2.2) no Eclipse para poder ter acesso aos métodos setLeftStripDrawable e setRightStripDrawable. Porém ele pode ser executado em aparelhos 2.1, que neste caso executará o código via Reflection. Para dar suporte a API 2.1, mesmo desenvolvendo com a 2.2 basta colocar a tag abaixo no AndroidManifest.xml.

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

Dica 5 - Salvar estado do scroll da ListView
Na aplicação que estou desenvolvendo, sempre estava reatribuindo o Adapter à ListView no onResume da Activity. O problema era que eu clicava em um item da lista lá do final pra edita-lo era aberta a tela pra edição. Quando fechava essa tela, aí abria a tela de detalhes, mas quando voltava pra tela de listagem queria manter a posição do scroll.


// savedIndex e savedY é um atributo int

// Salvar estado
// Esse código coloquei no onPause
savedIndex = getListView().getFirstVisiblePosition();
View v1 = getListView().getChildAt(0);
savedY = (v1 == null) ? 0 : v1.getTop();

// Restaurando estado
// Esse código eu coloquei no onResume
setListAdapter(novoAdapter);
getListView().setSelectionFromTop(savedIndex, savedY);

Dica 6 - Checar se o GPS está habilitado
Existe basicamente duas formas de obter a posição geográfica no Android: por GPS e pela Rede de Dados. Essa configuração fica no menu Configurações > Local > Meu Local. Se sua aplicação usa geolocalização, você deve checar se uma dessas opções está habilitada.

public boolean isEnabled() {
LocationManager lm = (LocationManager)
context.getSystemService(
Context.LOCATION_SERVICE);

return
locationManager.isProviderEnabled(
LocationManager.GPS_PROVIDER) ||
locationManager.isProviderEnabled(
LocationManager.NETWORK_PROVIDER);
}
Dica 7 - Problema no GoogleMaps
Essa dica foi mandada pelo meu aluno da LinuxFI, Sávio (Sávio saviojp@gmail.com).
Durante a aula de Mapas eu passei pelo seguinte problema: Mesmo com todas as configurações da API Key e permissões definidas de modo correto, o meu emulador não carregava o mapa. Para solucionar esse problema fiz o seguinte:

Customizei a execução da aplicação no eclipse por meio do Run Configurations/Android Application /na aba Target no campo Additional Emulator Command Line Options, setei o comando: -dns-server 8.8.8.8.

4br4ç05,
nglauber