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

segunda-feira, 4 de abril de 2016

Carregando imagens de outras fontes com o Picasso

Olá pessoal,

As bibliotecas de carregamento de imagens são utilizadas em praticamente todos os projetos Android. Elas facilitam o carregamento de imagens a partir de diversas fontes como: web, sistema de arquivos e até de banco de dados. Possuem recursos de redimensionamento, crop, placeholder, animação, etc. As mais famosas desse segmento são: Picasso, Glide e Universal Image Loader (UIL). Todas são ótimas, já resolvi uns problemas com uma, outros problemas com outra e por aí vai.
No projeto que estou atualmente estou usando o Picasso, e estava precisando carregar imagens de um local não suportado nativamente por ele: o arquivo de expansão de APK.
Graças ao grande mestre Jake Wharton, podemos implementar isso de uma forma muito fácil. Basta criar uma subclasse de RequestHandler.
import com.squareup.picasso.Request;
import com.squareup.picasso.RequestHandler;

public class MeuRequestHandler extends RequestHandler {

    public MeuRequestHandler() {
    }

    @Override
    public boolean canHandleRequest(Request data) {
        // Retorne true se essa classe pode tratar a leitura da imagem
        return data != null
                && data.uri != null
                && data.uri.getScheme() != null
                && data.uri.getScheme().startsWith("ngvl");
    }

    @Override
    public Result load(Request request, int networkPolicy) 
            throws IOException {
        Bitmap imagem = metodoQueCarregaSeuBitmap(request.uri);
        Result result = new Result(imagem, Picasso.LoadedFrom.DISK);
        return result;
    }
}
Essa classe possui apenas dois métodos:
  • canHandleRequest(Request) define se essa classe é capaz de carregar uma determinada imagem. Nesse exemplo, estou usando o parâmetro Request para checar se o endereço (definido por uma Uri) começa com "ngvl". 
  • Já o método load(Request, int) carrega a imagem em si e a retorna por meio de um objeto Result, que recebe o Bitmap da imagem e de onde ela foi carregada (memória, disco ou rede).
Criado o handler, basta adicioná-lo a uma instância do Picasso.
Picasso = mPicassoInstance = 
    new Picasso.Builder(mContext.getApplicationContext())
        .addRequestHandler(new MeuRequestHandler())
        .build();
Com isso, você está adicionando uma nova fonte de imagens à sua instância do Picasso, ou seja, você pode usar todos os schemas já suportados pelo Picasso (http, file, content, ...) e esse que acabamos de criar.
Agora, se invocarmos o código a seguir, o nosso handler tratará essa requisição.
mPicassoInstance
        .load("ngvl://minhaimagem/logo.jpg")
        .into(imageView);

#perfmatters #protip É importante que só haja uma instância desse objeto para evitar problemas de memória! Sendo assim, implemente um Singleton, e caso precise de um Context, passe o getApplicationContext() para ele ;)

4br4ç05,
nglauber

sexta-feira, 29 de novembro de 2013

Comunicação HTTP eficiente no Android com Volley

Olá povo,

Conversando com meus novos mentores Neto Marin, Marcelo Quinta e Lúcio Maciel que conheci no DevBus Brasil, percebi uma unanimidade no que diz respeito a acesso HTTP no Android: a biblioteca Volley. Quando assisti o vídeo do Ficus Kirkpatrick no Google IO de 2013 não dei muita bola, mas os meus novos "orientadores" me mostraram o quanto essa biblioteca é poderosa e fácil de usar. Tudo isso aliado ao fato dela ser mantida pelo próprio Google.
Eu já falei em outros posts aqui do blog como fazer comunicação HTTP com AsyncTask (link 1 e link 2) e com AsyncTaskLoader (link), assim como carregar imagens da web em um Adapter usando o UniversalImageLoader (link). E também como ler um JSON de um WebService REST (link). Mas com o Volley podemos resolver todos os problemas citados nos posts anteriores e ainda remover todo aquele boiler plate que fazemos ao realizar essas requisições, simplificando o código. Além disso, ela tem as seguintes vantagens:
  • Comunicação paralela com possibilidade de priorização. Por exemplo, podemos criar uma fila de requisições JSON e outra de requisição de imagens e dizer que a primeira tem maior prioridade que a segunda;
  • Quando giramos a Activity, a mesma é destruída (mostrei como resolver isso aqui) e se nesse momento estiver havendo alguma requisição HTTP, o resultado é perdido. Podemos implementar algum tipo de cache em memória ou em banco, mas o Volley já faz esse serviço pra gente.
  • Um "ImageLoader" para carregar imagens da web, particularmente útil em adapters (falei de adapter aqui).
Depois dessa breve introdução, vamos por a mão na massa!

O Volley está em um repositório git no código-fonte do próprio Android. Então você terá que fazer um clone do mesmo via comando. Se você não tem o git instalado na sua máquina, siga esse tutorial aqui. Depois é só digitar no terminal.
git clone https://android.googlesource.com/platform/frameworks/volley

Feito isso, importe o projeto do Volley dentro do Eclipse e marque-o como biblioteca clicando com o botão direito sobre o projeto e selecionando Properties. Em seguida, selecione a opção Android do lado esquerdo e marque a checkbox "Is Library".

Em homenagem ao Ricardo Lecheta, vou fazer uma Activity que lê o JSON de carros disponível no site do seu livro de Android (que por sinal é muito bom e uso nas minhas aulas). E para ler o JSON e as imagens dos carros, vamos usar o Volley.

Vamos começar pela classe que vai manter a fila de execução de requisições do Volley bem como seu ImageLoader. O Google recomenda que ela seja um singleton, pois podemos gerenciar quantas filas de execução podemos ter e não termos que criar várias.
public class VolleySingleton {
  private static VolleySingleton mInstance = null;
  // Fila de execução
  private RequestQueue mRequestQueue;
  // Image Loader
  private ImageLoader mImageLoader;
 
  private VolleySingleton(Context context){
    mRequestQueue = Volley.newRequestQueue(context);

    mImageLoader = new ImageLoader(this.mRequestQueue, 
      new ImageLoader.ImageCache() {
        // Usando LRU (Last Recent Used) Cache
        private final LruCache<String, Bitmap> 
          mCache = new LruCache<String, Bitmap>(10);

        public void putBitmap(
          String url, Bitmap bitmap) {
          mCache.put(url, bitmap);
        }
        public Bitmap getBitmap(String url) {
          return mCache.get(url);
        }
      });
  }
 
  public static VolleySingleton getInstance(
    Context context){

    if(mInstance == null){
      mInstance = new VolleySingleton(context);
    }
    return mInstance;
  }
 
  public RequestQueue getRequestQueue(){
    return this.mRequestQueue;
  }
 
  public ImageLoader getImageLoader(){
    return this.mImageLoader;
  }
}
Vou definir um POJO simples que representa os objetos carro que iremos listar.
public  class Carro {
  String nome;
  String imageUrl;
 
  public Carro(String nome, String imageUrl) {
    this.nome = nome;
    this.imageUrl = imageUrl;
  }
}
Abaixo temos o arquivo de layout usado no adapter. Note que estamos usando NetworkImageView ao invés do ImageView tradicional.
<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/itemRoot"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" >

  <com.android.volley.toolbox.NetworkImageView
    android:id="@+id/img"
    android:layout_width="140dp"
    android:layout_height="70dp"
    android:src="@drawable/ic_launcher" />

  <TextView
    android:id="@+id/txtName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:textSize="20dp"
    android:text="@null"/>
</LinearLayout>
A classe Adapter listada abaixo herda de ArrayAdapter e está usando o NetworkImageView e o ImageLoader do Volley.
public class CarroAdapter extends ArrayAdapter<Carro>{

  static final int LAYOUT = R.layout.item_lista;
 
  public CarroAdapter(Context context, 
    List<Carro> objects) {

    super(context, LAYOUT, objects);
  }

  @Override
  public View getView(int position, 
    View convertView, ViewGroup parent) {

    Context ctx = parent.getContext();
    if (convertView == null){
      convertView = LayoutInflater.from(ctx)
        .inflate(R.layout.item_lista, null);
    }
    NetworkImageView img = (NetworkImageView)
      convertView.findViewById(R.id.img);
    TextView txt = (TextView)
      convertView.findViewById(R.id.txtName);
  
    Carro carro = getItem(position);
    txt.setText(carro.nome);
    img.setImageUrl(
      carro.imageUrl, 
      VolleySingleton.getInstance(
        getContext()).getImageLoader());
  
    return convertView;
  }
}
E finalmente a Activity... Ela implementa duas interfaces: Response.Listener e Response.ErrorListener. O método da primeira (onResponse) será chamada quando a requisição ocorrer sem problemas, e da segunda (onErrorResponse) caso contrário. No onCreate, obtemos a fila de execução a partir do nosso singleton, e depois criamos um JsonObjectRequest. No Volley, além desse tipo de Request, temos o ImageRequest e o StringRequest, o primeiro para imagens e o segundo para qualquer requisição que retorne uma String. Após criar a requisição, a adicionamos na fila para ser executada.
No método onResponse já recebemos o JSONObject, então é só fazer o parse do mesmo. Aqui poderíamos usar o Gson, mas eu não gosto dele :p
Após transformar o JSONObject em uma Lista de Carros, criamos e setamos o adapter da nossa ListActivity.
Se alguma coisa der errado, o método onErrorResponse será chamado, e daí estamos exibindo um Toast.
public class MainActivity extends ListActivity 
  implements 
    Response.Listener<JSONObject>, 
    Response.ErrorListener {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    String url="http://www.livroandroid.com.br/livro/"+
      "carros/carros_classicos.json";
  
    RequestQueue queue = Volley.newRequestQueue(this);

    JsonObjectRequest jsObjRequest = 
      new JsonObjectRequest(
        Request.Method.GET, // Requisição via HTTP_GET
        url,   // url da requisição
        null,  // JSONObject a ser enviado via POST
        this,  // Response.Listener
        this); // Response.ErrorListener
  
    queue.add(jsObjRequest);  
  }

  @Override
  public void onResponse(JSONObject response) {
    List<Carro> carros = new ArrayList<Carro>();
  
    try {
      // Não precisamos converter o 
      // InputStream em String \o/
      JSONObject jsonCarros = 
        response.getJSONObject("carros");
      JSONArray jsonCarro = 
        jsonCarros.getJSONArray("carro");
   
      for (int i = 0; i < jsonCarro.length(); i++) {
        JSONObject jsonCarroItem = 
          jsonCarro.getJSONObject(i);
        String nome = 
          jsonCarroItem.getString("nome");
        String thumbnail = 
          jsonCarroItem.getString("url_foto");
    
        Carro carro = new Carro(nome, thumbnail);
        carros.add(carro);
      }
    } catch (Exception e){
      e.printStackTrace();
    }
   
    setListAdapter(new CarroAdapter(this, carros));
  }
 
  @Override
  public void onErrorResponse(VolleyError error) {
    Toast.makeText(this, "Erro!", 
      Toast.LENGTH_SHORT).show();
  }
}
Ah! Como toda app Android que acessa a Web, adicione a permissão de Internet no seu AndroidManifest.xml.

É isso, a partir de agora podemos utilizar o Volley em nossas aplicações com essa biblioteca usada e mantida pelo próprio Google. Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

domingo, 17 de novembro de 2013

Universal Image Loader

Olá povo,

Quem nunca precisou carregar imagens vindas da rede ou do cartão de memória em um Adapter no Android? Nativamente não temos uma maneira simples de fazer isso (porque hein Google?). Se assim como eu, você sempre buscava alguma solução alternativa, eu recomendo o Universal Image Loader.

Ele é o mais simples de usar que achei, e o que fornece mais recursos, pois nos permite setar uma imagem para uma ImageView passando apenas uma URI, que pode ser:
// da Web
String imageUri = "http://site.com/image.png"; 
// do SD card
String imageUri = "file:///mnt/sdcard/image.png"; 
// de um content provider
String imageUri = 
  "content://media/external/audio/albumart/13"; 
// da pasta assets do seu projeto
String imageUri = "assets://image.png"; 
// da pasta res/drawable
String imageUri = "drawable://" + R.drawable.image; 
Foi essa biblioteca que utilizei no aplicativo do blog. E como pode ser visto no site deles, já foi utilizada e aprovada por dezenas de aplicações.
Os passos da utilização são bem simples e estão descritos no site, mas vou colocar aqui a maneira mais simples. 1) Faça o download do jar e adicione na pasta libs do seu projeto. 2) Adicione as permissões de Internet e de escrita no cartão de memória no AndroidManifest.xml. Além disso, vamos criar uma classe que herda de Application e também declará-la no manifest. Essa classe é o melhor lugar para inicializarmos os singletons da nossa aplicação, pois ela é ponto de partida da mesma.

<uses-permission 
  android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission 
  android:name="android.permission.INTERNET" />

<application
  android:name="pacote.da.app.ClasseDaSuaApp" ... >
Agora adicione a classe da sua app que inicializará o Universal Image Loader.
public class ClasseDaSuaApp 
  extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    DisplayImageOptions defaultOptions = 
      new DisplayImageOptions.Builder()
        .cacheInMemory(true)
        .cacheOnDisc(true)
        .build();

    ImageLoaderConfiguration config = 
      new ImageLoaderConfiguration.Builder(
          getApplicationContext())
        .defaultDisplayImageOptions(defaultOptions)
        .build();

    ImageLoader.getInstance().init(config);
  }
}
No código acima, ativamos a opção de cache em memória e em disco. Feito isso, é só utilizar no seu adapter lá no getView.
String imgUrl = entry.thumbnailURL;
holder.imgThumbnail.setImageResource(R.drawable.ic_launcher);
if (!TextUtils.isEmpty(imgUrl)){
  ImageLoader.getInstance().displayImage(imgUrl, holder.imgThumbnail);
}
Qualquer dúvida, consultem o site da biblioteca no github.

4br4ç05,
nglauber