sábado, 30 de junho de 2012

iOS: Dicas

Olá povo,

Como tô mexendo muito com iOS ultimamente vou registrar aqui algumas pequenas dicas no desenvolvimento de aplicações para iOS.

Dica 1 - Adicionando botões na navigation bar dinâmicamente
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
  initWithTitle:@"Salvar" 
  style:UIBarButtonItemStyleDone 
  target:self action:@selector(salvarDados)];
[self.navigationItem setRightBarButtonItem:saveButton];

Dica 2 - Ocultando o teclado virtual

O teclado virtual do iOS é exibido automaticamente quando você clica em uma caixa de texto (UITextFiedl), se tornando o "primeiro a responder" (firstResponder) a eventos de toque. Entretanto para fechá-la você vai codificar um pouquinho. Digamos que em uma tela você tenha 3 caixas de texto. Quando o usuário estiver na primeira caixa de texto ele terá a opção de pular para o próximo campo clicando no botão Next no teclado virtual. Para habilitar esse botão altere a propriedade Return Button do TextField para Next. Faça o mesmo no segundo (e nos outros que você precisar). No último coloque Done na mesma propriedade.
Agora ligue o evento Did End On Exit de todas as caixas de texto para o método abaixo.

- (IBAction)hideKeyboard:(id)sender {
  if ([sender isEqual:edtName] == YES){
    [edtAddress becomeFirstResponder];
  } else if ([sender isEqual:edtAddress] == YES){
    [edtPhone becomeFirstResponder];
  } else {
    [edtName resignFirstResponder];
    [edtAddress resignFirstResponder];
    [edtPhone resignFirstResponder];
  }
}
Isso resolve o problema da navegação. Mas uma coisa comum é, ao clicarmos fora da caixa de texto, o teclado desaparecer. Para fazer isso, clique na View da tela e mude seu tipo de classe de UIView para UIControl. Depois associe o evento Touch Up Inside para o método acima.
O parâmetro sender identifica qual componente disparou o evento. Se não foi nenhum dos dois primeiros TextFields ocultamos o teclado chamando o método resignFirstResponder (o TextField agora é o "primeiro respondedor"). Pronto! Agora ao clicar em qualquer área da tela, o teclado desaparecerá.

Dica 3 - Editando células da UITableView

Quando precisamos, em algum tipo de listagem, excluir um registro, podemos recorrer a um recurso bacana do iOS: deslizar sobre célula. Quando isso acontece o método tableView:editingStyleForRowAtIndexPath é chamado. Aqui podemos retornar um estilo de edição para a célula: UITableViewCellEditingStyleDelete ou UITableViewCellEditingStyleInsert.
-(UITableViewCellEditingStyle)tableView:
    (UITableView *)tableView 
  editingStyleForRowAtIndexPath:
    (NSIndexPath *)indexPath{

    return UITableViewCellEditingStyleDelete;
}
Quando deslizar o dedo para a direita sobre a célula, aparecerá ao botão Delete na célula. Para tratar o evento desse botão devemos implementa o método abaixo:
-(void)tableView:(UITableView *)tableView 
 didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
}
Uma outra opção é colocar utilizar o código abaixo ao clicar em um UIBarButton.
-(void)habilitarExcluir:(id)sender {
  [self.tableView setEditing:
    !self.tableView.editing animated:YES];
}
E realizar o código da exclusão no método abaixo:
- (void)tableView:(UITableView *)tableView 
  commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 
  forRowAtIndexPath:(NSIndexPath *)indexPath {

  if (editingStyle == 
    UITableViewCellEditingStyleDelete) {
  }   
}

Dica 4 - Abrindo uma URL no Browser

O código abaixo também aplica-se a telefone (tel:88990099) e mapas...
NSURL *url = [NSURL URLWithString:
  @"http://nglauber.blogspot.com"];
[[UIApplication sharedApplication] openURL:url];

Dica 5 - iPhone ou iPad?

Em uma aplicação Universal (que roda em iPhone e iPad) para checar se o device é um iPhone (ou iPod) ou iPad utilizamos o código abaixo:
if ([[UIDevice currentDevice] userInterfaceIdiom] == 
  UIUserInterfaceIdiomPhone) {
  //iPhone ou iPod
} else {
  // iPad
}

Dica 6 - Salvando Objetos com NSUserDefaults

Para salvar um objeto todo no NSUserDefaults você deve implementar os métodos que vão serializar e deserializar os dados...
// NSPessoa.h --------------------------------
#import 

@interface NSPessoa : NSObject

@property (nonatomic, strong) NSString *nome;
@property (nonatomic, strong) NSString *email;

@end

// NSPessoa.m --------------------------------
#import "NSPessoa.h"

@implementation NSPessoa

@synthesize nome, email;

- (void)encodeWithCoder:(NSCoder *)encoder {
  [encoder encodeObject:self.nome  forKey:@"nome"];
  [encoder encodeObject:self.email forKey:@"email"];
}

- (id)initWithCoder:(NSCoder *)decoder {
  if((self = [super init])) {
    self.nome  = [decoder decodeObjectForKey:@"nome"];
    self.email = [decoder decodeObjectForKey:@"email"];
  }
  return self;
}
@end

Abaixo o código para carregar e salvar os dados.
// Carregando ----------------------------------
NSUserDefaults *prefs = 
  [NSUserDefaults standardUserDefaults];

NSData *data = [prefs objectForKey:@"pessoa"];
NSPessoa *pessoa = (NSPessoa *)
  [NSKeyedUnarchiver unarchiveObjectWithData: data];

// se os dados da pessoa não existirem, 
// inicia um novo objeto    
if (!pessoa){
  pessoa = [NSPessoa new];
}

// Salvando ------------------------------------
NSData *data = [NSKeyedArchiver 
  archivedDataWithRootObject:pessoa];

NSUserDefaults *defaults = 
  [NSUserDefaults standardUserDefaults];

[defaults setObject:data forKey:@"pessoa"];

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 25 de junho de 2012

SQLite no iOS

Olá povo,

Nesse post vou mostrar como utilizar o SQLite em aplicações iOS através da criação de um cadastro simples. Vou utilizar também o recurso de Storyboards (veja esse post) para agilizar a criação das telas.
Comece criando um projeto no Xcode do tipo "Single View Application" que vou nomeá-lo de ExemploSQLiteBlog. Lembre-se de deixar habilitadas as opções Use storyboards e Use automatic reference counting. Após criar o projeto, devemos adicionar a biblioteca do SQLite ao nosso projeto. Para tal, clique sobre o projeto, depois selecione a aba Build phases. Na seção Link Binary With Libraries clique no botão "+" e selecione libsqlite3.dylib.
Uma vez que o projeto está configurado, vamos fazer toda a parte de "negócio" da aplicação, onde criaremos a classe de dados da aplicação, assim como a classe que fará a persistência dos dados da aplicação.
Crie uma nova classe chamada NGContact conforme abaixo (a utilização de prefixos no nome da classe em Objective-C é comum para evitar duplicidade):
// NGContact.h ----------------------
#import <Foundation/Foundation.h>

@interface NGContact : NSObject

@property (assign) NSInteger _id;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *address;
@property (strong, nonatomic) NSString *phone;

- (id)initWithName:(NSString *)name 
  andAddress:(NSString *)address 
  andPhone:(NSString *)phone;

@end

// NGContact.m ----------------------
#import "NGContact.h"

@implementation NGContact

@synthesize _id = __id, name = _name, 
  address = _address, phone = _phone;

- (id)initWithName:(NSString *)name 
  andAddress:(NSString *)address 
  andPhone:(NSString *)phone {

  self = [super init];
  if (self){
    self.name = name;
    self.phone = phone;
    self.address = address;
  }
  return self;
}

@end
Aqui temos uma classe simples com 4 propriedades (id, nome, endereço e telefone) e um método para inicializar esses atributos. Para quem é "do Java" (como eu) esse método servirá como um construtor (lembrando que Objective-C não tem construtores).
Vamos agora criar a classe que fará a persistência desses objetos e utilizará a API do SQLite. Ela servirá de repositório da aplicação e a chamaremos de NGContactDB, e o arquivo de cabeçalho (.h) deve ficar conforme abaixo.
// NGContactDB.h
#import <Foundation/Foundation.h>
#import <sqlite3.h>

@class NGContact;

@interface NGContactDB : NSObject {
  sqlite3 *contactDB;
  NSString *databasePath;
}

- (void)createDatabase;
- (void)insertContact:(NGContact *)contact;
- (void)updateContact:(NGContact *)contact;
- (void)deleteContact:(NGContact *)contact;
- (NSArray *)getAllContacts;

@end
Como podemos observar, essa classe contém dois atributos, o primeiro é uma referência para sqlite3, e o segundo é o caminho do arquivo do banco. Além disso, temos também cinco métodos:
- createDatabase, que como o próprio nome diz, criará o banco caso ele não exista;
- insertContact, updateContact e deleteContact irão respectivamente inserir, atualizar e excluir um contato no banco;
- e getAllContacts irá obter todos os contatos cadastrados no banco.
Deixe o arquivo de implementação conforme abaixo:
#import "NGContactDB.h"
#import "NGContact.h"
#import <sqlite3.h>

@implementation NGContactDB

// Inicializa o banco -------------------------------
- (void)initDB {   
  // Obtém os diretório de arquivos da aplicação
  NSArray *dirPaths = 
    NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask, YES);
  // Obtém o diretório para salvar o arquivo do banco
  NSString *docsDir = [dirPaths objectAtIndex:0];
  // Cria o caminho do arquivo do banco
  databasePath = [[NSString alloc] initWithString: 
    [docsDir stringByAppendingPathComponent: 
      @"contacts.db"]];
  
  const char *dbpath = [databasePath UTF8String];
  // Inicializa o atributo contactDB com o banco  
  if (sqlite3_open(dbpath, &contactDB) != SQLITE_OK){
    NSLog(@"Failed to open/create database - 1");
  }
}

// Cria a tabela no banco ---------------------------
- (void)createDatabase {   
  // Chama o método acima...
  [self initDB];
    
  char *errMsg;
  const char *sql_stmt = "CREATE TABLE IF NOT EXISTS 
    CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, 
    NAME TEXT, ADDRESS TEXT, PHONE TEXT)";

  // Cria a tabela no banco se ela não existir          
  if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, 
    &errMsg) == SQLITE_OK) {
    NSLog(@"Database successfully created");
  } else {
    NSLog(@"Failed to create table");
  }
            
  sqlite3_close(contactDB);
}

// Inserir um contato no banco ---------------------
- (void)insertContact:(NGContact *)contact {
  [self initDB];
      
  char *sql = "INSERT INTO CONTACTS (NAME,ADDRESS, 
    PHONE) VALUES (?, ?, ?);";

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(contactDB, sql, -1, 
    &stmt, nil) == SQLITE_OK) {

    sqlite3_bind_text(stmt, 1, 
      [contact.name UTF8String], -1, NULL);
    sqlite3_bind_text(stmt, 2, 
      [contact.address UTF8String], -1, NULL); 
    sqlite3_bind_text(stmt, 3, 
      [contact.phone UTF8String], -1, NULL); 
  }
  if (sqlite3_step(stmt) == SQLITE_DONE){
    NSLog(@"Record added");
  } else {
    NSLog(@"Failed to add contact");
  }
  sqlite3_finalize(stmt);
  sqlite3_close(contactDB);
}

// Atualizar contato no banco ----------------------
- (void)updateContact:(NGContact *)contact {
  [self initDB];
      
  char *sql = "UPDATE CONTACTS SET NAME = ?, 
    ADDRESS = ?, PHONE = ? WHERE ID = ?;";

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(contactDB, sql, -1, 
    &stmt, nil) == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, 
      [contact.name UTF8String], -1, NULL);
    sqlite3_bind_text(stmt, 2,
      [contact.address UTF8String], -1, NULL); 
    sqlite3_bind_text(stmt, 3, 
      [contact.phone UTF8String], -1, NULL); 
    sqlite3_bind_int(stmt, 4, contact._id);
  }

  if (sqlite3_step(stmt) == SQLITE_DONE){
    NSLog(@"Record updated");
  } else {
    NSLog(@"Failed to update contact");
  }
  sqlite3_finalize(stmt);
  sqlite3_close(contactDB);
}

// Excluir contato ---------------------------------
- (void)deleteContact:(NGContact *)contact {
  [self initDB];
      
  char *sql = "DELETE FROM CONTACTS WHERE ID = ?;";
  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(contactDB, sql, -1, 
    &stmt, nil) == SQLITE_OK) {

    sqlite3_bind_int(stmt, 1, contact._id); 
  }
  if (sqlite3_step(stmt) == SQLITE_DONE){
    NSLog(@"Record removed");
  } else {
    NSLog(@"Failed to removed contact");
  }
  sqlite3_finalize(stmt);
  sqlite3_close(contactDB);
}

// Obter lista de objetos do banco -----------------
- (NSArray *)getAllContacts;
{
  [self initDB];
    
  NSMutableArray *contacts = 
    [[NSMutableArray alloc]init];
    
  sqlite3_stmt *statement;
    
  NSString *querySQL = [NSString stringWithFormat: 
    @"SELECT * FROM contacts"];
        
  const char *query_stmt = [querySQL UTF8String];
        
  if (sqlite3_prepare_v2(contactDB, query_stmt, -1, 
    &statement, NULL) == SQLITE_OK){

    while (sqlite3_step(statement) == SQLITE_ROW)
    {
      NSInteger contactId = 
        sqlite3_column_int(statement, 0);
               
      NSString *nameField = [[NSString alloc] 
        initWithUTF8String:(const char *) 
          sqlite3_column_text(statement, 1)];
                
      NSString *addressField = [[NSString alloc] 
        initWithUTF8String:(const char *) 
          sqlite3_column_text(statement, 2)];
                
      NSString *phoneField = [[NSString alloc] 
        initWithUTF8String:(const char *) 
          sqlite3_column_text(statement, 3)];
                
      NGContact *contact = [[NGContact alloc]init];
      contact._id = contactId;
      contact.name = nameField;
      contact.address = addressField;
      contact.phone = phoneField;
                
      [contacts addObject:contact];
    }
    sqlite3_finalize(statement);
  }
  sqlite3_close(contactDB);
    
  return [NSArray arrayWithArray:contacts];
}

@end
O código está comentado, mas vou fazer algumas observações. O método initDB começa tentando obter o caminho do diretório de documentos, na qual a aplicação pode salvar seus arquivos. Em seguida monta o caminho do arquivo de banco de dados com a variável databasePath. Por fim, utilizamos a função sqlite3_open (isso mesmo, função, já que o sqlite é escrito em C) para abrir o arquivo de banco de dados e inicializar o atributo contactDB.
Já o método createDatabase criará a tabela Contatcts no banco de dados caso ela não exista. Esse método deve ser chamado apenas uma vez durante o ciclo de vida da aplicação. Pois, uma vez criada, a tabela permanecerá no banco. Esse método começa chamado o initDB para inicializar o atributo contactDB depois monta o SQL para criação da tabela. Note que esse SQL é montado com um char*, que é o tipo esperado pela biblioteca (e não NSString).  Para executar o comando no banco, utilizamos a função sqlite3_exec, passando a referência para o banco e a sentença SQL (para os demais parâmetros olhe aqui).
Os métodos insertContact, updateContact e deleteContact são bem similares. Chamamos o método initDB para inicializar o atributo contactDB. Depois declaramos um sqlite3_stmt que é inicializado através da função sqlite3_prepare_v2 que recebe a referência do banco, a instrução SQL e a referência para o statement. Cada uma das "?" será um parâmetro que será preenchida com um valor através da função sqlite3_bind_* (onde * é o tipo do parâmetro que se quer passar). Para executar a instrução SQL, utilizamos a função sqlite3_step, se tudo correr bem, será retornado SQLITE_DONE. Por fim, fechamos o statement e o banco com as funções sqlite3_finalizesqlite3_close respectivamente.
O método que retorna uma lista de objetos NGContact utiliza os mesmos conceitos dos métodos anteriores, a diferença é que para percorrer os registros retornados, a função sqlite3_step retorna SQLITE_ROW quando temos uma linha para ler. E para ler essa coluna, utilizamos as funções sqlite3_column_* (onde * é o tipo da coluna).

Bem pessoal. Da parte de SQLite é isso, em um próximo post vou mostrar como integrar essa classe com uma UITableViewController (para mostrar os dados) e criar uma tela para inserir e alterar os registros. Mas quem quiser ir se adiantando, pode usar esse post aqui.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

quarta-feira, 13 de junho de 2012

Criando arquivos Excel no Android


Este post foi escrito e sugerido por Leonardo Malkes (e pequenos ajustes meus :)

Um cliente que estava precisando desenvolver uma app para Android e me perguntou se era possível criar arquivos do Excel a partir da aplicação. De imediato eu respondi que não sabia, mas complementei a frase dizendo: “Hoje em dia em programação quase tudo é possível”. Entretanto, alguns dias depois esse mesmo cliente disse que não seria mais necessário gerar o arquivo. Mas a minha curiosidade de como fazer isso já havia sido atiçada.

Após algumas pesquisas na internet, encontrei uma biblioteca Java, open source e bem documentada (com JavaDoc incluso), chamada JExcelAPI. Baixe a última versão, vamos aos trabalhos!

Crie um novo projeto Android e na raiz do projeto adicione uma pasta chamada lib, e dentro dela  coloque o arquivo jxl.jar que está no zip da biblioteca.
Para trabalhar com biblioteca de terceiros, precisamos adicioná-la ao Build Path do projeto. Para fazer isso, clique com o botão direito sobre o arquivo da biblioteca e selecione Build Path > Add to Build Path. Em seguida, clique com o botão direito sobre o projeto e selecione Build Path > Configure Build Path. Na janela que for exibida, na aba Order and Export, marque a biblioteca jxl.jar.

Agora estamos prontos para iniciar o projeto. Lembrando que se queremos criar um arquivo no Excel, o mais natural é gravá-lo no cartão de memória do aparelho (apesar de ser possível gravar na memória interna do aparelho). Para fazer isso precisamos adicionar a permissão WRITE_EXTERNAL_STORAGE no AndroidManifest.xml.
<uses-permission 
  android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Para criar o arquivo, usamos a classe File (do Java padrão) definindo o local e o nome do arquivo. Não esquecendo de importar o java.io.
File file = new File(
  getExternalFilesDir(null), 
  "MeuArquivoXLS.xls");
Isso criará o arquivo MeuArquivoXLS.xls no diretório Android/data/pacote.sua.app/files do cartão de memória. O método getExternalFilesDir é da classe Context, então parta do pressuposto que estou chamando o código acima de uma Activity. Esse método fará com que, se a aplicação for excluída, os arquivos da mesma também sejam. Caso não queira que isso aconteça, você pode usar o método Environment.getExternalStorageDirectory() que salvará o arquivo na raiz do cartão de memória.

Em seguida vamos criar o Excel Workbook, que é uma pasta de trabalho do Excel que pode conter uma ou mais Planilhas (Sheets). Observe que para criar o workbook usamos a classe WritableWorkbook que não tem construtor, e faz uso do FactoryMethod createWorkbook.
WritableWorkbook wb = null;
try {
  wb = Workbook.createWorkbook(file);
} catch (IOException e) {
  e.printStackTrace();
}
Já com o workbook criado, é preciso criar a planilha(Sheet). O método createSheet recebe dois parâmetros: o nome da planilha e a posição que ela vai ficar dentro do workbook.
wb.createSheet("Planilha", 0);
Agora para escrever nesta planilha precisamos pegar a instância de um WritableSheet através do método getSheet do objeto WorkBook. Esse método recebe como parâmetro o índice na planilha ou o nome.
WritableSheet plan = wb.getSheet(0);
Com a instância da planilha, podemos inserir valores nas células usando o método addCell que recebe um WritableCell que é uma interface. A classe Label representa um texto simples e implementa esta interface. Para criar um  Label em uma célula, passamos coluna, linha e o valor da célula.
Label label = new Label(0,0,"Primeira célula");
// Como o método pode levantar exceção 
// iremos coloca-lo dentro de um try/catch
try {
  plan.addCell(label);
} catch (RowsExceededException e1) {
  e1.printStackTrace();
} catch (WriteException e1) {
  e1.printStackTrace();
}
Pronto agora é só gravar o fechar o workbook.
wb.write();
wb.close();

Feito isso, basta rodar a aplicação e testar o código. Para pegar o arquivo, você pode utilizar a janela File Explorer da perspectiva DDMS. Navegue até o diretório e selecione o botão Pull File from Device.


Dúvidas? Mandem pra Leo :)

 4br4ç05,
nglauber

segunda-feira, 11 de junho de 2012

Facebook API no iOS

Olá povo,

Logo após escrever um post sobre como utilizar a API do Facebook no Android, tive que fazer o mesmo para o iOS, então resolvi compartilhar com vocês.

Registrando a aplicação do Facebook

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 iOS App.


Preencha o campo iOS Bundle ID com o ID da sua aplicação (configurado no Xcode) e habilite as opções Configured for iOS SSO e iOS Native Deep Linking.

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-ios-sdk e selecione a opção Download this repository as a ZIP file". Terminado o download, descompacte onde desejar.

Após descompactar, abra o terminal para gerar a lib que nos permitirá utilizar o Automatic Reference Counting (ARC) recurso do iOS que nos permite delegar para o sistema operacional limpeza dos objetos da memória. Digite o seguinte comando no diretório onde você descompactou o facebook SDK:

./scripts/build_facebook_ios_sdk_static_lib.sh

Será gerado o diretório lib/facebook. Você irá copiar esse diretório para o diretório da sua aplicação.

Criando o projeto

Crie um novo projeto no Xcode do tipo Single View Application e clique em Next. Preencha no nome do projeto e o company identifier de modo que juntos formem o campo iOS Bundle ID utilizado no cadastro do Facebook. Conclua o assistente de criação do projeto. Adicione a pasta lib que foi gerada na seção anterior para seu projeto (botão direito sobre o projeto, Add files).

Abra o delegate da sua aplicação e deixe-o conforme abaixo:

#import <UIKit/UIKit.h>
#import "FBConnect.h"

@interface NGAppDelegate : UIResponder 
  <UIApplicationDelegate, FBSessionDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) Facebook *facebook;

@end
#import "NGAppDelegate.h"

@implementation NGAppDelegate

@synthesize window = _window;
@synthesize facebook;

// Método que trata a autenticação após o facebook
// validar o login e senha do usuário no browser
- (BOOL)application:(UIApplication *)application 
  openURL:(NSURL *)url 
  sourceApplication:(NSString *)sourceApplication 
  annotation:(id)annotation {

  return [facebook handleOpenURL:url];
}

// *** Métodos do Protocolo FBSessionDelegate
// Salva o accessToken e data de expirar
-(void)fbDidLogin {
  NSUserDefaults *defaults = 
    [NSUserDefaults standardUserDefaults];
  [defaults setObject:[facebook accessToken] 
    forKey:@"FBAccessToken"];
  [defaults setObject:[facebook expirationDate] 
    forKey:@"FBExpirationDateKey"];
  [defaults synchronize];
}

// Ao fazer logout, apaga o accessToken
-(void)fbDidLogout {
  NSUserDefaults *defaults = 
    [NSUserDefaults standardUserDefaults];
  [defaults removeObjectForKey:@"FBAccessToken"];
  [defaults removeObjectForKey:@"FBExpirationDateKey"];
  [defaults synchronize];    
}

-(void)fbDidNotLogin:(BOOL)cancelled {}

-(void)fbDidExtendToken:(NSString *)accessToken 
  expiresAt:(NSDate *)expiresAt{}

- (void)fbSessionInvalidated {}

// *** Métodos de UIApplicationDelegate ***
- (BOOL)application:(UIApplication *)application 
  didFinishLaunchingWithOptions:
   (NSDictionary *)launchOptions
{
  facebook = [[Facebook alloc] 
    initWithAppId:@"ID_SUA_APP" andDelegate:self];
    
  // Carregar o AccessToken pra ver se está logado
  NSUserDefaults *defaults = 
    [NSUserDefaults standardUserDefaults];
  if ([defaults objectForKey:@"FBAccessToken"] && 
      [defaults objectForKey:@"FBExpirationDateKey"]){

    facebook.accessToken = 
      [defaults objectForKey:@"FBAccessToken"];
    facebook.expirationDate = 
      [defaults objectForKey:@"FBExpirationDateKey"];
  }
  // Se não estiver logado, exibe a tela de login
  if (![facebook isSessionValid]) {

    NSArray *permissions = [NSArray 
      arrayWithObjects:@"publish_stream", nil];
        
    [facebook authorize:permissions];
  }                            
    
  return YES;
}
Agora vamos mexer no ViewController
#import <UIKit/UIKit.h>
#import "Facebook.h"

@interface NGViewController : UIViewController 
  <FBRequestDelegate> {

  Facebook *facebook;    
}
@property (weak, nonatomic) IBOutlet 
  UITextField *txtMensagem;

- (IBAction)updateStatusClick:(id)sender;
- (IBAction)logoutClick:(id)sender;

@end
#import "NGViewController.h"
#import "NGAppDelegate.h"

@implementation NGViewController
@synthesize txtMensagem;

- (void)viewDidLoad
{
  [super viewDidLoad];
    
  NGAppDelegate *delegate = (NGAppDelegate *)
    [UIApplication sharedApplication].delegate;
  facebook = delegate.facebook;
}

- (void)viewDidUnload
{
  [self setTxtMensagem:nil];
  [super viewDidUnload];
  facebook = nil;
}

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

- (IBAction)updateStatusClick:(id)sender {
  NSMutableDictionary *params = 
    [[NSMutableDictionary alloc]init];

  [params setObject:txtMensagem.text 
    forKey:@"message"];
    
  [facebook requestWithGraphPath:@"me/feed" 
    andParams:params andHttpMethod:@"POST" 
    andDelegate:self];
}

- (IBAction)logoutClick:(id)sender {
  if (![facebook isSessionValid]) {
    NSArray *permissions = [NSArray arrayWithObjects:
      @"publish_stream", nil];
        
    [facebook authorize:permissions];
  } else {
    [facebook logout];
  }
}

-(void)request:(FBRequest *)request 
  didReceiveResponse:(NSURLResponse *)response {
  UIAlertView *alert = [[UIAlertView alloc] 
    initWithTitle:@"Info" message:response.description 
    delegate:nil cancelButtonTitle:@"OK" 
    otherButtonTitles:nil, nil];
  [alert show];
}

@end
Abra agora o *.plist com o nome da sua aplicação no modo XML e adicone o trecho abaixo dentro da tag <dic>:
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>fbID_SUA_APP</string>
    </array>
  </dict>
</array>
Arraste uma caixa de texto e ligue com o Outlet txtMensagem. Arraste dois botões e associe o evento Touch Up Inside aos métodos logoutClick e updateStatusClick. Feito isso é só rodar a aplicação:

Na primeira vez que a aplicação for executada, será exibida a tela acima, onde o usuário deverá autorizar nossa aplicação a utilizar a conta do facebook do usuário. Após aceitar a permissão, a nossa aplicação volta para foreground, e aparece conforme abaixo:
Pronto! Digite sua mensagem e clique no botão para atualizar o status do facebook.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 4 de junho de 2012

Android: Dicas 7

Olá povo,

Depois de um bom tempo, um novo post da série "Dicas de Android". Espero que gostem ;)

1. Aplicando um background para toda a aplicação

Primeiro precisamos criar uma extensão do estilo que está sendo utilizado na aplicação. Você pode fazer isso criando o arquivo styles.xml na pasta values e deixando-o conforme abaixo:
<resources>
  <style name="MeuTemaPersonalizado"
  android:parent="@android:style/Theme.Black.NoTitleBar">
  <item name="android:windowBackground">
    @drawable/sua_imagem_de_bg
  </item>
  </style>
</resources>

Depois, é só utilizar o estilo criado na tag
<application
  android:icon="@drawable/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/MeuTemaPersonalizado">

2. Deixando os nomes dos parâmetros corretos no Eclipse

No Eclipse, clique com o botão direito sobre o projeto e selecione Properties. No lado esquerdo, selecione Java Build Path, e em seguida, selecione a aba Libraries. Aqui você deve expandir a versão do Android que você está utilizando (2.3.3 por exemplo), e depois expandir o android.jar. Serão exibidos alguns subitens, entre eles, o Javadoc location. Basta clicar em Edit... e informar o local onde está a documentação do Android (até a pasta docs/reference). Depois é só clicar em Validate e pronto! Agora o Javadoc aparecerá no editor e nas classes/interfaces do Android que você herdar/implementar.


3. Bloqueando a Home key
Desde sempre dizia que não dava para bloquear a Home até que meu colega Rodrigo Barbosa precisou fazer isso e resolveu compartilhar aqui no blog. Basta colocar o método abaixo na sua Activity.
@Override 
public void onAttachedToWindow() {
  this.getWindow().setType(
    WindowManager.LayoutParams.TYPE_KEYGUARD); 
  super.onAttachedToWindow(); 
}

4. Lendo XML com a Biblioteca Simple XML 
Apesar de ser adepto do "Do it yourself", algumas pessoas preferem as facilidades das bibliotecas. Recebi essa dica de Rafael Cavalcanti, que me indicou a Simple XML Serialization que facilita a leitura de arquivos XML. Ele converte automaticamente os elementos do arquivo XML em Objetos e Atributos de uma classe utilizando apenas anotações.
Abaixo um tutorial no próprio site do desenvolvedor:
http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php

Mas para não deixar de mostrar alguma coisa aqui... Baixe o JAR da biblioteca e adicione ao Build Path do projeto.
@Root
class Carros {
  @ElementList(inline=true)
  public  List<Carro> list;
}

@Root
class Carro {
  @Element
  public String nome;
  @Element
  public String desc;
  @Element
  public String url_info;
  @Element
  public String url_foto;
}

Para ler esse XML do livro do Ricardo Lecheta era só fazer isso...

URL url = new URL(
  "http://livroandroid.com.br/"+
  "livro/carros/carros_esportivos.xml");

HttpURLConnection conexao = (HttpURLConnection) 
  url.openConnection();

Serializer serializer = new Persister();
Carros example = serializer.read(
  Carros.class, conexao.getInputStream());

for (Carro carro : example.list) {
  Log.d("NGVL", "Carro:" + carro.nome);
}


5. Personalizando Links do Android
A classe TextView tem a propriedade AutoLink (android:autoLink) que cria detecta links automaticamente no texto que está sendo exibido. Esses links podem abrir o navegador, cliente de email e o discador do aparelho. Mas e se quisermos fazer um link para abrir uma Activity nossa?
A classe Linkify permite criar links baseados em uma expressão regular.
TextView txt1 = (TextView)findViewById(R.id.textView1);
txt1.setText("Testando esse CEP 55555-4444");
// Zip code dos EUA
Pattern pattern = Pattern.compile(
  "\\d{5}([\\-]\\d{4})?");

String scheme = "glauber://";
Linkify.addLinks(txt1, pattern, scheme);
Notem que foi passado o esquema glauber:// Para fazer com que uma Activity nossa responda a chamadas a esse "protocolo" é só adicionar uma na declaração da Activity no AndroidManifest.xml
<activity
  android:name="ActivityQueSeraChamada"
  android:label="@string/app_name" >
  <intent-filter>            
    <action 
       android:name="android.intent.action.VIEW"/>
    <category 
      android:name="android.intent.category.DEFAULT"/>
    <data android:scheme="glauber"/>
  </intent-filter>
</activity>
Para obter o valor do link clicado, basta usar getIntent().getDataString(). Isso retornará "glauber://55555-4444"

6. TableLayout e a propriedade layout_span
Estava querendo fazer um exemplo de TableLayout similar ao abaixo na minha última aula do TECDAM:
Uma característica do TableLayout é que cada linha é uma TableRow e cada coluna é um componente, que no caso acima, são TextViews, EditText e Checkboxes. Eu utilizei para a segunda coluna, a propriedade android:stretch_columns que informa quais colunas devem ser esticadas.
Porém, se você observar, a linha de cima tem 2 colunas, enquanto que a de baixo tem 3, foi aí que achei a propriedade android:layout_span (que é bem similar a do HTML) e informa quanto uma coluna irá expandir e criar uma nova coluna. Vejam como ficou o código do layout acima.
<?xml version="1.0" encoding="utf-8"?>
<TableLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
  android:stretchColumns="1,2" >

  <TableRow>
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Nome" />

    <EditText
      android:id="@+id/editText1"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_span="2" />
  </TableRow>

  <TableRow>
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Avisos por:" />

    <CheckBox
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Email" />

    <CheckBox
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Telefone" />
  </TableRow>
</TableLayout>


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

4br4ç05,
nglauber