O Instagram como você já deve ter ouvido falar é um serviço de compartilhamento de fotos. Como muitos dos aplicativos web ele fornece uma API para permitir que outras aplicações troquem informações com ele, por isso é um exemplo legal de como fazer o Android acessar um WebService.

Para construir um aplicativo que se comunique com o Instagram é necessário registrar sua aplicação para obter um cliente_id e uma secret, isso pode ser feito pelo endereço Instagram Developer. Após logar com sua conta você deve ir em Manage Clients -> Register a New Client. Após ter se cadastrado corretamente você terá os seguintes dados.

Para consultar por tag temos as seguintes URL

https://api.instagram.com/v1/tags/{TAG_A_BUSCAR}/media/recent?client_id={CLIENT_ID}

Para consultar as postagens de um usuário temos a seguinte URL

https://api.instagram.com/v1/users/{ID_DO_USUARIO}/media/recent/?client_id={CLIENT_ID}

* o ID_DO_USUARIO não é o login e sim o identificar, você pode utilizar a ferramenta do pinceladas da web

Existem várias outras URLs que fornecem informações diferentes, você pode ver melhor na documentação oficial.

Se você já acessou a URL pelo navegador deve ter notado que o formato de resposta é no formato JSON que já falamos um pouco aqui no blog como acessar informações via Javascript, mas agora usaremos Java, este é o legal de utilizar estes formatos para integração de sistemas, ficamos independente de linguagem. Parando um pouco de enrolação vamos ver um exemplo de retorno de uma requisição a uma das URLs(https://api.instagram.com/v1/tags/cervejadigital/media/recent/?client_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) indentada para facilitar o entendimento.

{  
   "pagination":{  
      "next_max_tag_id":"1401066135218647",
      "deprecation_warning":"next_max_id and min_id are deprecated for this endpoint; use min_tag_id and max_tag_id instead",
      "next_max_id":"1401066135218647",
      "next_min_id":"1408850711681714",
      "min_tag_id":"1408850711681714",
      "next_url":"https:\/\/api.instagram.com\/v1\/tags\/cervejadigital\/media\/recent?client_id=c3f1322f34db4dc8bbbf386532f73a65\u0026max_tag_id=1401066135218647"
   },
   "meta":{  
      "code":200
   },
   "data":[  
      {  
         "attribution":null,
         "tags":[  
            "cerveja",
            "birra",
            "cervejaespecial",
            "fullpintbr",
            "beer",
            "instagood",
            "olut",
            "beergeek",
            "cervejadigital",
            "biere",
            "brejadodia",
            "pivo",
            "beerstagram",
            "cerveza"
         ],
         "location":null,
         "comments":{  
            "count":1,
            "data":[  
               {  
                  "created_time":"1408890792",
                  "text":"Ta sabendo heim !! Top.",
                  "from":{  
                     "username":"diegofioravante",
                     "profile_picture":"http:\/\/photos-a.ak.instagram.com\/hphotos-ak-xaf1\/10608078_1489474217961600_1032687804_a.jpg",
                     "id":"352581790",
                     "full_name":"Diego Team Studio 1"
                  },
                  "id":"794155987601013531"
               }
            ]
         },
         "filter":"Normal",
         "created_time":"1408850711",
         "link":"http:\/\/instagram.com\/p\/sENt3lNTnc\/",
         "likes":{  
            "count":35,
            "data":[  
               {  
                  "username":"fullpintbr",
                  "profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_4967034_75sq_1383496224.jpg",
                  "id":"4967034",
                  "full_name":"FullPintBR"
               },
               {  
                  "username":"flaviozfagundes",
                  "profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_288787283_75sq_1358204915.jpg",
                  "id":"288787283",
                  "full_name":"Fl\u00e1vio Fagundes"
               },
               {  
                  "username":"tomas_hasik",
                  "profile_picture":"http:\/\/photos-f.ak.instagram.com\/hphotos-ak-xfp1\/10311211_701742096555653_153939091_a.jpg",
                  "id":"333632542",
                  "full_name":"Tomas Hasik - Beer Geek"
               },
               {  
                  "username":"craftshack",
                  "profile_picture":"http:\/\/photos-g.ak.instagram.com\/hphotos-ak-xap1\/925917_446556922153766_663491287_a.jpg",
                  "id":"256411806",
                  "full_name":"CraftShack"
               }
            ]
         },
         "images":{  
            "low_resolution":{  
               "url":"http:\/\/scontent-b.cdninstagram.com\/hphotos-xfa1\/t51.2885-15\/10643909_319055608268798_1981051214_a.jpg",
               "width":306,
               "height":306
            },
            "thumbnail":{  
               "url":"http:\/\/scontent-b.cdninstagram.com\/hphotos-xfa1\/t51.2885-15\/10643909_319055608268798_1981051214_s.jpg",
               "width":150,
               "height":150
            },
            "standard_resolution":{  
               "url":"http:\/\/scontent-b.cdninstagram.com\/hphotos-xfa1\/t51.2885-15\/10643909_319055608268798_1981051214_n.jpg",
               "width":640,
               "height":640
            }
         },
         "users_in_photo":[  
 
         ],
         "caption":{  
            "created_time":"1408850711",
            "text":"Cervejaria argentina que, apesar de pertencer ao grupo AB-Inbev, surpreende ao produzir brands suaves mas com alt\u00edssima qualidade.\u00a0Origin\u00e1ria de Rio Negro, sul do pa\u00eds, mais precisamente na patag\u00f4nia argentina, a cervejaria vem produzindo desde 2007 e agora conta com tr\u00eas brands: weisse, amber lager e bohemian pilsner.\n\nEsta Weisse utiliza tanto o l\u00fapulo como o trigo produzido na regi\u00e3o. Uma \u00f3tima cerveja.\n\n#cervejadigital #fullpintbr #biere #beer #beerstagram #beergeek #birra #pivo #olut #instagood #brejadodia #cervejaespecial #cerveja #cerveza",
            "from":{  
               "username":"tafinardi",
               "profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_366708405_75sq_1398893403.jpg",
               "id":"366708405",
               "full_name":"Thiago Alves Finardi"
            },
            "id":"793819761463343856"
         },
         "type":"image",
         "id":"793819760985192924_366708405",
         "user":{  
            "username":"tafinardi",
            "website":"",
            "profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_366708405_75sq_1398893403.jpg",
            "full_name":"Thiago Alves Finardi",
            "bio":"",
            "id":"366708405"
         }
      }
   ]
}

Como resposta temos um objeto que entre seus atributos, o que mais nos interessa agora é o data que é um array de objetos que dentro de cada um representa uma postagem com seus dados como tags, comentários, link, likes, e o mais importante para nós um array imagens que é um array com vários tamanhos de imagem(low_resolution, thumbnail, standard_resolution) que tem a resolução e um link para imagem. Também temos o atributo user com dados da pessoa que postou.

Então antes de fazermos o parse deste documento JSON devemos ler os dados da URL para um String, o que podemos fazer através do método abaixo.

public static String acessarURL( String link){
 
    try {
        URL url = new URL(link);
        URLConnection conn = url.openConnection();
        InputStream is = conn.getInputStream();
        Scanner scanner = new Scanner(is);
        String conteudo = scanner.useDelimiter("\\A").next();
        scanner.close();
             
        return conteudo;
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

Resumindo na linha 4 criamos um objeto URL com o endereço que queremos acessar, na linha 5 abrimos a conexão com este endereço e na linha 6 pegamos o InputStream, na próxima linha é criado um objeto Scanner para ler do InputStream, na linha é definido o delimitador de leitura do Scanner como a expressão regular “\A” para ler até o final do arquivo.

E só lembrando que para este método funcionar dentro de um aplicativo Android este deve ter permissão de acesso a rede, que deve ser colocada no arquivo AndroidManifest.xml.

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

Como já sabemos como pegar as informações do serviço vamos começar a trabalhar em nossa pequena aplicação de exemplo, começamos com o layout do Activity:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Buscar por tag no Instagram"
   />
 
<EditText
    android:id="@+id/pesquisa"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ems="10"
     >
 
    <requestFocus />
</EditText>
 
<Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Buscar"
    android:onClick="buscar"
    />
 
<ListView
    android:id="@+id/listView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
</ListView>

Basicamente é apenas um campo de busca para digitar a tag, um botão e um ListView, onde será listado os resultados.

Neste momento começaríamos a implementar o método buscar no Activity e pensaríamos em chamar o método acessarURL dentro do buscar, mas isso não é uma boa ideia(nem possível), pois o acesso a recursos da rede é algo muito lento e como o método buscar está rodando na UI Thread que se demorar para responder causa o erro ANR(Application Not Responding).

Para realizar qualquer atividade que deve demorar, devemos realizá-la em uma outra Thread. Para facilitar esta atividade o Android fornece a classe AsyncTask que a executa em background em outra Thread. No nosso exemplo criaremos uma classe interna que estende o AsyncTask que irá acessar o rede e popular o nosso ListView como os dados resultantes. Veja como irá ficar.

private class InstagramTask extends  AsyncTask<String, Void, List<Map<String,Object>>  > {
 
    private ProgressDialog dialog;
         
    @Override
    protected void onPreExecute() {
        dialog = new ProgressDialog(MainActivity.this);
        dialog.setMessage("Aguarde...");
        dialog.show();
    }
 
    @Override
    protected List<Map<String,Object>>  doInBackground(String... param) {
        String pesquisa = param[0].trim();
             
        String codigo = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        String link = "https://api.instagram.com/v1/tags/"+pesquisa+"/media/recent?client_id="+codigo;
 
        String conteudo = HttpUtil.acessarURL( link );
             
        List<Map<String,Object>> fotos = new ArrayList< Map<String,Object> >();
             
        try {
            JSONObject jsonObject = new JSONObject( conteudo );
             
            JSONArray resultados = jsonObject.getJSONArray("data");
                 
            for(int i = 0 ; i <10 ; i++){
                JSONObject fotoJSON = resultados.getJSONObject(i);
                     
                Map<String,Object> mapa = new HashMap<String,Object>();
                     
                String foto = fotoJSON.getJSONObject("images").getJSONObject("standard_resolution").getString("url") ;
                String url = fotoJSON.getString("link");
                String username = fotoJSON.getJSONObject("user").getString("username");
                String thumb =  fotoJSON.getJSONObject("images").getJSONObject("thumbnail").getString("url") ;
                      
                     
                mapa.put( "url" , url );
                mapa.put( "username" , username );
                mapa.put( "thumb" , loadBitmap(thumb) );
                 
                fotos.add(mapa);
                     
            }
                 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fotos;
    }
         
         
    protected void onPostExecute(List<Map<String,Object>> fotos) {
        String[] de = {"url"     , "username"    , "thumb"    };
        int[] para =  { R.id.url , R.id.username , R.id.thumb };
             
        MySimpleAdapter adapter = new MySimpleAdapter(MainActivity.this , fotos , R.layout.layout_foto, de , para);
         
        ListView listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);
             
        dialog.dismiss();
    }
         
    public  Bitmap loadBitmap(String url) {
        try {
            URL newurl = new URL(url);
                Bitmap b = BitmapFactory.decodeStream(newurl.openConnection().getInputStream());
                return b;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Vamos então entender um pouco como a classe AsyncTask funciona, ela é uma classe genérica que utiliza três parâmetros

private class InstagramTask extends  AsyncTask<String, Void, List<Map<String,Object>>  > 
  1. String: É o valor que passamos para a AsyncTask processar, no nosso caso é a tag que queremos pesquisar.
  2. Void: É o tipo que representa o progresso, normalmente um Integer que irá representar a porcentagem de progresso. Como não iremos utilizar colocamos Void.
  3. List<Map<String,Object>>: É o retorno da operação que será executada em uma segunda Thread, no nosso caso é um List de Map que iremos utilizar para popular o ListView.

Quando chamamos o método execute da classe AsyncTask uma série de métodos é chamada, vamos velos.

  • onPreExecute – Este método é chamado na UI Thread antes da tarefa ser executada, normalmente é aberto um ProgressDialog para dar uma mensagem para o usuário ficar esperando a tarefa ser completada.
  • doInBackground – este método é chamado em outra Thread, onde o “trabalho pesado” deve ser feito. Ele recebe como parâmetro o 1º valor definido na AsyncTask(no nosso caso String) e retorna o 3º valor definido(no nosso caso – List<Map<String,Object>>).
  • onProgressUpdate – este método é invocado na UI Thread para atualizar o progresso da operação, nós não estamos utilizando.
  • onPostExecute – este método irá ser executado na UI Thread após o método doInBackground ser chamado e ele recebe como parâmetro o próprio retorno do método doInBackground.

Agora vamos analisar melhor o que fazemos em cada um dos métodos.

No método onPreExecute() criamos um objeto ProgressDialog, configuramos uma mensagem para ele, ao chamarmos o método show() será mostrada uma caixa de dialogo para o usuário esperar.

No método doInBackground começamos pegando o valor de parâmetro que irá ser passado para pesquisarmos. Na linha 15 e 16 criamos a URL de consulta concatenando a tag de pesquisa com nosso código de client_id. Na linha seguinte utilizamos o método acessarURL que vimos acima para pegar o conteúdo do endereço e nos retorna em uma String, criamos este método em uma classe utilitária. Esta String contém os dados que queremos exibir e estão no formato JSON e teremos que processá-los para criar um List de Map(que criamos na linha 21) que utilizaremos para exibir no componente ListView.

Para processar um documento JSON iremos utilizar a classe JSONObject que recebe a String que contem o documento JSON a ser convertida como parâmetro do construtor. Como vimos em nosso documento JSON de exemplo retornado por uma requisição, ele é composto de valores armazenado no formato chave/valor, sendo que o valor pode ser outros objeto JSON, ou mesmo um array de objetos JSON. O nosso objeto jsonObject é a raiz de nosso documento e queremos pegar o array de objetos JSON que está armazenado em sua “chave” data, para isso chamamos o método getJSONArray("data") que retorna um objeto JSONArray que contem todos os objetos que estão dentro da chave “data”, iremos percorrer este JSONArray com um laço for e pegar cada cada um dos objetos JSON(linha 29) que representa uma foto e pegar os valores que queremos exibir.

Na linha 31 criamos um objeto Map para armazenar as informações.

Na linha 33 fazemos chamadas encadeadas de método, primeiro chamamos getJSONObject("images") que irá retornar o objeto JSON armazenado com a chave “imagens”, deste objeto chamamos o getJSONObject("standard_resolution") que irá retornar outro objeto JSON e deste chamamos getString("url") que irá devolver o valor String armazenado com a chave “url”. Como vimos temos vários objetos dentro de objetos, consulte o documento de exemplo para melhor visualizar.

Nas linhas seguintes pegamos as demais informações que queremos exibir e nas linhas 39, 40 e 41 colocamos estes valores dentro da estrutura Map. Você deve ter notado que na linha 41 não armazenamos a URL da miniatura da foto, pois queremos exibi-la em um ImagemView que não é capaz de exibir a imagem através de uma URL então temos que baixar a imagem e criar um objeto Bitmap, fazemos isso através do método loadBitmap(linha 66) que recebe um String de URL que utiliza para criar um objeto URL do qual abre uma conexão e pega o seu InputStream utilizando ele para passar para o BitmapFactory que irá lê-lo para criar um objeto Bitmap.
No método onPostExecute(List<map<string,object>> fotos) iremos popular nosso ListView, sendo que cada um dos Map inseridos dentro do List irão virar um item do nosso ListView seguindo o layout do XML abaixo que criamos em res/layout/layout_foto.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
     
    <ImageView
        android:id="@+id/thumb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:gravity="center"
        android:layout_gravity="center"
    />
    <TextView
        android:id="@+id/url"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_margin="10dp"/>
     
    <TextView
        android:id="@+id/username"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_margin="10dp"
        />
</LinearLayout>

Para popular o ListView não podemos utilizar um SimpleAdapter pois como vimos no layout acima temos um ImagemView ao qual devemos atribuir o Bitmap que colocamos dentro do nosso Map, para podermos fazer isso devemos criar nosso próprio Adapter e sobrescrever o método getView(int position, View convertView, ViewGroup parent) que é chamado para cada um dos Map do nosso List como vemos no código abaixo.

public class MySimpleAdapter extends SimpleAdapter {
        private Context mContext;
        public LayoutInflater inflater = null;
        private int resource;
 
        public MySimpleAdapter(Context context , List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) {
            super(context, data, resource, from, to);
            mContext = context;
            this.resource = resource;
            inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }
 
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View vi = convertView;
            if (convertView == null)
                vi = inflater.inflate(resource, null);
 
            HashMap<String, Object> data = (HashMap<String, Object>) getItem(position);
 
            ImageView image = (ImageView) vi.findViewById(R.id.thumb);
            image.setImageBitmap( (Bitmap) data.get("thumb")  );
 
            TextView tvURL = (TextView) vi.findViewById(R.id.url);
            tvURL.setText( data.get("url").toString() );
             
            TextView tvUsername = (TextView) vi.findViewById(R.id.username);
            tvUsername.setText( data.get("username").toString() );
             
            return vi;
        }
}

No construtor do MySimpleAdapter é chamado o construtor da classe pai, guardamos os valores de de context e resource que é o identificador do layout dos itens. Também criamos um LayoutInflater para transformar o resource em um objeto View.

Dentro do método getView é carregado o layout, pegamos o Map(linha 19) com as informações que iremos exibir. Na linha 21 chamamos o método findViewById(E.id.thumb) para pegar o ImageView do view de layout, na linha abaixo adicionamos o bitmap do Map através do método setImageBitmap. Seguimos colocando as informações do Map nos outros views do layout.

Visto por cima o MySimpleAdapter e o layout voltamos aos onPostExecute onde temos dois arrays um de String que representa as chaves do Map e um de int que representa os componente do layout na ordem relativa as chaves do Map. Criamos o Adapter e adicionamo ao ListView, isso irá exibir os dados na tela e podemos esconder o dialog através do dialog.dismiss();.

Agora podemos voltar ao método buscar do nosso Activity que irá pegar o valor da pesquisa e passar para o AsyncTask para processar.

public void buscar(View v){
    EditText edT = (EditText) findViewById(R.id.pesquisa);
    String pesquisa = edT.getText().toString();
         
    InstagramTask task = new InstagramTask();
         
     task.execute(pesquisa);
}

Era isso, espero não ter ficado muito confuso, para ficar mais claro você conferir o exemplo completo.