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).
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.
4br4ç05,
nglauber