segunda-feira, 28 de maio de 2012

iOS: UIActionSheet + UIDatePicker

Olá povo,

Hoje pela manhã estava fazendo uma telinha no iOS que tinha 2 campos de data. E como o iOS tem um componente padrão para datas, o UIDatePicker, resolvi usá-lo. Entretanto ele ocupa muito espaço e fica inviável colocar dois na tela. Foi aí que me surgiu a ideia de utilizar uma UIActionSheet personalizada. Onde eu poderia selecionar a data, e em seguida a ActionSheet desapareceria da tela.

Para exemplificar isso, crie um novo projeto no Xcode, se tiver dúvidas, dê uma olhada aqui. Na tela da sua aplicação, arraste dois Labels e dois Buttons e deixe-os conforme abaixo. Nos botões, mudei a propriedade Type para Detail Disclosure e a propriedade Tag para 1 e 2 respectivamente.

Crie os Outlets para os dois Labels e utilize o método changeDateClick no Touch Up Inside dos dois botões. Nós saberemos qual botão foi clicado através da propriedade Tag.
#import <UIKit/UIKit.h>

@interface NGDetailViewController : UIViewController 
  <UIActionSheetDelegate>

- (IBAction)changeDateClick:(id)sender;

@property (weak, nonatomic) 
  IBOutlet UILabel *txtData1;
@property (weak, nonatomic) 
  IBOutlet UILabel *txtData2;

@end
Note que nossa classe implementa o protocolo UIActionSheetDelegate, necessário para sabermos qual botão o usuário clicou na ActionSheet. Deixe a implementação da classe conforme abaixo:
#import "NGDetailViewController.h"

@implementation NGDetailViewController

@synthesize txtData1;
@synthesize txtData2;

- (id)initWithNibName:(NSString *)nibNameOrNil 
  bundle:(NSBundle *)nibBundleOrNil {

  self = [super initWithNibName:
    nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
      // Custom initialization
  }
  return self;
}

- (void)viewDidLoad {
  [super viewDidLoad];
}

- (void)viewDidUnload {
    [self setTxtData1:nil];
    [self setTxtData2:nil];
    [super viewDidUnload];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
  (UIInterfaceOrientation)interfaceOrientation {
  return (interfaceOrientation != 
    UIInterfaceOrientationPortraitUpsideDown);
}

- (IBAction)changeDateClick:(id)sender {
  UIButton *button = sender;
    
  UIDeviceOrientation orientation = 
    [[UIDevice currentDevice] orientation];

  int topMargin = 
    (orientation == UIDeviceOrientationPortrait) ? 
      40 : 60;
    
  UIActionSheet *dateActionSheet = 
    [[UIActionSheet alloc] initWithTitle:
      @"Selecione a data" 
      delegate:self 
      cancelButtonTitle:@"Cancelar" 
      destructiveButtonTitle:nil 
      otherButtonTitles:@"OK", nil];
  [dateActionSheet setTag:button.tag];
  [dateActionSheet setActionSheetStyle:
    UIActionSheetStyleBlackTranslucent];
  [dateActionSheet showInView:self.view];    
  [dateActionSheet setFrame:CGRectMake(
    0, topMargin, self.view.frame.size.width, 400)];
}

- (void)willPresentActionSheet:
  (UIActionSheet *)actionSheet {

  // Inicializa o picker e adiciona ao actionSheet
  UIDatePicker *pickerView = 
    [[UIDatePicker alloc] init];
    
  [pickerView setDatePickerMode:UIDatePickerModeDate];
  [actionSheet insertSubview:pickerView atIndex:1];
  // A actionSheet tem 3 subviews agora,
  // o picker e os dois botões  
  UIView *titleView   = 
    [actionSheet.subviews objectAtIndex:0];
  UIButton *btnOk     =  
    [actionSheet.subviews objectAtIndex:2];
  UIButton *btnCancel = 
    [actionSheet.subviews objectAtIndex:3];
    
  UIDeviceOrientation orientation = 
    [[UIDevice currentDevice] orientation];
    
  if (orientation == UIDeviceOrientationPortrait) {
    // Mudou o Y do picker para ficar abaixo do título
    pickerView.frame  = CGRectMake(
      pickerView.frame.origin.x, 
      titleView.frame.size.height + 20, 
      pickerView.frame.size.width, 
      pickerView.frame.size.height);

    // Aqui também... só o Y, ficando abaixo do picker
    btnOk.frame = CGRectMake(
      btnOk.frame.origin.x, 
      pickerView.frame.origin.y + 
        pickerView.frame.size.height + 10, 
      btnOk.frame.size.width, 
      btnOk.frame.size.height);
    
    // De novo só o Y, ficando abaixo do btnOk
    btnCancel.frame = CGRectMake(
      btnCancel.frame.origin.x, 
      btnOk.frame.origin.y + 
        btnOk.frame.size.height + 10, 
      btnCancel.frame.size.width, 
      btnCancel.frame.size.height);

  } else {
    // Aqui dei um espaço de 10 em X,
    // em Y ficou abaixo do título 20px,
    // na largura, o picker ocupa 2/3 da tela,
    // e altura não mudou
    pickerView.frame  = CGRectMake(
      10, 
      titleView.frame.size.height + 20, 
      ((pickerView.frame.size.width) / 3 ) * 2, 
      pickerView.frame.size.height);

   // No eixo X, o botão fica a direita do picker,
   // em Y, fica alinhado com o topo do picker,
   // a largura do botão é área entre o fim do 
   // picker e a margem direita da tela,
   // a altura não mudou
   btnOk.frame = CGRectMake(
     pickerView.frame.origin.x + 
       pickerView.frame.size.width + 10, 
     pickerView.frame.origin.y, 
     (actionSheet.frame.size.width - 
       pickerView.frame.size.width -  
       pickerView.frame.origin.x - 20), 
     btnOk.frame.size.height);
        
   // Aqui é similar ao anterior, a diferença é 
   // que em Y, o botão está abaixo do btnOk.
   btnCancel.frame = CGRectMake(
     pickerView.frame.origin.x + 
       pickerView.frame.size.width + 10, 
     btnOk.frame.origin.y + 
       btnOk.frame.size.height + 10,
     (actionSheet.frame.size.width - 
       pickerView.frame.size.width - 
       pickerView.frame.origin.x - 20), 
     btnCancel.frame.size.height);
  }
}

- (void)actionSheet:(UIActionSheet *)actionSheet 
  clickedButtonAtIndex:(NSInteger)buttonIndex {
  if (buttonIndex == 0){
    UIDatePicker *picker = 
      [actionSheet.subviews objectAtIndex:1];
        
    NSString *dataStr = [NSDateFormatter 
      localizedStringFromDate:picker.date 
      dateStyle:NSDateFormatterShortStyle 
      timeStyle:NSDateFormatterNoStyle];
        
    if (actionSheet.tag == 1){
      txtData1.text = [NSString 
        stringWithFormat:@"Data: %@", dataStr];
    } else {
      txtData2.text = [NSString 
        stringWithFormat:@"Data: %@", dataStr];
    }
  }
}

@end

O método shouldAutorotateToInterfaceOrientation define que nossa aplicação apenas não suporta a orientação de Portrait invertido (ou seja, o aparelho de cabeça pra baixo).
A implementação do método changeDateClick inicia pegando a referência do botão que foi clicado. Em seguida pegamos a orientação do aparelho para definir a distância da ActionSheet para margem superior do aparelho. Depois criamos a ActionSheet passando as informações necessárias: título, botões, e o mais importante, o delegate (ou listener) que será chamado quando clicar em algum dos botões. Como nossa classe está implementando esse protocolo, passamos a instância da prórpria classe (self).
Na linha seguinte, setamos a tag da actionSheet para podermos identificar qual dos botões foi clicado para alterar a caixa de texto correspondente. Por fim, alteramos a área (frame) da actionSheet.

O método willPresentActionSheet irá personalizar a actionSheet, adicionando o UIDateTimePicker e reposicionando os botões de acordo com a orientação do aparelho. O código deste método está comentado. Então não vou entrar em detalhes.

O último método, assim como o anterior, é definido no protocolo UIActionSheetDelegate, ele vai ver se clicamos no primeiro botão (OK) e em caso positivo, pega a referência do UIDatePicker e obtém a data selecionada no componente. Em seguida, converte a data em uma NSString usando a classe NSDateFormatter. Feito isso, verifica a tag da actionSheet e altera o Label correspondente.

Se você executar a aplicação e clicar em um dos botões, a actionSheet é exibida abaixo.
Aproveite e teste a aplicação também em landscape.
É isso pessoal. Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

quarta-feira, 16 de maio de 2012

Facebook API no Android

Olá povo,

Até a presente data, eu não tenho conta (de verdade) no Facebook. Por isso que até agora não tinha colocado nada relacionado aqui no blog. Mas depois de tantos alunos me perguntando como integrar a aplicação com o Facebook, resolvi deixar registrado como utilizar essa rede social.

Registrando a aplicação do Facebook

O processo é bem similar ao que mostrei no post sobre integração com o Twitter. Devemos primeiro criar/registrar nossa aplicação no Facebook. Para tal, acesse: https://developers.facebook.com/apps, faça o login com uma conta do facebook (aconselho criar uma conta de teste) e clique na opção Create New App. A janela abaixo será exibida:
Digite o nome da aplicação e clique em Continue.

Nota: Por questões de segurança o Facebook pede que você coloque uma informação que te identifique. No meu caso, eu tive que preencher o número do meu celular. Uma vez que cadastrei, o Facebook envia um código de ativação via SMS. Clique no link que foi enviado para o seu aparelho ou digite o código de confirmação para concluir o seu cadastro.

Uma vez que a aplicação está cadastrada, habilite a opção Native Android App.

Nós precisaremos preencher o campo Android Key Hash que é o identificador da nossa aplicação. Ele é gerado a partir do arquivo *.keystore que é usado pra assinar nossa aplicações Android. Aqui, utilizaremos o debug.keystore.
Para gerar esse hash, precisaremos do OpenSSL. Se você estiver no Windows, baixe o openssl-for-windows (http://code.google.com/p/openssl-for-windows/) e descompacte o ZIP na unidade C:

Ainda se você estiver no Windows, abra o prompt de comando, e digite o comando abaixo para adicionar o diretório do openssl no path.
set PATH=%PATH%;C:\openssl-0.9.8k_WIN32\bin

Para gerar a chave digite esse comando:
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

Nota 1: o comando keytool é do JDK. Dessa forma, a pasta bin do JDK deve estar no seu PATH, se não tiver, adicione ou digite esse comando dentro da pasta bin do JDK.
Nota 2: se você estiver usando Windows, substitua ~/.android/debug.keystore por C:\Users\seuusuario\.android\debug.keystore (se o nome do seu usuário tiver espaço, coloque o caminho entre aspas)

Copie a hash que foi gerada e cole no campo Android Key Hash e conclua o cadastro da aplicação no Facebook.

Importanto o projeto do Facebook SDK

O código do Facebook SDK está disponível em um repositório GIT, mas você pode baixá-lo no formato ZIP. Acesse https://github.com/facebook/facebook-android-sdk/ e selecione a opção Download this repository as a ZIP file". Terminado o download, descompacte onde desejar.

Vamos criar agora um novo projeto Android com o código que acabamos de baixar. No Eclipse, selecione File | New | Android Project, coloque o nome do projeto de facebooksdk e selecione a opção Create project from existing code. Selecione o diretório onde você descompactou o código do facebooksdk e clique em Finish.

Nota: Se o projeto der erros em relação a anotações @Override, você pode removê-las.

Criando um projeto de exemplo

Vamos criar uma aplicação simples que realizará o login no facebook e atualizará o status do usuário. Crie um novo projeto Android e ao concluir o assistente, vamos adicionar a referência do projeto facebooksdk ao nosso projeto. Para tal, clique com o botão direito sobre o projeto e selecione Properties. No lado esquerdo, selecione Android, e na parte inferior adicione a referência ao projeto clicando em Add... e selecionando o facebooksdk.

Abra o AndroidManifest.xml e adicione a permissão de INTERNET.
<uses-permission 
  android:name="android.permission.INTERNET"/>

Agora, deixe a Activity da sua aplicação da seguinte forma:
public class ExFacebookActivity extends Activity {
 
  private static final String APP_ID = "ID_SUA_APP";
 
  private static final String
    ACCESS_EXPIRES = "access_expires";
  private static final String
    ACCESS_TOKEN = "access_token";

  private Facebook facebook;
  private SharedPreferences prefs;

  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    facebook = new Facebook(APP_ID);
    prefs = getPreferences(MODE_PRIVATE);
    // Carrega a accessToken pra saber se o usuário
    // já se autenticou.
    loadAccessToken();

    // Caso não tenha se autenticado, abre o login
    if(!facebook.isSessionValid()) {

      // Chama a tela de login do facebook
      facebook.authorize(this, 
        new String[] {"publish_stream"},
          new DialogListener() {
            // Login com sucesso, salva o accessToken
            public void onComplete(Bundle values) {
              saveAccessToken();
            }
            // Os métodos abaixo devem ser 
            // implementados para tratatmento dos
            // fluxos alternativos
            public void onFacebookError(
              FacebookError error) {}
    
            public void onError(DialogError e) {}
    
            public void onCancel() {}
          });
    }
  }
    
  public void onActivityResult(
    int requestCode, int resultCode, Intent data) {
    super.onActivityResult(
      requestCode, resultCode, data);
    // A API do Facebook exige essa chamada para 
    // concluir o processo de login.
    facebook.authorizeCallback(
      requestCode, resultCode, data);
  }
  
  // Evento de clique do botão para atualizar o status
  public void updateStatusClick(View v){
    EditText edt = (EditText)
      findViewById(R.id.editText1);
    updateStatus(edt.getText().toString()); 
  }

  private RequestListener requestListener = 
    new RequestListener() {
      public void onMalformedURLException(
        MalformedURLException e, Object state) {
        showToast("URL mal formada");
      }
      public void onIOException(
        IOException e, Object state) {
        showToast("Problema de comunicação");
      }
      public void onFileNotFoundException(
        FileNotFoundException e, Object state) {
        showToast("Recurso não existe");
      }
      public void onFacebookError(
        FacebookError e, Object state) {
        showToast("Erro no Facebook: "+ 
          e.getLocalizedMessage());
      }
      public void onComplete(
        String response, Object state) {
        showToast("Status atualizado");
      }
    };

  // Método que efetivamente atualiza o status
  private void updateStatus(String status) {
    AsyncFacebookRunner runner = 
      new AsyncFacebookRunner(facebook);
  
    Bundle params = new Bundle(); 
    params.putString("message", status);
    runner.request("me/feed", params, "POST", 
      requestListener, null);
  }
 
  private void showToast(final String s){
    final Context ctx = ExemploFacebookActivity.this;
    runOnUiThread(new Runnable() {
      public void run() {
        Toast.makeText(ctx, s, 
          Toast.LENGTH_SHORT).show();
      }
    });
  }

  private void saveAccessToken() {
    SharedPreferences.Editor editor = prefs.edit();
    editor.putString(
      ACCESS_TOKEN, facebook.getAccessToken());
    editor.putLong(
      ACCESS_EXPIRES, facebook.getAccessExpires());
    editor.commit();
  }

  private void loadAccessToken() {
    String access_token = 
      prefs.getString(ACCESS_TOKEN, null);
    long expires = prefs.getLong(ACCESS_EXPIRES, 0);
    if(access_token != null) {
      facebook.setAccessToken(access_token);
    }
    if(expires != 0) {
      facebook.setAccessExpires(expires);
    }
  }
}
A classe acima está comentada, então vou apenas ressaltar os pontos principais. No onCreate da Activity verificamos se o usuário está autenticado. Essa verificação é feita através do AccessToken que é salvo em uma SharedPreferences (veja mais aqui). Caso o usuário não esteja autenticado, chamamos o método autorize passando as permissões que a nossa aplicação requer para acessar determinados recursos. A lista completa de permissões está aqui. Como essa comunicação é feita em uma Thread separada (para não travar a GUI) é utilizado um RequestListener para saber o status da solicitação (sucesso ou falha).
O método updateStatus vai enviar uma mensagem para seu "wall" do Facebook. Nesse método, criamos uma AsyncFacebookRunner que realizará o processo em uma Thread separada. Criamos um objeto Bundle para passar os parâmetros da solicitação, que no nosso caso é apenas a mensagem.
O método request recebe como parâmetro:
- o recurso do facebook que queremos acessar, nesse caso "me/feed" (a lista completa pode ser vista aqui);
- o bundle com os parâmetros; - o método HTTP (GET ou POST); - um RequestListener similar ao que declaramos no login.

Por fim, vou colocar o layout da aplicação:
<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical" >

  <EditText
    android:id="@+id/editText1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

  <Button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Atualizar Status" 
    android:onClick="updateStatusClick"/<

</LinearLayout>
Agora é só executar a aplicação. Ao iniciar pela primeira vez, será exibida a tela de login. Após fazer a autenticação, a tela de login é fechada e voltará automaticamente para aplicação. Aí é só digitar sua mensagem e postar no Facebook :)
Se quiser postar uma foto, basta substituir o método updateStatus por esse aqui:
private void sendPhoto(String status) {
  AsyncFacebookRunner runner = 
    new AsyncFacebookRunner(facebook);
  
  Bitmap image = BitmapFactory.decodeResource(
    getResources(), R.drawable.ic_launcher);

  ByteArrayOutputStream baos = 
    new ByteArrayOutputStream();   

  image.compress(
    Bitmap.CompressFormat.PNG, 100, baos); 

  byte[] bytes = baos.toByteArray();  

  Bundle params = new Bundle(); 
  params.putByteArray("picture", bytes);  
  params.putString("message", status);
     
  runner.request("me/photos", params, 
    "POST", requestListener, null);
} 
Como vocês devem conhecer o Facebook melhor que eu, vocês já devem imaginar que existem muitas outras possibilidades nessa API. Eu só quis mostrar o suficiente para começar. Quem quiser se aprofundar, dá uma olhada aqui.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

sexta-feira, 11 de maio de 2012

iOS: Storyboards

Olá povo,

Mais vídeo-post aqui no blog. Desta vez, falando sobre Storyboard, um novo recurso do iOS 5 SDK. Com ele podemos criar todo o fluxo navegacional da nossa aplicação com apenas alguns cliques.

Espero que gostem,
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

terça-feira, 8 de maio de 2012

Android: SMS Content Provider

Olá povo,

Para utilizar o ContentProvider da aplicação de mensagens, você deve adicionar as permissões no AndroidManifest.xml, para ler e escrever/excluir SMS:

<uses-permission 
android:name="android.permission.READ_SMS"/>
<uses-permission
android:name="android.permission.WRITE_SMS"/>
Depois é só acessar o ContentProvider:
/* Essa URI traz as informações das mensagens, 
mas sem o corpo da mensagem.*/
Uri uriSms = Uri.parse("content://sms/inbox");

/* Já essa URI traz as informações 
de uma mensagem específica.*/ Uri uriSms = Uri.parse("content://sms/1");
Depois é só abrir um cursor com a URI desejada
Cursor c = getContentResolver().query(
  uriSms, null,null,null,null);
O código abaixo, lista todos os dados de todas as colunas do cursor, aí é só observar chave/valor.
int columns = c.getColumnCount();
while (c.moveToNext()){
  for (int i = 0; i < columns; i++) {
    System.out.println(
      c.getColumnName(i) +"="+ 
      c.getString(i));
  }
  System.out.println("--");
}
Para apagar uma mensagem, é só usar o código abaixo:
getContentResolver().delete(
  Uri.parse("content://sms/1"), null, null);

getContentResolver().delete(
  Uri.parse("content://sms/inbox"), 
  "address=88998899", null);
4br4ç05,
nglauber

segunda-feira, 7 de maio de 2012

Navegue no Android

Olá povo,

Já precisei algumas vezes dar uma vasculhada no código-fonte do Android para saber como ele faz certas coisas. Foi meu colega de trabalho André Rêgo que me deu essa ótima dica que já me ajudou bastante.
Você pode baixar o código na sua máquina seguindo esse tutorial aqui. Entrentanto, há uma cópia do código aqui onde você pode navegar pelo código.

Fonte: http://stackoverflow.com/questions/449763/where-can-i-browse-android-source-code-on-line

4br4ç05,
nglauber

quinta-feira, 3 de maio de 2012

Escola Regional de Informática chega à Pernambuco

Olá povo,

A ERI, evento promovido pela Sociedade Brasileira da Computação, através da Secretaria Regional de Pernambuco, acontece nos dias 3 e 4 de Maio, no auditório do CTG/UFPE.

O Centro de Informática (CIn) da UFPE em parceria com a Secretaria da Regional de Pernambuco da Sociedade Brasileira de Computação (SBC) vai promover a I Escola Regional de Informática de Pernambuco (I ERI-PE). O evento traz à tona debates sobre os temas atuais na área de TIC, com palestras, minicursos e apresentação de trabalhos acadêmicos. As atividades serão apresentadas nos dias 3 e 4 de Maio, das 8h às 17h30, no auditório do Centro de Tecnologia e Geociências (CTG), no Campus da UFPE e serão abertas ao público.

Ao todo, 10 trabalhos acadêmicos de diversas instituições de ensino superior serão apresentados no ERI-PE. A lista está disponível para download aqui. Entre as universidades que vão participar, estão: a UFPE, UFRPE e UPE de Recife, UPE de Caruaru e UFRPE de Garanhuns. A programação pode ser vista aqui. Os interessados em participar do evento devem se inscrever aqui.

A Escola Regional de Informática da SBC já atua em outras regiões do país. A ideia é fazer edições anuais do evento, a fim de fomentar a pesquisa e a dinâmica entre indústria e academia. Nessa primeira edição em Pernambuco, já estão confirmadas as palestras do Professor José Gilson de Almeida Teixeira Filho, pesquisador da UPE/UFPE: “Melhores Práticas e Maturidade em Planejamento Estratégico de SI/TI”; do consultor Rodrigo Elia Assad, especialista em novas tecnologias e segurança: “Computação em Nuvem: Por que surgiu?” e a palestra do Professor Ricardo Alexandre Afonso, da Universidade Federal de Alagoas (UFAL), “Mercado de trabalho: Onde você estava quando o futuro passou?”. Além das palestras, já está confirmado o minicurso de “Desenvolvimento de aplicativos para smartphones e tablets Google Android”, ministrado pelo Mestre em Engenharia de Software pelo C.E.S.A.R., Nelson Glauber de Vasconcelos Leal.

Fonte: http://www2.cin.ufpe.br/site/lerNoticia.php?s=1&c=94&id=521

Os slides da minha apresentação estão disponíveis aqui.

4br4ç05,
nglauber