6 passos para consumir uma API REST com retrofit no Android

6 passos para consumir uma API REST com retrofit no Android

O chamado retrofit é a principal ferramenta quando se trata de frameworks de rede/network. Desenvolvido pela Square, seguindo o padrão REST, esse framework é recomendado pelo Google não apenas por ser um framework com muita estabilidade, mas também por disponibilizar um controle de threads automático, aplicar parses de JSON para objetos e também por evitar códigos repetitivos.

Levando em conta todos esses benefícios citados acima e que a grande maioria de nossos aplicativos possuem algum tipo de comunicação com a internet (seja o Facebook, Instagram ou Twitter para atualizar o feed, nos mostrar novos stories etc) esse se torna um dos frameworks fundamentais para todo desenvolvedor Android.

Introdução

Para botar em prática tudo o que vamos aprender neste artigo, iremos consumir uma API da NASA chamada APOD - Astronomy Picture of the Day. Esta API mostra uma foto que foi tirada do céu para cada dia desde junho de 1995. No nosso aplicativo, o usuário poderá digitar uma data e descobrir a foto que foi tirada no dia requerido. Se você preferir acompanhar o código da sua própria máquina, pode seguir o link do repositório do projeto no meu GitHub.

Mas afinal, o que é uma API REST?

API ou Application Programming Interface é uma interface de um sistema que permite que outros sistemas acessem dados e funcionalidades, sem a necessidade de detalhes da implementação do software deste sistema. Já a sigla REST significa: Representational State Transfer (Transferência Representacional de Estado), ou seja, um estilo de arquitetura de software que define um conjunto de restrições a serem usadas para a criação de web services.

Passo 1: Configurando o Projeto


Importando dependências

Para começar, precisamos importar as dependências. Para isso, abriremos nosso arquivo app/build.gradle no Android Studio e importaremos três dependências como na imagem abaixo. As dependências das linhas 39 e 40 podem ser encontradas no link do Retrofit do GitHub, enquanto a dependência da linha 41 fica no square okhttp.


A primeira dependência da linha 39 disponibilizará todas as funcionalidades referentes ao retrofit, já a segunda na linha 40 diz respeito à conversão de arquivos JSON vindos da API para Objects no nosso aplicativo e, por último, a terceira dependência diz respeito a todas requisições que faremos utilizando requisições HTTP.

Esses Headers citados acima são utilizados em diversos casos, como veremos mais adiante. Algumas APIs utilizam uma chave que deve ser enviada durante a requisição para validar a permissão de consumir a API em questão.

Na imagem anterior temos dois pontos de atenção: o primeiro, como podemos notar na linha 40, está informando que existe uma versão mais atualizada dessa dependência. Para resolver isso, é só passar o mouse em cima dessa linha e o Android Studio dará a opção da versão mais atualizada. O segundo ponto é que, sempre que adicionarmos novas dependências, temos que clicar no botão Sync Now na barra superior azul. Assim que nossas dependências estiverem atualizadas poderemos acessar todas as funcionalidades de cada uma delas.

Adicionando a permissão de internet


Você já deve ter visto que, ao abrir um aplicativo pela primeira vez, algumas permissões são pedidas a você, tais como: câmera, localização, status da bateria etc. Em nosso aplicativo não é diferente, precisamos adicionar uma permissão para que o app consiga se comunicar com a internet. Abra o AndroidManifest.xml e em seguida adicione a tag uses-permission, como na imagem abaixo:

Passo 2: Adicionando um Interceptor e Criando o Retrofit Client

Nesse passo, iremos criar uma classe responsável por configurar nosso retrofit no Design Pattern Singleton para garantir que sempre iremos usar a mesma instância do retrofit no momento de cada requisição.

Adicionando um Interceptor na requisição

Para começar, iremos criar uma classe que estende o interceptor, e esta será responsável por adicionar a chave que é pedida pela API no header de nossa requisição. Para isso, é necessário implementar a função intercept.


Dentro do override da função intercept, eu criei uma variável para adicionar os parâmetros que serão acrescentados ao URL base da requisição. Dentro do “.addQueryParameter” da imagem abaixo são pedidos dois valores: value e name. Os valores pedidos são os que serão concatenados no URL em toda requisição que nosso app fizer. No caso da API que estamos consumindo, o value se chama “api_key”, como pode ser verificado na documentação da API, e o name é uma chave aleatória que é gerada no site das APIs da Nasa.

Em seguida, montamos a request passando a variável que criamos como parâmetro do .url() e por fim, damos um return com chain.proceed(request)

Ao final do processo a classe ficou assim:

É importante dizer que você pode se deparar com APIs que não pedem para que se adicione algum query parameter, mas como boa parte das que encontramos por aí pedem, achei de bom grado adicionar.

Os query parameters podem ter uma variedade de funcionalidades, como por exemplo: alterar o idioma do retorno da requisição. Essas opções ou obrigatoriedades sempre são apresentadas na documentação da API que você está consumindo, sendo que alguns dos query parameters (como veremos adiante) podem ser passados em outro momento.

Criando o Retrofit Client

Para começar, iremos criar uma classe chamada RetrofitClient e em seguida um companion object (que é um singleton da classe). Dentro deste companion object iremos criar uma variável constante que guardará o valor do nosso URL base, que é aquele que sempre vai se repetir na maioria das requisições. Além disso, nossa URL base precisa terminar com uma barra ( / ) no final.

Por exemplo:

Se eu quiser fazer uma requisição buscando todos os filmes de uma API de filmes e séries para meu aplicativo de filmes, usarei o URL base: apiDeFilmesESeries/filmes/.

Se eu quiser buscar os filmes que serão lançados este ano, no momento da requisição adicionarei apiDeFilmesESeries/filmes/upcoming; agora, se eu quiser um filme em específico, adicionarei ao URL base /FilmeESpecifico, ficando assim: apiDeFilmesESeries/filmes/FilmeESpecifico

Claro que esse é um exemplo abstrato, mas você consegue perceber que sempre que fazemos uma requisição em nossa API fictícia o URL base se repete e o que muda é somente o que estou requisitando na API? Dessa forma você consegue identificar seu URL base.

Ok, agora vamos deixar o URL base de lado, por enquanto. Vamos criar uma função responsável por nos prover uma instância de OkHttpClient com o interceptor que criamos adicionado. A função pode ser definida como private e o retorno será um OkHttpClient.Builder. Agora é só dar um return como na imagem abaixo, passando o APIkeyInterceptor() como parâmetro de .addInterceptor. Desta forma:

Vamos criar uma lateinit var que será uma instância de retrofit para utilizarmos depois. Agora vamos criar uma função privada chamada de getRetrofitInstace() por que ela literalmente irá disponibilizar uma instância de retrofit com todas as configurações necessárias.

Criamos uma variável chamada httpClient que chama nossa função httpBuilder() e em seguida fazemos um if para verificar se a nossa lateinit var criada anteriormente já foi inicializada - dentro do if perceba o uso da negação com “!” - pois se você não se atentar e acabar deixando passar esta negação, toda vez que utilizar essa função o código criará uma nova instância do retrofit.

Ok, agora já dentro do if vamos inicializar nossa lateinit var para configurarmos toda nossa instância de retrofit. Para isso, vamos passar dentro de .baseUrl a base url que criamos anteriormente; em seguida, passaremos dentro do .client nossa variável httpClient com um .Build e no .addConverterFactory passaremos um GsonConverterFactory.create(). Temos acesso a esse GsonConverterFactory por conta da importação da dependência que fizemos no passo 1, e como ele não precisa de nenhuma configuração a mais, podemos passá-lo direto.

Agora vamos criar uma função genérica para permitir que todas as classes que precisem utilizar nossa instância do retrofit consigam fazê-lo sem problemas. Para isso, é só abrir o maior e menor que (< >) e dentro colocar uma letra que irá simbolizar o que será passado. Como parâmetro, precisamos receber uma classe e esta será genérica. Com a mesma letra que utilizamos no começo, o retorno desta função será um generics da classe que passamos como parâmetro. Em seguida é só dar um return contendo a função getRetrofitInstace().create() passando como parâmetro do .create a classe que recebemos como parâmetro.

Passo 3 : Mapeando a API na Model

Agora precisamos mapear os dados que queremos receber da nossa API. Vamos acessar o link da API que estamos consumindo https://apod.nasa.gov/planetary/ e faremos uma requisição direto no navegador montando a requisição à própria mão em https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=1999-7-12 .
Repare que, comparada ao URL base, o que foi adicionado foi o nome “apod” pedido pela API, uma api_key e uma data simulando a data que usuário vai inserir no app; agora, é só jogar esse link em um navegador para vermos o resultado. Eu recomendo usar o Firefox por formatar o arquivo JSON sem a necessidade de uma extensão, como é o caso no Google Chrome, por exemplo.

Pronto. É dessa forma que os dados nos são apresentados quando fazemos uma requisição. Podemos notar que à esquerda, em azul, estão os campos, e à direita, seus respectivos valores. Por exemplo: no campo “date” temos o valor “1999-07-12”.

Agora que identificamos os campos, tempos que representá-los em nosso código, para que o aplicativo entenda como receber os dados vindos da API. Para isso, criamos uma data class chamada NasaModel, selecionamos os campos que são do nosso interesse e os guardamos em variáveis com seu respectivo tipo. Agora sempre que recebermos dados da API podemos os colocar dentro desse “molde”.


Passo 4: Construindo o Service

Nesta etapa iremos construir o Service. Essa interface será responsável por ditar o verbo HTTP a ser utilizado para se fazer a requisição e também por adicionar parâmetros na requisição, caso necessário. Como nosso aplicativo irá precisar passar uma data na requisição, aprenderemos como fazer isso também.

Na descrição da API podemos descobrir o verbo HTTP à esquerda da imagem que deveremos utilizar nas nossas requisições. Podemos também identificar o nosso URL base e em sua extremidade o nome “apod”; essa é uma adição ao nosso URL base que faremos no momento da requisição em nosso Service. Vamos ao código.


Agora é só criar uma interface e dentro desta iremos criar uma função. Vamos usar a anotação @GET e dentro passaremos o valor “apod” visto anteriormente. Em seguida, criaremos uma função sem corpo. Vamos abrir o parêntese e adicionar a anotação @Query e abrir parênteses novamente.

A anotação @Query anexa um parâmetro de consulta ao URL. Esta anotação pede dois parâmetros: um value, que representa o nome do parâmetro a ser adicionado à requisição, e outro valor booleano chamado encoded, que é codificado por URL.

Após o valor do encoded podemos fechar o parênteses e vamos adicionar um parâmetro que será recebido pela função que criamos. No meu caso, esse parâmetro se chamará query e será do tipo String.

O parâmetro que recebemos pela função é concatenado com o valor que passamos dentro da anotação @Query, montando a URL como montamos manualmente anteriormente. A imagem abaixo mostra como isso funciona de forma mais visual:


Nossa base URL é concatenada com o valor que inserimos na anotação @GET em nosso parâmetro, a nossa API Key que é passada através do interceptor que criamos no passo dois vem em seguida, como citado: são inseridos um name e um value respectivamente. Em seguida, a nossa anotação @Query entra em ação, concatenando o valor que inserimos em seu parâmetro e por fim, vem o valor que será passado no parâmetro da função.

E finalmente chegando ao retorno da nossa função, o retorno do tipo “Call” citando a própria documentação do retrofit é “Uma invocação de um método Retrofit que envia uma solicitação para um servidor web e retorna uma resposta. Cada chamada produz seu próprio par de solicitação e resposta HTTP.” Fonte: https://square.github.io/retrofit/2.x/retrofit/retrofit2/Call.html

Passo 5: Fazendo a requisição com nosso remote data source

Vamos criar uma classe contendo a função que será responsável por executar de fato a chamada para a API. Para isso, precisamos instanciar nosso RetrofitClient, e a partir desta instância acessar a função createService e passar por parâmetro nossa classe de serviço NasaService. Desta forma:

Agora iremos à função. Ela terá três parâmetros: o primeiro da imagem abaixo, chamado “userDate”, é a data que o usuário passará em sua Activity. O segundo parâmetro é uma função que será executada se nossa função for bem sucedida e esta função recebe uma NasaModel como parâmetro, seu retorno será um Unit e o terceiro parâmetro também é uma função que será chamada, caso a nossa requisição for mal sucedida, com o retorno Unit também.

As duas funções, onSuccess e OnFailure, são muito importantes neste contexto por que existem dois cenários com possibilidades distintas, por exemplo: se tivéssemos definido o retorno da função getDataFromUserDate como um NasaModel teríamos problemas em retornar os casos de erro que são uma String e que não contêm todos os campos que a nossa model contém. Assim se sucederia caso tentássemos definir o retorno como uma String. Além de outros erros possíveis ao tentarmos fazer isso.

Vamos criar uma variável do tipo Call<NasaModel> e em seguida vamos atribuir o valor dessa variável à chamada da nossa função do service através da instância de RetrofitClient criada anteriormente, passando por parâmetro o userDate que recebemos nesta função do RemoteDataSource.

A classe Call nos permite utilizar duas funções para executar a requisição, sendo elas: call.execute(), uma função síncrona, que executa a requisição na MainThread, o que nos resultaria uma NetworkOnMainThreadException: Você pode ver mais detalhes dessa exceção neste link.

Podemos utilizar a função call.enqueue(), que é uma função assíncrona, o que não gera um exception como a função execute(). Será esta que iremos utilizar, a função pede como parâmetro um CallBack<T>, que é uma interface com dois métodos a serem implementados, onResponse e onFailure, como na imagem a seguir:

Uma coisa que temos que levar em consideração sobre a função onResponse e está escrito em uma nota na documentação desta função, é que: uma resposta HTTP ainda pode indicar uma falha no nível do aplicativo, como uma resposta 404 (page not found). Para verificarmos se a resposta é bem sucedida é recomendado que utilizemos o isSuccessful, que verifica se o status code da requisição está em um range de 200 a 300.

Tendo em vista os fatos supracitados, vamos fazer um if no nosso response e adicionar o isSuccessful para verificar se nossa resposta foi bem sucedida em seguida acessamos o corpo da requisição através do método response.body()? com um ponto de interrogação como um null checker e fazemos um let para só executar o código dentro do bloco caso a response não seja nula; agora iremos chamar a função onSuccess que criamos no parâmetro desta mesma função utilizando o .invoke, passando por parâmetro o valor do nosso lambda, que eu renomeei como nasaModel. Em nosso else vamos chamar nossa função onFailure.invoke, e no parâmetro acessamos o corpo do erro da requisição transformando-o em uma string, com o toString().

Agora, como podemos ver na próxima imagem do nosso onFailure implementado do Callback, iremos acessar o método message através do throwable, que é a classe base de todos os erros e exceções, e traz os detalhes em forma de string do nosso erro ou exceção. Daí é só usar o invoke visto anteriormente e passar nossa stringMessage através do parâmetro.

Passo 6: Transmitindo os dados para a ViewModel e View

Para consumir nossos dados na View e nosso usuário finalmente conseguir utilizar o aplicativo, antes iremos transmitir esses dados para nossa ViewModel para que nossa View fique somente responsável por coisas referentes às Activities. Para isso, vamos criar nossa ViewModel, que vai estender AndroidViewModel, recebendo e repassando no parâmetro de AndroidViewModel uma Application.


Agora vamos às variáveis. Precisamos de uma instância de nosso RemoteDataSource para que seja possível acessar os métodos da classe e assim fazer a requisição.  Criaremos um LiveData que receberá um MutableLiveData; o LiveData será observado pela view e apresentará os resultados na tela sempre que houver uma alteração.

Em seguida, vamos criar uma função para chamar a função do RemoteDataSource que faz a requisição, através da instância que criamos. Também implementamos os métodos onSuccess e onFailure, passamos a data que é recebida pela view por parâmetro e para implementarmos onSuccess e onFailure é só abrir o lambda e dentro do lambda atribuir o valor ao nosso MutableLiveData. Como podemos ver, existem duas maneiras de atribuir o valor ao MutableLiveData; fica a seu critério escolher se será utilizado o postValue ou o .value. Agora é só criar uma função na view para observar estes valores e desenhar os campos na tela.

Observando os dados na MainActivity

Na view criei uma função chamada “observe()” e através de uma instância da ViewModel podemos acessar o LiveData e chamar a função observe dele, passamos um contexto e implementamos esse Observer que é uma interface com um método chamado void onChange(T t); esse método é chamado toda vez que há alguma mudança em nosso LiveData.




Na imagem a seguir estamos montando a tela para o usuário, repassando tudo que foi recebido através da nossa requisição para a tela. Não tem muito segredo: é só acessar os campos que foram construídos no XML e atribuir os valores recebidos da API a eles.

E, por fim, como podem ver nesta imagem, dentro da função “observe()” também criei um Observer para mostrar uma mensagem de erro através de um Toast, caso haja falha em alguma de nossas requisições. Agora é só rodar o aplicativo e ver como ficou.


Conclusão

E é assim que o app ficou: o usuário selecionou uma determinada data e o nosso aplicativo fez toda a requisição na API e o mostrou na activity conforme a distribuição de layout:


O objetivo deste artigo foi abordar uma forma simples mas concisa do que é um consumo de API com retrofit no Android. Existem algumas técnicas que envolvem outras tecnologias como Coroutines, por exemplo. Contudo, a maneira apresentada neste artigo traz para você algo essencial, recomendado pelo Google e muito bem visto pelo mercado. Que tal fazer uma pokédex ou seu próprio aplicativo sobre o clima? Com essa nova habilidade você poderá fazer aplicativos incríveis com APIs públicas e privadas que existem por aí!

No próximo artigo partiremos deste e iremos abordar o que é injeção de dependência e como podemos mitigar as dependências deste aplicativo utilizando Hilt, até breve!