Consumindo API REST no Android com Retrofit em Kotlin — Parte 1
No artigo onde criamos o Ceep, implementamos uma lista básica utilizando o Recycler View e usamos uma lista de objetos fixa na App para apresentar cada um dos itens.
Como sabemos, atualmente é muito comum desenvolvermos Apps que funcionam tanto offline como online, por exemplo, consumindo APIs REST.
Sendo mais breve, neste artigo veremos como realizamos requisições HTTP na CRUD API que criamos com o Spring Boot. Para esse primeiro contato, o nosso objetivo será pegar todas as notas do servidor! 😊
Quer aprender mais sobre Kotlin tanto no mundo mobile como no back-end? Então confira este agregador de conteúdo onde listo todos os conteúdos que escrevi de Kotlin e os que serão publicados mais pra frente 😉
Decidindo a API para realizar as requisições HTTP
Um dos grandes passos para começarmos com a implementação é justamente decidir a API que iremos utilizar para realizar as requisições.
A princípio, poderíamos utilizar a API do Java HttpURLConnection
para estabelecer a comunicação HTTP com a API REST. Entretanto, ela exige muita configuração manual que acaba deixando essa tarefa um tanto quanto não produtiva.
Visando uma implementação mais objetiva e muito comum na comunidade Android, vamos utilizar uma lib que vai facilitar esse processo de realização de requisições, ou seja, o Retrofit.
Além da fama na comunidade Android, existem diversas razões para considerarmos o uso do Retrofit ao invés do modo nativo, dentre elas temos:
- Serialização de objetos: não precisamos fazer essa tarefa manualmente
- Configuração: configuramos apenas o necessário para estabelecer a comunicação
- Usabilidade: parece até engraçado, mas veremos como é bem mais fácil realizar requisições com o Retrofit
Lembrando que o Retrofit não é restrito para uso no Android, você pode utilizá-lo em server-side seja Java ou Kotlin 😃
Preparado para começar? Let’s go!
Preparando o ambiente
O nosso primeiro passo para utilizar o Retrofit é colocando a dependência dele no nosso projeto:
dependencies {
// restante das dependências
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
}
Pronto, já podemos utilizá-lo!
Criando a classe de configuração
Agora vamos criar a classe que vai ficar responsável em realizar a configuração de inicialização do Retrofit pra gente, portanto, criaremos a classe RetrofitInitializer
:
Existem outras convenções para criar esta classe, como por exemplo,
RetrofitConfig
,WebClient
ou até mesmo via injeção de dependência… Fique à vontade em nomear ou configurar da maneira que achar mais adequado
Começando o processo de configuração do Retrofit
Em seguida, vamos criar a função init()
que vai ficar responsável em inicializar o Retrofit:
Dentro dela, precisamos criar uma instância de um Builder do Retrofit que se trata de uma classe com a responsabilidade de construir um objeto do tipo Retrofit
pra gente:
Definindo uma URL base
Com o Builder em mãos, uma das primeiras configurações que consideramos, é a indicação de uma URL base por meio da função baseUrl()
:
Veja que adicionei o endereço http://192.168.0.23:8080. Ele indica exatamente o meu IP dentro da rede onde estou, ou seja, dessa forma estarei indicando que vou acessar o localhost na porta 8080. Neste momento você pode estar pensando:
“Se pretende utilizar o localhost, porque não deixou o endereço http://localhost:8080, assim como acessamos a API?”
Peculiaridade ao definir a URL local da API em Apps Android
De fato essa é uma dúvida bem comum e a resposta pra ela é fácil. Se acessarmos o localhost da App, mesmo que seja o emulador, estaremos acessando o endereço do celular!
Em outras palavras, o localhost tanto do celular como do emulador, são diferentes do computador!
Isso significa que ao acessarmos o IP do computador diretamente, mesmo que rode no celular físico, sempre estaremos acessando diretamente a API. Claro, desde que todos os dispositivos estejam na mesma rede.
Caso tenha ficado em dúvida sobre o que seja uma URL base, de forma objetiva, é a URL que vai ficar fixa em todas as requisições que forem feitas.
Isso significa que da forma como configuramos, sempre que for realizada uma requisição, a URL vai sempre começar com o valor http://192.168.0.23:8080.
Configuração do conversor de objetos
Agora que realizamos a primeira configuração, o próximo passo é indicar o conversor de objetos que será utilizado, para isso chamamos a função addConverterFactory()
:
Entretanto, como a ideia do Retrofit é automatizar esse processo, a própria documentação nos sugere alguns conversores já prontos:
- Gson:
com.squareup.retrofit2:converter-gson
- Jackson:
com.squareup.retrofit2:converter-jackson
- Moshi:
com.squareup.retrofit2:converter-moshi
- Protobuf:
com.squareup.retrofit2:converter-protobuf
- Wire:
com.squareup.retrofit2:converter-wire
- Simple XML:
com.squareup.retrofit2:converter-simplexml
Nenhum dos conversores vem por padrão com o Retrofit, ou seja, precisamos adicioná-los via dependência também. Fique à vontade em usar a lib que preferir.
Caso esteja com dúvida na escolha de um conversor por questões de performance ou até mesmo personalização, recomendo que faça uma pesquisa para entender as diferenças entre eles, ou então, você pode implementar o seu próprio conversor seguindo as instruções da documentação.
Adicionando o conversor do GSON
Dentre as possibilidades, vou utilizar o GSON por ser uma lib comum na comunidade Android. Para adicioná-lo, basta apenas inserir a dependência:
dependencies {
// demais dependências
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
}
Após finalizar o processo de build, precisamos apenas enviar como parâmetro do addConverterFactory()
a chamada estática da função create()
da classe GSONConverterFactory
:
Pronto, configuramos o conversor, bem fácil, né? Por mais incrível que pareça, já realizamos as configurações necessárias para usar o Retrofit!
Construíndo o objeto do Retrofit
Depois de finalizar a configuração do Builder, podemos construir o objeto da classe Retrofit
com a função build()
:
Agora que temos um objeto do Retrofit
, podemos seguir para o próximo passo que é criar um Service que vai representar a requisição HTTP.
Implementando o Service do Retrofit
Para implementarmos um Service do Retrofit, criamos interfaces indicando o que o Service vai representar, como por exemplo, qual recurso ele vai consumir ou lidar.
Já que o nosso recurso é uma nota, podemos criar a interface NoteService
:
Dentro dela, precisamos indicar quais requisições serão representadas, ou melhor, os endpoints.
No nosso caso o nosso objetivo inicial é realizar uma requisição para pegar todas as notas do servidor, sendo assim, inicialmente criamos a assinatura de função list()
:
Em seguida, veja que ainda não estão claras algumas informações da requisição HTTP, como por exemplo, se é um GET ou POST, ou então, qual URL será chamada…
Como sabemos, o nosso endpoint está retornando todas as notas para a requisição GET na URL “/notas” e para indicarmos essa nossa intenção, adicionamos a annotation @GET
enviando o parâmetro "notes"
:
Pronto, configuramos o nosso Service, ou seja, já podemos utilizá-lo!
Começando com o processo da requisição
Agora que temos um objeto do Retrofit
e um Service, podemos realizar a chamada dentro da Activity. Portanto, começaremos pela instância da classe e RetrofitInitializer
:
Em seguida, precisamos do nosso Service para que seja possível a realização da requisição.
Entretanto, para conseguirmos um objeto do tipo Service que seja vinculado à configuração que realizamos no Retrofit, precisamos ter acesso ao objeto retrofit
que criamos na RetrofitInitializer
.
Criando a instância do Service
Em outras palavras, precisamos criar uma função dentro do RetrofitInitializer
que vai nos devolver uma instância do NoteService
:
Para criarmos a instância do NoteService
vinculada à configuração que fizemos do Retrofit, precisamos ter acesso ao objeto retrofit
que está dentro da função init()
.
Sendo mais objetivo, faz todo o sentido transformarmos o retrofit
em uma property que vai ser acessível por todos os membros da classe, pois, se amanhã surgir mais Services, não teremos problemas em criá-los:
“Existem outras técnicas comuns durante a criação do objeto
retrofit
, entretanto, por ser um artigo mais introdutório, não entrarei nessa discussão. Caso tiver dúvidas, fique à vontade em perguntar.
Agora, podemos criar a instância do NoteService
por meio da função create()
do retrofit
enviando a referência de classe da interface:
Repara que estamos apenas devolvendo o retorno da função create()
, ou seja, quando temos esse tipo de comportamento no Kotlin, podemos resumir mais ainda o código!
Single-Expression function
A técnica para a redução destes casos, trata-se de uma chamada de função em uma única linha, tal feature é conhecida como Single-Expression function:
Inclusive, nesse tipo implementação, o retorno explícito se torna opcional:
Bem mais simples, concorda?
Existe uma outra técnica que nos permite “reduzir” mais ainda essa chamada transformando em property e modificando o
get()
. Não entrarei no assunto, mas caso queira saber como fica, é só avisar 😄
Chamando a função do Service
Pronto, temos o nosso Service disponível, basta apenas chamarmos a função list()
do NoteService
na Activity:
Por mais que chamemos a função list()
que representa a requisição HTTP, o Retrofit utiliza uma entidade responsável pelas chamadas de requisições, conhecida como Call
.
Representando a chamada das requisições
Sendo assim, ao invés de vazio (Unit
) vamos retornar uma Call
na função list()
:
Porém, a Call
precisa saber com qual tipo de informação ela vai lidar.
“Como assim?”
Lembra que mencionei que o Retrofit já faz a serialização dos objetos de maneira automática? Então, é na Call
que indicamos o objeto que será serializado durante a requisição, ou seja, uma lista de objetos do tipo Note
.
Indicando o objeto que vai ser serializado durante a requisição
Para indicarmos essa informação, podemos enviar o parâmetro List<Note>
via generics:
Agora, na Activity, podemos pegar a nossa Call:
Com a call
em mãos, podemos realizar a requisição utilizando duas funções:
execute()
: faz uma chamada síncronaenqueue()
: faz uma chamada assíncrona
Por se tratar de um processo que pode demorar (envolve questões de comunicação com o meio externo via rede), o próprio Android não permite que requisições síncronas sejam realizadas na UI Thread, isto é, precisamos realizar uma requisição assíncrona com a função enqueue()
:
Entretanto, a chamada da função enqueue()
exige uma implementação da interface Callback
. Neste momento você pode estar pensando:
“O que é um callback?”
Basicamente, callbacks são chamadas de volta de uma requisição, em outras palavras, quando uma requisição finaliza, o callback é chamado para tomarmos uma ação.
“Bacana, mas como posso implementar um callback?”
Uma alternativa seria criar uma classe e implementar a interface Callback
, porém, como sabemos, no Java temos a capacidade de criar classes anônimas para implementar interfaces.
Implementando interfaces com classes anônimas
No Kotlin, conseguimos o mesmo resultado a partir da feature Object Expressions:
Repara que adicionamos o parâmetro object: Callback<List<Note>?>
para indicar qual interface estamos implementando, e então, dentro do escopo do Object Expression, foram declaradas duas funções da interface Callback
:
onResponse()
: quando a comunicação com o servidor é realizada e ele nos devolve uma respostaonFailure()
: quando a comunicação com o servidor não acontece ou quando ele apresenta uma resposta de falha
De modo resumido, quando as requisições derem certo, o onResponse()
será chamado e, quando falharem, o onFailure()
será acionado. Simples, né?
Sendo assim, vamos implementar as funções do callback para tomar uma ação.
Implementando as funções do callback
A primeira delas será aonResponse()
, dentro dela, vamos pegar o parâmetro response
(que representa a resposta HTTP) e pedir o objeto devolvido pelo corpo da requisição por meio da função body()
:
Veja que neste momento, o Kotlin indica que existe a possibilidade do response
ser null
, ou seja, podemos aplicar a estratégia do let
que vimos lá na implementação do RecyclerView:
Neste momento retornamos o objeto notes
que representa a nossa lista de notas. E tudo indica que temos uma lista de notas segura, certo? Só que não…
Cuidados com a resposta do callback
Pois é, veja o que acontece quando colocamos o tipo da variável notes
no modo explícito:
Temos uma lista que pode ser null
! Portanto, podemos modificar essa chamada utilizando uma chain na Safe Call:
E agora temos uma lista segura!!! 😄
Computando o RecyclerView com a resposta do callback
Agora que temos a lista de notas, precisamos apenas modificar a forma como estamos criando a lista do RecyclerView para que receba as notas recebidas pelo onResponse()
.
Para que o código fique mais legível, vamos extrair a função configureList()
que vai receber uma lista de notas e vai manter todo o código que configura o RecyclerView:
Veja que ao invés de receber a função notes()
(que nos devolvia notas criadas dentro da App), agora estamos recebendo as notas por meio do parâmetro notes
, portanto, podemos remover a função notes()
. E então, basta apenas chamar a função dentro do let
:
Pronto, implementamos o onResponse()
, agora basta apenas implementar o onFailure()
, pois se ocorrer alguma exception durante a requisição, ele que vai ficar responsável em tomar uma ação.
Para este exemplo, vamos imprimir a mensagem do parâmetro t: Throwable
em log:
Se preferir tomar mais ações, como por exemplo, avisar o usuário com um Toast, fique à vontade.
Adicionando permissões para acessar a internet
Já que a requisição exige uma conexão via rede, precisamos pedir a permissão para o Android. Para isso, inserimos o seguinte código no AndroidManifest.xml:
Com toda implementação feita, podemos executar a nossa App:
Opa! Agora sim em! Nossa App está se comunicando com a nossa API e buscou todas as notas! 😉
Código fonte
Caso tenha dúvida em relação à implementação, fique à vontade em consultar o código fonte que deixei no repositório do GitHub.
Claro, aproveite e pergunte nos comentários também!
Para saber mais
Conseguimos realizar a requisição com o Retrofit, entretanto, ainda existem pontos pelos quais precisamos ficar atentos, como por exemplo:
- Requisição dentro da Activity: como sabemos, não faz parte da responsabilidade da Activity realizar uma requisição
- Implementação do Callback: atualmente escrevemos muito código para implementar o callback, algo que pode prejudicar a leitura do código
Pensando justamente nesses pontos, logo mais veremos técnicas que lidam com esse tipo de cenário.
Além disso, fizemos apenas um GET no servidor, sendo que temos a capacidade de realizar um CRUD!
Não se preocupe! Implementaremos a funcionalidade de inserção das nossas notas, como também, veremos uma técnica que permite desacoplar entidades no Android como é caso do callback dentro da Activity.
Em outras palavras, deixo a segunda parte do artigo de Retrofit com Kotlin:
Conclusão
Neste artigo aprendemos a realizar requisições HTTP com o Retrofit em uma App Android utilizando o Kotlin. E como foi visto, o Retrofit facilita e tanto a nossa vida neste tipo de tarefa.
Também vimos que existem diversas peculiaridades, como por exemplo, adicionar um conversor, implementar um Service, chamada síncrona assíncrona e a utilização de um callback.
Por fim, vimos que nesse tipo de tarefa temos que pedir as permissões de acesso a rede para o Android.
E aí, o que achou do Retrofit? Compartilhe comigo sua experiência durante a implementação ou, se tiver alguma sugestão, é muito bem vinda também 😄