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

segunda-feira, 29 de agosto de 2011

Armazenando configurações no Android

Olá povo,

É muito comum armazenar configurações simples das nossas aplicações para que o usuário não tenha que setá-las toda vez que abrir o programa. No Android, podemos fazer isso através da interface SharedPreferences, com ela podemos salvar grupos de configurações para recuperá-las posteriormente.

// Obtém a instância da SharedPreference

SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(this);

// Salva dois valores nas preferências
SharedPreferences.Editor editor = prefs.edit();
editor.putString("operadora", "Claro");
editor.putBoolean("som", false);
editor.commit();

// Recupera os valores
String op = prefs.getString("operadora", null);
boolean som = prefs.getBoolean("som", false);


Ao salvar esses valores eles serão armazenados na pasta /data/data/pacote.da.sua.app/shared_prefs. O valores "Claro" e false serão armazenados para as configurações "operadora" e "som" respectivamente, e estarão lá mesmo que o usuário feche a aplicação. Eles só são apagados através do menu Configurações> Aplicações > Gerencias Aplicações > [Sua app] > Limpar dados.

Mas se eu quiser criar uma tela de configurações? O Android já tem uma API para facilitar (e padronizar) a criação de telas para esse propósito. Podemos defini-la como abaixo:

<PreferenceScreen

xmlns:android="http://schemas.android.com/apk/res/android"
<CheckBoxPreference
android:summaryOff="Som desabilitado"
android:key="som"
android:summaryOn="Som habilitado"
android:title="Som" />
<EditTextPreference
android:key="operadora"
android:dialogTitle="Operadora"
android:dialogMessage="Digite o nome da operadora"
android:title="Operadora" />
</PreferenceScreen>

Eu nomeei esse arquivo como tela_config.xml e salvei em res/xml. Notem que os atibutos android:key são iguais aos que foram especificados no código Java anterior. Para carregar esse arquivo, devemos ter uma classe que herda de PreferenceActivity como a seguir.

public class PreferenciasActivity

extends PreferenceActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.tela_config);
}
}

Você pode chamar essa tela como uma Activity comum, e quando os valores dessa tela forem alterados, automaticamente os valores já são salvos na SharedPreference, e consequentemente podem ser obtidos com o código mostrado mais acima. Dessa forma, não precisamos utilizar o SharedPreference.Editor para salvar as configurações ;)





Bem pessoal, esse foi um post bem rápido. Qualquer dúvida deixem seus comentários.

4br4ç05,
nglauber

sexta-feira, 26 de agosto de 2011

Fazendo uma tela de splash

Olá povo,

É muito comum vermos nas aplicações a famosa "tela de splash", ou tela de loading. É nela que a aplicação dá um feedback pro usuário informando que os recursos necessários para execução da aplicação estão sendo carregados. Após verificar uma dessas telas elaborada por uma equipe do projeto em que trabalho, resolvi dar minha sugestão de como implementá-la.

[EDITADO 08/02/2016]
O código a seguir é basicamente o que está sendo descrito nesse post do Ian Lake.
https://plus.google.com/+AndroidDevelopers/posts/Z1Wwainpjhd

Basicamente você precisa definir um tema para sua Activity.
<style name="AppTheme.Launcher">
  <item name="android:windowBackground">@drawable/launch_screen</item>
</style>
Em seguida defina esse tema na sua activity MAIN/LAUNCHER (no AndroidManifest.xml). Perceba que "launch_screen" não precisa ser necessariamente uma imagem PNG, pode ser um XML como a seguir.
<layer-list 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:opacity="opaque">
  <!-- Uma cor de background, preferencialmente a mesma do tema -->
  <item android:drawable="@android:color/white"/>
  <!-- Logo do app. Imagem de 144dp -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>
Depois é só mudar o tema em tempo de execução para o tema que você preferir.
public class MyMainActivity extends AppCompatActivity {
 @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Essa linha tem que ser chamada antes do super.onCreate
    setTheme(R.style.Theme_MyApp);
    super.onCreate(savedInstanceState);
    // ...
  }
}

[/EDITADO]
[EDITADO 20/08/2016]
Ótima apresentação do Cyril Mottier sobre splash screens.
http://cyrilmottier.com/2016/06/20/launch-screens-from-a-tap-to-your-app/
[/EDITADO]

Essa implementação não é recomendada (ao contrário da mostrada acima), mas já precisei fazer por exigência de cliente.
Vamos começar pela classe que realiza esse trabalho.
public class SplashActivity extends Activity
 implements Runnable {
 private Handler handler;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.splash_layout);

   handler = new Handler();
   handler.postDelayed(this, 2000);
 }

 @Override
 protected void onPause() {
   super.onPause();

   handler.removeCallbacks(this);
 }

 @Override
 protected void onRestart() {
   super.onRestart();

   run(); 
 }

 @Override
 public void run() {
   // Faça o carregamento necessário aqui...
   
   // Depois abre a atividade principal e fecha esta
   Intent it = new Intent(
     this, PrincipalActivity.class);
   startActivity(it);

   finish();

   overridePendingTransition(
     android.R.anim.fade_in, android.R.anim.fade_out);
 }
}

A Activity está implementando Runnable para carregaremos os recursos da aplicação em uma Thread separada, uma vez que não queremos dar a impressão de que ela está travada. Em seguida, temos um atributo do tipo Handler, ele é utilizado para atualizar os componentes da tela (informando o progresso por exemplo) através de outra Thread, uma vez que é uma restrição do Android permitir que apenas a Thread da UI (ou seja, a Thread principal) faça isso.
Ao criar a atividade (onCreate), inicializamos o Handler e agendamos a execução da Thread de carregamento dos recursos para ser executada daqui a 2 segundos (2000 milissegundos). Como não estou carregando nada nesse exemplo, estou usando esse valor pra dar tempo de ver a splash. Na prática, esse valor deve ir para um valor mais baixo (0,5 segundo por exemplo). No método onPause, eu estou tratando o caso de o usuário clicar na tecla "Back" durante o carregamento da splash. Caso isso aconteça, o carregamento é cancelado.
No método run, que será executado em background, devemos fazer o carregamento dos recursos da aplicação. Em seguida, criamos a Intent para a tela principal da aplicação e a chamamos com o startActivity. Por fim, finalizamos a tela de splash com o método finish.
Um recurso interessante que coloquei nesse exemplo foi a mudança da transição padrão entre as telas. Isso é feito através do método overridePendingTransition, que recebe uma animação para a tela que será exibida e outra para a tela q está sendo fechada. Nesse exemplo, usei um efeito de transparência (Fade In e Fade Out) da classe R do próprio Android. Se quiser fazer a sua própria, eu coloquei um post sobre isso aqui no blog.
De código é só. O que não podemos esquecer, é de declarar a Activity no AndroidManifest.xml, e as telas de Splash e Principal têm uma configuração interessante.
<activity
 android:name=".SplashActivity">
 <intent-filter>
   <action 
      android:name="android.intent.action.MAIN" />
   <category 
      android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
</activity>

<activity
 android:name=".PrincipalActivity">
 <intent-filter>
   <action 
      android:name="android.intent.action.MAIN" />
   <category 
      android:name="android.intent.category.DEFAULT" />
 </intent-filter>
</activity>
Notem que as duas atividades têm a ação android.intent.action.MAIN que informa ao Android que elas são pontos de partida da aplicação. Entretanto, a tela de Splash tem a categoria android.intent.category.LAUNCHER, enquanto que a principal tem a categoria android.intent.category.DEFAULT. O que isso significa?
Ao clicar no ícone no menu principal de aplicações do Android (Launcher) a SplashActivity é executada (isso graças a categoria LAUNCHER), e como vimos, em seguida ela chama a PrincipalActivity e se finaliza (com o finish).
Quando clicamos na tecla "Back" na tela Principal, a aplicação "morreu", e se a executarmos novamente a splash será aberta novamente. Porém, se saírmos da aplicação com a tecla "Home", a PrincipalActivity deve ficar lá suspensa, para que se o usuário clique no ícone da aplicação novamente, a tela principal (que foi aberta anteriormente) seja exibida. Isso funciona graças a categoria DEFAULT que foi passada para a PrincipalActivity.
Por hoje é só pessoal. Qualque dúvida, deixem seus comentários.
[EDITADO 15/03/2016]
Recentemente tive que fazer a tela de splash da maneira não recomendada, mas ela precisava ocultar também a barra de status e os botões de navegação. Sendo assim, precisei colocar o código a seguir no onCreate.
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);

setContentView(R.layout.splash_layout);
[/EDITADO]
4br4ç05,
nglauber
P.S.: O exemplo acima pode ser feito com AsyncTask também. Fica o desafio pra quem quiser ;)

domingo, 21 de agosto de 2011

Galeria de mídia do Android

Olá povo,

Neste post vou mostrar como tirar uma foto com a aplicação de câmera, salvar a imagem em um diretório temporário e em seguida adiciona-la à galeria de mídia do aparelho. E por último, vou mostrar como carregar uma imagem da galeria.
Vamos iniciar mostrando como chamar a aplicação de câmera.
public void tirarFotoClick(View v) {
  String nomeFoto = DateFormat.format(
    "yyyy-MM-dd_hhmmss", new Date()).toString();

  caminhoFoto = new File(
    Environment.getExternalStoragePublicDirectory(
      Environment.DIRECTORY_PICTURES),
    nomeFoto);

  Intent it = new Intent(
    MediaStore.ACTION_IMAGE_CAPTURE);  
  it.putExtra(MediaStore.EXTRA_OUTPUT, 
    Uri.fromFile(caminhoFoto));
  startActivityForResult(it, 0);
}
A aplicação de câmera é chamada através da ação ACTION_IMAGE_CAPTURE passado no construtor da Intent. Um detalhe interessante aqui é o parâmetro EXTRA_OUTPUT, ele serve para indicar um caminho para a foto ser salva. Sem ele, a imagem será salva com tamanho e qualidade inferior a que a câmera realmente tirou.

caminhoFoto é um atributo do tipo File.
A chamada do método startActivityForResult indica que queremos tratar o resultado da activity que estamos chamando. Esse tratamento é feito no método onActivityResult.
protected void onActivityResult(
  int requestCode, int resultCode, Intent data) {
  super.onActivityResult(
    requestCode, resultCode, data);

  if (resultCode == RESULT_OK && requestCode == 0) {
    ImageView img = (ImageView)
      findViewById(R.id.imageView1);
   
    // Obtém o tamanho da ImageView
    int targetW = img.getWidth();
    int targetH = img.getHeight();
    
    // Obtém a largura e altura da foto
    BitmapFactory.Options bmOptions =
      new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(
      caminhoFoto.getAbsolutePath(), bmOptions);

    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;
    
    // Determina o fator de redimensionamento
    int scaleFactor = Math.min(
      photoW/targetW, photoH/targetH);
    
    // Decodifica o arquivo de imagem em 
    // um Bitmap que preencherá a ImageView
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;
    
    Bitmap bitmap = BitmapFactory.decodeFile(
      caminhoFoto.getAbsolutePath(), bmOptions);
    img.setImageBitmap(bitmap);
  }
}
Quando chamamos a câmera, no startActivityForResult, passamos o valor 1 como segundo parâmetro, esse valor representa o código da requisição (requestCode). Esse valor é retornado no método onActivityResult para saber se o resultado que estamos tratando é daquela requisição. No if que fizemos, além do requestCode, checamos o resultCode. O resultCode descreve o resultado da operação, que neste caso é tirar a foto. Se o resultado for RESULT_OK, é porque uma foto foi tirada.
[EDITADO em 12/05/2013] 
A foto que é tirada com as câmeras atuais requer muita memória para ser alocada. Sendo assim, verificamos o tamanho da ImageView para carregar a imagem deste tamanho.
O atributo caminhoFoto que foi atribuído no método tirarFotoClick é utilizado para carregar a imagem e atribuir em um ImageView. Agora vamos ver como salvar essa imagem na galeria de mídia.
public void salvarFotoClick(View v) {
  if (caminhoFoto != null && caminhoFoto.exists()) {
    MediaStore.Images.Media.insertImage(
      getContentResolver(), 
      caminhoFoto.getAbsolutePath(), 
      caminhoFoto.getName(), "");

    Toast.makeText(this, 
      "Imagem adicionada a galeria.",
      Toast.LENGTH_SHORT).show();
  }
}
A classe MediaStore.Images.Media tem um "método mágico" que permite que possamos inserir um Bitmap na galeria. Essa operação é feita graças a um ContentProvider disponibilizado pela aplicação de galeria. O próximo passo será como abrir a galeria de mídia para selecionarmos uma imagem.
public void galleryButtonClick(View v) {

  Intent intent = new Intent(
    Intent.ACTION_GET_CONTENT);
  intent.setType("image/*");
  startActivityForResult(intent, 2);
}
O código acima abrirá a aplicação de galeria. Notem que agora passamos o valor 2 para o startActivityForResult. Da mesma forma que fizemos para tirar foto, usaremos o método onActivityResult para tratar a imagem selecionada.
// adicione esse código no onActivityResult
if (resultCode == RESULT_OK && requestCode == 2) {
  Uri selectedImage = data.getData();
  String[] filePathColumn = { 
    MediaStore.Images.Media.DATA };

  Cursor cursor = getContentResolver().query(
    selectedImage, filePathColumn, null, null, null);
  cursor.moveToFirst();

  int columnIndex = cursor.getColumnIndex(
    filePathColumn[0]);
  String filePath = cursor.getString(columnIndex); 
  cursor.close();

  Bitmap yourSelectedImage = 
    BitmapFactory.decodeFile(filePath);

  imageView1.setImageBitmap(yourSelectedImage);
}
A galeria retorna o endereço da imagem selecionada através de um objeto Uri que é obtido em data.getData(). Com essa informação podemos acessar o ContentProvider da galeria para obter o caminho da imagem. Com esse caminho passamos para a classe BitmapFactory que retornará um objeto Bitmap para preencher a imageView.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 15 de agosto de 2011

Google compra Motorola Mobility


Olá povo,

Você chega na segunda de manhã e se depara com uma notícia que balança o mundo mobile: a compra da Motorola Mobility pela Google.
Não tô afim de escrever muito, mas vou colocar minha opinião pessoal. Achei essa aquisição bastante oportuna e ao meu ver até esperada. A Apple tem seu próprio SO (iOS) e fabrica seu hardware; a Nokia fecha parceria com a Microsoft para o Windows Phone; nada mais justo do que a Google se juntar com alguém, e com alguém que apostou muito no Android como a Motorola.

Aí pode vir a pergunta: e porque não a HTC ou a Samsung? Na minha opinião, pode ser porque esses dois fabricantes em especial "dão tiros pra todo lado". Digo isso porque podemos encontrar celulares Samsung com Bada, e da HTC com Windows, além do próprio sistema da Google. Então eu creio que eles continuarão assim, diversificando seus produtos.

Outro ponto importante é que o Google/Android/Fabricantes estavam sendo processados por diversos concorrentes por questões de patentes, e em muitos casos, perdendo essas disputas na justiça. Com a compra da Motorola, o Google amplia seu leque de patentes e protege a si e aos parceiros (fabricantes) que adotam o Android, uma vez que a Google reiterou sua posição de deixa-lo Open Source.

Sendo assim, acho que isso fortalecerá o Android e aquecerá ainda mais o mercado mobile. E nós, como desenvolvedores, devemos estar preparados para onde quer que o vento leve este barco.
E vocês, o que acham? Deixem seus comentários.

4br4ç05,
nglauber

*imagem Gizmodo Brasil

sábado, 6 de agosto de 2011

Artigo "Sensores no Android"

Olá povo,

A revista Java Magazine edição 94, trás uma matéria sobre utilização de sensores na plataforma Android. Ela foi escrita por mim e pelos meus colegas Edilson Mendes(Ronaldo!) e Artur Botelho (o doido :)

Este artigo demonstra como obter informações dos sensores dos aparelhos Android através da Sensor API. Com ela podemos capturar informações como aceleração e orientação do aparelho. Estes e outros sensores podem servir tanto como mecanismo de iteração com o usuário, quanto de canal de comunicação para obter informações sobre o dispositivo e o ambiente onde ele se encontra. O sensor de aceleração, também conhecido como acelerômetro, por exemplo, é amplamente utilizado em jogos disponíveis no Android Market.

Espero que vocês gostem.

4br4ç05,
nglauber