Mostrando postagens com marcador HTTP. Mostrar todas as postagens
Mostrando postagens com marcador HTTP. Mostrar todas as postagens

quarta-feira, 22 de maio de 2013

Android: Dicas 9

Olá povo,

Segue mais um post da série "Dicas de Android". Aproveitem!

Dica 1 - Driver ADB universal para Windows

Essa dica foi dada pelo Pedro Borba, meu aluno da Unibratec. Muitas vezes quando conectamos alguns devices menos populares e eles não são reconhecidos pelo Windows, e às vezes é até complicado achar esses drivers na internet. O site abaixo disponibiliza uma versão do driver ADB, que funciona em muitos desses aparelhos. Vale a pena conferir.

http://adbdriver.com/

Dica 2 - Imagem de background repetida
Como nas velhas páginas HTML, às vezes é interessante colocar uma imagem de background repetida lado a lado, dando a impressão que é uma imagem maior. A solução é criar um arquivo XML (meu_bg.xml por exemplo) na pasta drawable com o seguinte conteúdo.
<bitmap 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:src="@drawable/minha_imagem_que_repete"
  android:tileMode="repeat" />
Depois é só colocar @drawable/meu_bg como sua imagem de background.

Dica 3 - Vários núcleos do processador para o Emulador
Essa dica foi dada por Daniel Sant'Ana, meu aluno do TECDAM. Quando executamos o emulador,  apenas 1 núcleo do processador da máquina é ativado. Para permitir que o emulador utilize os outros núcleos do processador, abra o gerenciador de tarefas do windows, clique em processos, e encontre o processo referente ao emulador. Em seguida, clique com o botão direito nele, e escolha a opção afinidade, então marque todos os núcleos. Se quiser, você ainda pode definir a prioridade do processo do emulador como alta. Feito isso, é possível ganhar um pouco de desempenho no emulador.

Outra opção, em fase experimental, pode ser vista aqui, na seção "Configuring Virtual Machine Acceleration". Ou aqui, no post escrito pelo meu colega Eric Cavalcanti.

Dica 4 - HttpURLConnection ou HTTPClient
A maioria das aplicações Android utiliza HTTP para enviar e receber dados. O Android tem duas APIs para essa tarefa: HTTPClient da Apache e HttpURLConnection do próprio Java.
Conforme podemos ver aqui e aqui, o pessoal da Google está recomendando utilizar o HttpURLConnection para aplicações voltadas para Android 2.3 ou superior. Nas versões 2.2 e inferiores, essa API tinha uma série de bugs que foram corrigidos a partir da versão seguinte. Além disso, alguns recursos como compressão e cache.

Dica 5 - DumbleDroid e WebCachedImageView
Meu amigo Leocádio Tiné, um dos caras que mais conhece de Android que eu conheço, acabou de disponibilizar para a comunidade essas duas bibliotecas interessantíssimas. A primeira faz o download de arquivos XML e JSON e autoMAGICAmente cria as classes Java que representam essas estruturas.
Perguntei ao Leocádio qual a diferença entre essa biblioteca e a GSON (da Google) e o Simple XML. E a resposta foi:

"A diferença básica do Dumbledroid pra essas 2 libraries é que o Dumbledroid faz caching automático em memória e em disco, e roda código específico pra Android. Por exemplo: ele usa as classes de JSON incluídas no Android SDK, e não as do Java SDK, como o GSON. As diferenças específicas: GSON: Usando GSON, você tem que carregar o JSON manualmente. Usando Dumbledroid, basta passar a URL que ele faz o carregamento, parsing e caching. É mais simples. SimpleXML: Usando SimpleXML, além de ter que carregar o XML manualmente, você tem que escrever annotations nas classes pra mapear os campos aos nós do XML. No Dumbledroid, isso não é preciso."

Além disso o DumbleDroid já tem um plugin para Eclipse que facilita ainda mais o seu uso.
Já o WebCachedImageView é bem similar ao ImageView nativo, com o benefício de passarmos apenas a URL da imagem que desejamos e ainda fazer o cache da mesma.

Segue os links para download das libs:
https://github.com/leocadiotine/Dumbledroid
https://github.com/leocadiotine/WebCachedImageView

Vou tentar fazer um post só pra essas duas libs em breve.

Dica 6 - Detectando JavaScript da WebView na Activity
Um recurso que pode trazer grandes possibilidades é conseguirmos capturar funções Java Script que são executadas dentro de uma WebView. Criem o arquivo meu.html dentro da pasta assets do projeto e deixe-o conforme abaixo.
<html>
<header>
<script type="text/javascript">
function showAndroidToast(s, t) {
  window.nglauber.showToast(s, t);
} 
</script>
</header>
<body>
  <H1>Formulario em HTML</h1>
  <form name="meuForm">
    Nome: 
    <input type="text" name="txtNome"/><br>
    Idade: 
    <input type="text" name="txtIdade"/><br>
    <input type="button" 
      onclick="showAndroidToast(txtNome.value, txtIdade.value);" 
      value="Enviar">
  </form>
</body>
</html>
É possível interceptar a função showAndroidToast e chamar o método showToast dentro da nossa Activity. Dessa forma, quando o usuário pressionar o botão do HTML, o código da Activity será executado.
public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  
    WebView wv = (WebView)findViewById(R.id.webView1);
    WebSettings settings = wv.getSettings();
    settings.setJavaScriptEnabled(true);
    wv.addJavascriptInterface(this, "nglauber");
    wv.loadUrl("file:///android_asset/meu.html");
  }

  @JavascriptInterface
  public void showToast(String s, String t) {
    Toast.makeText(this, 
      "Nome:"+ s + " Idade:"+ t, 
      Toast.LENGTH_SHORT).show();
  }
}
Apesar de não ser algo que não encorajo, essa solução pode ser útil para alguns tipos de tela. Disponibilizamos a interface "nglauber" para o código JavaScript chamar. Notem que no JavaScript ficou window.nglauber.showToast. Outro detalhe aqui é que existe um bug do Android 2.3 que impede que esse código funcione nessa versão. Outro detalhe é a Annotation @JavascriptInterface que só é necessária a partir do Android 4.2, em versões anteriores, basta o método ser público.

Dica 7 - Definindo uma orientação fixa via código
É possível definir uma orientação fixa para um Activity através do arquivo AndroidManifest.xml através da propriedade screenOrientation.
<activity name=".MinhaActivity"   
  android:screenOrientation="landscape"/>
Mas e se quisermos que essa configuração seja feita dependendo de alguma condição? Podemos usar o código abaixo.
setRequestedOrientation(
  ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)

Dica 8 - Versão do Android do Aparelho
Obtendo a versão do Android que está rodando no aparelho.
int apiLevel = Build.VERSION.SDK_INT;
if (apiLevel < Build.VERSION_CODES.HONEYCOMB){
  // Usando Android 2.3 ou inferior
} else if (apiLevel < 
  Build.VERSION_CODES.ICE_CREAM_SANDWICH){
  // Android 3.x
} else {
  // ICS ou superior
}

4br4ç05,
nglauber

quarta-feira, 1 de maio de 2013

AsyncTaskLoader

Olá povo,

Uma das coisas mais comuns em aplicações móveis é o acesso a servidores web através do protocolo HTTP. O Android obviamente nos permite esse tipo de comunicação de várias formas. Mas uma forma interessante foi introduzida na versão 3 da plataforma: os Loaders. Esse tipo de abordagem permite isolar a Activity da classe que está fazendo o download dos dados.
Um exemplo típico dessa abordagem é a utilização da classe AsyncTask - que eu sempre usei, e ainda uso - jutamente com um ProgressDialog. Normalmente o ProgressDialog era um atributo da AsyncTask  e no onPreExecute ele era exibido, sendo ocultado no onPostExecute. O problema dessa abordagem é que ao rotacionar a tela do aparelho, a Activity é recriada. Dessa forma, quando a AsyncTask terminava seu trabalho, a instância do Dialog armazenada ficava apontando para a instância da Activity que o tinha criado, gerando uma exceção em tempo de execução.

Com a chegada dos Fragments, é possível reter a instância do mesmo e ter um controle melhor sobre a rotação da tela (e outras mudanças de configuração). Mas a abordagem que eu achei interessante é a utilização da classe AsyncTaskLoader em conjunto com o LoadManager e a interface LoaderCallbacks. Vejamos o exemplo abaixo:
public class MainActivity 
  extends FragmentActivity 
  implements LoaderCallbacks<String> {

  ProgressDialog dialog;
  String json;
 
  private static final int MEU_LOADER = 0;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Recuperando o estado da Activity
    if (savedInstanceState != null) {
      json = savedInstanceState.getString("valores");
      atualizaTela();
    }

    // Essa classe gerencia todos os Loaders
    LoaderManager lm = getSupportLoaderManager();

    // Se o JSON ainda não foi baixado
    if (json == null) {
      // Exibe o dialog
      dialog = ProgressDialog.show(
        this, "Aguarde...", "Baixando tweets");

      // E inicializa o Loader (se ele já foi 
      // inicializado, ele apenas continuará)
      lm.initLoader(MEU_LOADER, null, this);
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    // Se estiver exibindo o progress, remova-o 
    if (dialog != null && dialog.isShowing()) 
      dialog.dismiss();
  } 

  @Override
  protected void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);
    // Salvando o estado da Activity
    outState.putString("valores", json);
  }

  // Métodos da interface LoaderCallbacks -----------
  @Override
  public Loader<String> onCreateLoader(
    int id, Bundle args) {
    // Instancia e retorna a AsyncTaskLoader
    return new MinhaAsyncTask(this);
  }

  @Override
  public void onLoadFinished(Loader<String> loader, 
    String dados) {
    // Chamado quando o Loader termina seu trabalho
    // Aqui, atribuímos o resultado ao 
    // atributo json e ocultamos o dialog
    json = dados;
    atualizaTela();
    dialog.dismiss();
  }

  @Override
  public void onLoaderReset(Loader<String> loader) {
     // Chamado se o Loader for resetado
     Log.d("NGVL", "onLoaderReset");
  }

  // AsyncTaskLoader
  public static class MinhaAsyncTask 
    extends AsyncTaskLoader<String> {

    // O JSON baixado será armazenado aqui para ser 
    // repassado para Activity
    String data;

    public MinhaAsyncTask(Context context) {
      super(context);
    }

    @Override
    protected void onStartLoading() {
      // Se não baixou os dados, faça agora!
      if (data == null) {
        forceLoad();

      // Se já baixou, apenas entregue o resultado 
      } else {
        deliverResult(data);
      }
    }

    @Override
    public String loadInBackground() {
      // Nesse método será feito o download do JSON
      return downloadJSON();
    }
  }
}
O código acima está todo comentado. Então vou focar nos detalhes que devem ser observados.
O primeiro ponto é utilizar as classes do pacote android.support.v4.* pois o conceito de Loaders só surgiu no Honeycomb, então utilizei aqui a API de compatibilidade para que esse código execute sem problemas nas versões anteriores ao Android 3. O segundo ponto interessante é que, com essa abordagem o download não é reiniciado quando a Activity é recriada.
Outro detalhe é que, caso você queira chamar o loader novamente, você deve usar o método restartLoader e não o initLoader. E se quiser passar parâmetros para o Loader, pode usar um objeto Bundle e passá-lo como o segundo argumento desses métodos.
getSupportLoaderManager().restartLoader(
  MEU_LOADER, null, this);
Os métodos downloadJSON() e atualizaTela() devem ser implementados por você :) para baixar o JSON desejado e atualizar a tela da maneira apropriada.
Abaixo eu coloquei um exemplo desses dois métodos que baixa o JSON de uma busca no Twitter e exibe os textos dos Tweets em uma ListView.
private void atualizaLista() {
  if (json == null) return;
  try {
    JSONObject jsonObj = new JSONObject(json);
    JSONArray jsonResults = 
      jsonObj.getJSONArray("results");
   
    List<String> texts = new ArrayList<String>();
    for (int i = 0; i < jsonResults.length(); i++) {
      JSONObject result = jsonResults.getJSONObject(i);
      texts.add(result.getString("text"));
    }
   
    ListView list = (ListView)
      findViewById(R.id.ListView1);

    list.setAdapter(new ArrayAdapter<String>(this, 
      android.R.layout.simple_list_item_1, texts));
   
  } catch (JSONException e) {
    e.printStackTrace();
  }
}
 
private static String downloadJSON() {
  try {
    URL url = new URL(
      "http://search.twitter.com/search.json?q=jctransito");
    HttpURLConnection conexao = (HttpURLConnection)
      url.openConnection();
    conexao.connect();
   
    InputStream is = conexao.getInputStream();
    BufferedReader reader = new BufferedReader(
      new InputStreamReader(is));

    String s = reader.readLine();
    return s;
   
  } catch (Exception e) {
    e.printStackTrace();
  }
  return null;
}
Não esqueça de adicionar a permissão de INTERNET no AndroidManifest.xml.

Com abordagem acima, mesmo ao girar o aparelho, o download continuará, e caso não tenha terminado quando a Activity carregar, o ProgressDialog será exibido novamente.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Referências:
http://developer.android.com/reference/android/app/LoaderManager.html
http://developer.android.com/reference/android/app/LoaderManager.LoaderCallbacks.html
http://developer.android.com/reference/android/content/AsyncTaskLoader.html

terça-feira, 28 de agosto de 2012

iOS: Lendo JSON

Olá povo,

Nesse post vou mostrar como ler JSON utilizando a API nativa do iOS. Como exemplo, vamos obter os principais tópicos do Twitter conhecidos como Top Trends.
Crie um novo projeto no Xcode, e marque para utilizar Storyboards e o ARC (Automatic Reference Counting). No ViewController principal da aplicação, declare um NSMutableArray que armazenará a lista dos tópicos que serão mostrados na UITableView. Declare também um NSMutableData, que armazenará os bytes do arquivo JSON que iremos ler na conexão HTTP.
Note que nossa classe implementa o protocolo NSURLConnectionDelegate, que contém os métodos que são chamados pela conexão HTTP. No nosso caso, estamos utilizando para ler os bytes da conexão ser travar a Thread de UI.

#import <UIKit/UIKit.h>

@interface NGViewController : UITableViewController
  <NSURLConnectionDelegate> {

  NSMutableArray *trends;
  NSMutableData *data;
}
@end
O arquivo de implementação ficará como abaixo:
#import "NGViewController.h"

@implementation NGViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  trends = [NSMutableArray new];
  data = [NSMutableData new];
    
  NSURL *url = [NSURL URLWithString:
    @"https://api.twitter.com/1/trends/23424768.json"];
  NSURLRequest *request = 
    [[NSURLRequest alloc]initWithURL:url];   
  NSURLConnection *conexao = [[NSURLConnection alloc]
    initWithRequest:request delegate:self];
  [conexao start];
}

- (void)viewDidUnload {
  [super viewDidUnload];
  trends = nil;
  data = nil;
}

// Métodos de UITableViewController

- (UITableViewCell *)tableView:
  (UITableView *)tableView 
  cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  UITableViewCell *cell = [tableView
    dequeueReusableCellWithIdentifier:@"Cell"];
    
  cell.textLabel.text = 
    [trends objectAtIndex:indexPath.row];
    
  return cell;
}

- (NSInteger)numberOfSectionsInTableView:
  (UITableView *)tableView {
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section {
  return trends.count;
}

// NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection
  didReceiveData:(NSData *)pdata {
  [data appendData:pdata];
}


- (void)connectionDidFinishLoading:
  (NSURLConnection *)connection {   
  id jsonObject = [NSJSONSerialization 
    JSONObjectWithData:data 
    options:NSJSONReadingMutableContainers error:nil];
    
  id jsonTrends =  [
    [jsonObject objectAtIndex:0] 
      objectForKey:@"trends"];
        
  for (NSDictionary *trend in jsonTrends) {
    [trends addObject:[trend objectForKey:@"name"]];
  }
    
  [self.tableView reloadData];
}

@end
No método viewDidLoad inicializamos nossos dois atributos e logo em seguida começamos o processo de conexão com o servidor. Notem que a URL termina com 23424768.json, esse número é o woeid do Brasil, se quiser os tópicos mundiais, basta substituir por 1.json. Mas se quiser obter os tópicos de outro país, basta acessar http://developer.yahoo.com/yql/console/ e digitar o comando select * from geo.places where text="Seu Local". Mais informações sobre woeid aqui.
Em seguida criamos um request e enviamos essa solicitação através da conexão. O parâmetro delegate indica que nossa classe será notificada sobre eventos na conexão. O método viewDidUnload liberará os recursos alocados.
Não vou comentar os métodos de UITableViewController, mais detalhes aqui. Na nossa classe implementamos dois métodos do protocolo NSURLConnectionDelegate. O método connection:didReceiveData alimentará o nosso atributo data com os bytes recebidos pela conexão. Já o método connectionDidFinishLoading será chamado quando os dados terminarem de serem lidos. É nele que estamos fazendo a leitura do JSON.
A classe NSJSONSerializarion recebe um objeto NSData e retorna um objeto. Um documento JSON em Objective-C consta basicamente de NSArray ou NSDictionary. Se acessarmos a URL que definimos no primeiro método da nossa classe, vamos visualizar o arquivo JSON e se quisermos vê-lo de uma forma mais amigável, podemos utilizar o site http://jsonviewer.stack.hu/ e colar o texto do arquivo JSON lá. Então será apresentado o documento de uma forma hierárquica. Conforme a figura abaixo:
O elemento com [] é um array, enquanto que o {} é um dicionário (contento chave/valor). Sendo assim, a variável jsonObject contém um array de apenas um elemento. Dentro desse elemento temos um array chamado trends. Esse array está sendo representado pela variável jsonTrends. Em seguida, percorremos esse array (que tem vários dictionaries) e em cada dictionary obtemos o valor da chave name, que contém o nome do tópico mais comentado.
O resultado da aplicação é mostrado abaixo:
Qualquer dúvida, deixem seus comentários,

4br4ç05,
nglauber

sexta-feira, 17 de junho de 2011

Tarefas assíncronas no Android

Olá pessoal,

Em muitos momentos será necessário a comunicação com um servidor web para fazer a integração da aplicação mobile. Essa atividade deve ser feita em uma Thread separada da UI, entretanto precisamos dar um feedback para o usuário de que essa operação está sendo executada. Para esse tipo de tarefa o Android disponibiliza a classe AsyncTask. Ele permite configurar o que será feito antes, durante e depois da execução de uma tarefa.
No código eu vou acessar a página do dicionário Michaelis para obter a tradução de uma determinada palavra. No onClick do botão, estou iniciando uma nova AsyncTask. Notem que foi passado o conteúdo da caixa de texto como parâmetro pro método execute.
O método downloadArquivo baixará o conteúdo HTML que é gerado pelo site do Michaelis.

public class PrincipalActivity extends Activity
implements OnClickListener {

private String URL_TXT  =
  "http://michaelis.uol.com.br/moderno/portugues/"+
  "index.php?lingua=portugues-portugues&"+
  "typeToSearchRadio=exactly&pagRadio=50&palavra=";
private TextView txtHTML;
private EditText edtTexto;
 
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.teste_async);

  findViewById(R.id.button1).setOnClickListener(this);

  txtHTML = (TextView)findViewById(R.id.textView2);
  edtTexto = (EditText)findViewById(R.id.editText1);
}

public void onClick(View v) {
  new MinhaAsyncTask().execute(
    edtTexto.getText().toString());
}

public String downloadArquivo(String url) {
  HttpURLConnection connection = null;
  InputStream is = null;
  ByteArrayOutputStream bos = null;
  String s = null;
  
  try {
    URL urlAddr = new URL(url);
    connection = (HttpURLConnection)
      urlAddr.openConnection();

    connection.setRequestMethod("GET");
    connection.setDoInput(true);     
    connection.setDoOutput(false);
    connection.connect();
   
    is = connection.getInputStream();
   
    bos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length;
   
    while ((length = is.read(buffer)) > 0 ) {
      bos.write(buffer, 0, length);
    }
    
    s = new String(bos.toByteArray(), "ISO-8859-1");
   
  } catch (Exception e) {
    e.printStackTrace();
  }  finally {
    try {
      bos.close();
      is.close();
      connection.disconnect();
    } catch (Exception e){}
  }

  return s;
}
 
class MinhaAsyncTask 
  extends AsyncTask <String, Void, String> {

  private ProgressDialog progressDialog;

  protected void onPreExecute() {
    progressDialog = new ProgressDialog(
      PrincipalActivity.this);

    progressDialog.setMessage("Aguarde...");
    progressDialog.show();
  }

  protected String doInBackground(String... v) {
    String s = downloadArquivo(
      URL_TXT + v[0].toString());

    return s;
  }

  protected void onPostExecute(String result) {
    txtHTML.setText(result);
    progressDialog.dismiss();
  }
}
}


Nossa AsyncTask é declarado recebendo 3 valores genéricos. O primeiro é o tipo do dado que será passado como parâmetro na para o método que executará em segundo plano (doInBackground). O segundo é o tipo do dado que pode ser usado para enviar o progresso de alguma execução da thread background (onProgressUpdate). E o último é o tipo de retorno da ação que será realizada em segundo plano.
No nosso caso usamos: uma String, que será a palavra a ser buscada; Void, já que não usarei o progresso com números; e a String que será o HTML lido.

EDITADO 19/06/2012
Para o Android, toda aplicação que possa onerar ou por em risco os dados do usuário deve requerer uma permissão para pode utilizar determinado recurso. No caso da nossa aplicação é a permissão de acesso a internet. Adicione a permissão abaixo dentro da tag do AndroidManifest.xml
<uses-permission
  android:name="android.permission.INTERNET" />

Esse post foi baseado no post sobre AsyncTask do blog Eu, Android e foi enviado pelo meu aluno Bruno Baudel. Qualquer dúvida, olhem o blog dos caras e deixem seus comentários.

4br4ç05,
nglauber