Acessando a API(webservice) do Instagram pelo Android
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>> >
- String: É o valor que passamos para a
AsyncTask
processar, no nosso caso é a tag que queremos pesquisar. - Void: É o tipo que representa o progresso, normalmente um
Integer
que irá representar a porcentagem de progresso. Como não iremos utilizar colocamosVoid
. - List<Map<String,Object>>: É o retorno da operação que será executada em uma segunda
Thread
, no nosso caso é umList
deMap
que iremos utilizar para popular oListView
.
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étododoInBackground
.
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.