Consumindo API REST no Android com Retrofit em Kotlin — Parte 2

No artigo onde implementamos o Retrofit para buscar todas as notas do servidor, fizemos uma configuração e utilização básica do Retrofit, ou seja, além de não realizar as outras tarefas de CRUD, no caso, inserção, alteração e remoção, deixamos o código um tanto quanto não apropriado para uma App.

Em outras palavras, neste artigo vamos melhorar o nosso código atual, e também, implementaremos a funcionalidade de enviar uma nota para a API. Bora começar?

Menino falando yep

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 😉


Analisando o código

Atualmente a implementação do callback está dentro do onCreate() da Activity:

Código atual do onCreate() da NoteListActivity

Uma responsabilidade um tanto quanto inapropriada para essa entidade da App. Sendo assim, o nosso objetivo inicial é isolar esse callback em um lugar específico.

Delegando a responsabilidade para uma entidade específica

Uma das técnicas que podemos aplicar, é criando uma classe específica para lidar com requisições de notas, como por exemplo, um Web Client.

Sendo assim, vamos criar a classe NoteWebClient e, dentro dela, manderemos todo o código que faz a requisição para a função list():

Código que vai ficar responsável em realizar as requisições e lidar com callbacks

Então, lá na Activity, podemos chamar essa função:

Chamando o Web Client dentro do onCreate()

Entretanto, observe que da maneira que está, temos 2 problemas:

  • A função configureList() precisa ser chamada dentro da Activity.
  • A configureList() pode ser chamada apenas quando o callback entrar na função onResponse().

Pensando justamente neste cenário, como podemos implementar um comportamento dentro da Activity que vai ser executado no momento que o callback do Web Client for acionado?

Enviando uma interface via parâmetro

Uma das técnicas comuns, até mesmo em desenvolvimento Java, é enviar uma interface que vai ficar responsável em avisar a Activity quando, por exemplo, o callback responder.

“Mas como isso funciona na prática?”

Basicamente, podemos criar a interface chamada NoteResponse que vai conter a assinatura success() responsável em apenas receber uma lista:

Função que vai receber a lista de notas

Então, declaramos que a list() do Web Client, recebe a interface NoteResponse e, ao invés de chamar a função configureList(), chamamos a success() enviando a lista notes:

Recebendo a interface NoteResponse e executando a função success() após conseguir a lista de notas

Dessa forma, na Activity, somos capazes de implementar a interface da mesma maneira quando usamos classes anônimas do Java, como por exemplo, em listeners. Mas agora, vem a questão:

“Como implementamos uma classe anônima no Kotlin?”

Implementando interfaces com o Object Expression

Para obtermos o mesmo resultado de implementação de classes anônimas do Java no Kotlin, utilizamos a feature Object Expression:

Implementando interface NoteResponse com Object Expression

Então veja que agora sobrescrevemos a função success() quando é chamada de dentro do list().

Em outras palavras, todo o código que implementarmos dentro deste escopo, vai ser executado apenas quando o onResponse() chamar a função success().

Isso significa que o parâmetro notes é a lista que recebemos do servidor!

Portanto, podemos chamar a função configureList() dentro da nossa implementação:

Chamando a função da Activity dentro do Object Expression

Testando a App, vemos que ainda temos o mesmo resultado, ou seja, o processo de delegar a responsabilidade deu certo! 😃

Essa solução que apresentei é conhecida como o Design Pattern Delegate.

Portanto, daremos continuidade na App fazendo com que ela insira uma nota na API também.

Começando com a implementação de envio de nota para a API

Para que seja possível o envio de notas precisamos, de alguma forma, realizar os seguintes passos:

  • Criar um componente visual para iniciar o processo.
  • Apresentar um formulário para o usuário.
  • Declarar a requisição para envio da nota.
  • Execução da requisição e atualização da lista de notas.

Considerando todos esses pontos, vamos implementá-los passo a passo.

Criando o componente para permitir a adição

Um componente bem comum para indicar que existe uma possibilidade de inserção é o FloatActionButton.

Porém, ele não vem por padrão no projeto, ou seja, adicionaremos a lib de suporte de design do Google por meio da seguinte dependência:

dependencies {
// demais dependências
implementation 'com.android.support:design:26.0.2'
}
Estou adicionando a versão 26.0.2 para manter a mesma compatibilidade com as outras libs de suporte da Google, ou seja, tente sempre usar a mesma versão para essas libs.

Após sincronizar com o Gradle, lá no layout, podemos adicionar o FAB (FloatingActionButton) da seguinte maneira:

Adicionando FAB no layout da Activity
FAB adicionado no layout

Veja que a App apresenta o FAB, porém, é muito comum vermos este tipo de botão com um ícone, como por exemplo, uma cruzinha indicando inserção. Considerando este aspecto, vamos adicionar esta representação visual.

Inserindo o ícone de adição no FAB

Para adicionar um ícone, você pode baixar no site material.io, ou então, pode usar o próprio AS que ele adiciona pra você. De qualquer forma, o nome do ícone é add e ele vai ficar da seguinte maneira dentro do projeto:

hierarquia de arquivos do projeto para adicionar o ícone

Veja que ele fica dentro de main/drawable e para cada tamanho diferente ele adiciona um prefixo. Após conter o ícone dentro do projeto, basta apenas inserir pelo atributo src do FAB:

Adicionando o ícone no FAB
Fiz a inserção do ícone via AS

Então, executando a App, temos o seguinte resultado visual:

FAB com o ícone adicionado

Pronto, finalizamos a parte visual para permitir a inserção de uma nota, vamos agora para o código!

Adicionando a tela para receber as informações do usuário

Uma das técnicas comuns para recebermos as informações do usuário seria abrir uma Activity nova que ficaria responsável em computar as informações da nota.

Entretanto, já que atualmente a nossa nota recebe apenas informações de título e descrição, podemos implementar uma solução mais enxuta e objetiva, ou seja, um Dialog.

O código de implementação do Dialog vai ficar da seguinte maneira:

Código para criar um Dialog
Considerando que o foco do post não é a construção de Dialogs, preferi não dar tanta atenção nesta parte. Entretanto, se tiver dúvida, além do link que forneci durante o artigo, fique à vontade em perguntar

Apenas para deixar claro alguns parâmetros, o window.decorView representa a view da Activity, por isso pode ser usado como o ViewGroup para a função inflate().

Veja que estamos criando um Dialog baseando-se em um layout. Segue o código do layout:

Layout para receber as informações das notas
Ao invés de um EditText simples, utilizei o TextInputEditText para apresentar um aspecto visual comum no Material Design.

Criamos o código do Dialog, porém, se executarmos a App do jeito que está, o Dialog vai ser criado e apresentado logo de cara, ou seja, precisamos indicar que esse código do Dialog só vai ser executado apenas por uma ação, que no nosso caso, é clicando o FAB.

Portanto, vamos implementar a função setOnClickListener():

Adicionando o Dialog dentro do listener do FAB

Veja que neste momento, foram apresentados alguns problemas de compilação na referência do this. Isso acontece, pois na implementação do Object Expression o this acaba se tornando o objeto da interface View.OnClickListener.

Utilizando labels para referenciar o objeto this

Em outras palavras, precisamos indicar que o this é uma referência à Activity, para isso, podemos utilizar uma label com o nome da classe que queremos referenciar:

Adicionando a label para referenciar a Activity no this

Repare que agora o código volta a compilar, pois ao invés de referenciar o objeto da interface, estamos referenciando o objeto da Activity. Vamos testar a App e ver como ficou:

Dialog inicial apenas com o layout que foi montado

Note que agora temos uma caixinha que nos permite enviar informações das nossas notas. Entretanto, da maneira como está atualmente, não temos nenhuma opção para salvar a nota baseando-se nas informações que estão vindo do Dialog.

Adicionando botões no Dialog

Sendo assim, vamos adicionar um botão para salvar a nota chamando a função setPositiveButton() do AlertDialog.Builder:

Chamando um botão positivo do Dialog

Para essa função, precisamos enviar 2 parâmetros:

  • Texto para ser apresentado.
  • Implementação da interface DialogInterface.onClickListener.

Portanto, podemos realizar a seguinte implementação:

Implementando a interface DialogInterface.OnClickListener

Veja que indicamos que o valor do texto da opção vai ser "Save" e, por enquanto, só implementamos a interface por meio do Object Expression. Portanto, dentro da função onClick(), vamos pegar as informações dos campos do Dialog.

Para isso podemos usar o Synthetic a partir da variável createdView que é o objeto do tipo View que tem acesso a todos os componentes do Dialog.

Pegando os componentes com o Synthetic

Agora que temos uma nota que está sendo criada a partir de uma interação com o usuário. Precisamos enviá-la para a API, mas, como podemos fazer isso?

Criando a requisição para enviar uma nota para a API

Para isso, o nosso primeiro passo é criar a representação da requisição, ou seja, vamos criar a assinatura insert() na interface NoteService:

Declarando a assinatura insert() no Service

Então, basta apenas anotarmos com @POST para indicar que vai ser uma requisição do tipo POST e o caminho que é "notes":

Fazendo com que a função insert() seja uma requisição POST

Agora, precisamos indicar que nesta requisição, enviaremos uma nota. Mas como podemos indicar isso?

Enviando objetos nas representações de requisição do Service

Quando queremos enviar uma informação no Retrofit, o primeiro passo é enviar um objeto via parâmetro:

Enviando o objeto note na assinatura insert()

Entretanto, quando enviamos um parâmetro para um Service, precisamos indicar o que ele representa na requisição, ou seja, se ele faz parte, por exemplo, do corpo da requisição ou de um valor relacionado a um cabeçalho…

Como no nosso caso, precisamos enviar via corpo da requisição, podemos utilizar a annotation @Body:

Indicando que o objeto note vai ser enviado via parâmetro

Por fim, indicamos que o retorno desta requisição será uma Call para realizarmos a requisição, porém, o que esperamos de retorno do servidor para esta requisição?

Repara que na assinatura list() esperamos uma lista de notas, certo? Mas lembra que no post onde implementamos a CRUD API ele nos devolve a própria nota? Claro, junto com o id que foi criado no servidor…

Sendo mais objetivo, isso significa que podemos indicar que o retorno é uma Call que vai conter uma Note:

Declarando que a call vai conter uma nota

Temos a nossa representação para inserir uma nota!

Declarando a função de inserção no Web Client

O nosso próximo passo, é implementar a função que vai fazer a chamada dentro do nosso NoteWebClient, portanto, criaremos a função insert():

Criando a função insert() no Web Client

Nela, precisamos da nota que será enviada para a API, logo, pediremos via parâmetro:

Recebendo um objeto do tipo Note via parâmetro do insert() do Web Client

Então, podemos realizar a requisição da mesma maneira como fizemos na função list():

Implementando o callback dentro da função insert() do Web Client

Bacana, criamos a função que vai permitir enviar a nota que é criada pelo usuário. Entretanto, temos que chamar essa função após o momento que criamos o objeto de acordo com as informações que o usuário vai enviar:

Por mais que chamemos o Web Client, o que vai acontece com a tela do usuário caso seja enviado apenas para o servidor? Vamos testar?

Enviando nota via Dialog para a API, porém não aparece na lista de notas

Veja que não é apresentado nenhum erro, como também, o nosso usuário não vê a nota que ele adicionou na lista de notas! Mais engraçado ainda, é quando fechamos a App e abrimos de novo ou se simplesmente pedimos para o AS executá-la:

Nota que foi inserida na API aparece

Repara que a nota foi salva e agora ela aparece!

Basicamente, não tomamos nenhuma ação após a finalização da requisição, como por exemplo, modificar a lista de notas.

Em outras palavras, da mesma maneira como fizemos na função list(), precisamos também enviar uma interface na qual vai permitir com que tomemos uma ação assim que a requisição for realizada.

Recebendo a interface para permitir a implementação

Sendo assim, além de enviarmos o parâmetro note vamos enviar o noteResponse:

Recebendo a interface NoteResponse via parâmetro do insert() do Web Client

Então, basta apenas chamarmos a função success():

Chamando a função success dentro do onResponse()

Entretanto, note que essa função exige que enviemos uma lista de notas, sendo que agora temos apenas um objeto do tipo Note… E agora?

Utilizando o Generics para aumentar a flexibilidade

Quando entramos no caso em que precisamos utilizar uma mesma função que recebe um tipo específico, porém, em determinadas situações, ela precisa receber tipos diferentes, como é o caso da nossa success() dentro da função insert().

Podemos aplicar o Generics da mesma maneira como faríamos no Java. Portanto, ao invés de receber uma lista de notas ou uma nota, a nossa função success() vai receber um tipo genérico chamado t:

Recebendo parâmetros genéricos na função success()

E para deixar mais descritivo o que esse T significa, vamos modificar o nome do parâmetro e deixar como response:

Adicionando um nome mais expressivo no valor genérico

Mas repara que neste ponto do código, o T não compila!

Isso acontece, pois quando declaramos o Generics, precisamos também indicar no nome da interface que quem for usá-la, vai precisar especificar o tipo genérico que ela vai lidar.

Podemos indicar esse tipo de comportamento da seguinte maneira:

Adicionando o generics na NoteResponse

Veja que foi adicionado <T> logo após o nome da interface.

Agora, em todo ponto de código que usar essa interface, vai precisar especificar o tipo, portanto, vamos ver como fica no nosso NoteWebClient, primeiro vamos modificar na função list():

Chamando a função success() da implementa de NoteResponse<List<Note>>

Veja que agora estamos recebendo por parâmetro um NoteResponse<List<Note>> e conseguimos chamar o success() enviando uma lista de notas! Mas como fica o código na função insert()?

Chamando o success do NoteResponse na função insert

Repare que agora, também estamos utilizando a interface NoteResponse, desta vez, enviamos via generics a classe Note e, consequentemente, conseguimos chamar a função success() enviando um objeto do tipo Note!

Em outras palavras, criamos uma interface de resposta às callbacks genérica, inclusive, podemos até mesmo renomeá-la para CallbackResponse, pois, dessa forma, podemos reutilizá-la com outros recursos:

Renomeando a função NoteResponse para CallbackResponse
Sempre que for renomear variáveis, funções, interfaces ou classes, considere o uso de recursos da IDE, como é o caso do Rename do AS, o atalho para utilizado é o Shift + F6 ou, via menu, Refactor > Rename…

Agora, na Activity, precisamos resolver o problema de compilação, o primeiro é justamente no momento que chamamos a list() do nosso Web Client:

Implementando a função success do CallbackResponse na chamada da função list

Observe que implementamos um CallbackResponse sendo que agora, ele precisa ser mais específico, ou seja, basta apenas mudar para CallbackResponse<List<Note>>:

Implementando a função success do CallbackResponse<List<Note>>

Então, no momento que chamamos a função insert() lá na Activity, basta apenas enviarmos como segundo parâmetro, uma implementação da interface CallbackResponse<Note>:

Implementando a função success do CallbackResponse<Note>

Em seguida, precisamos apenas pegar esse objeto e adicionar na lista que contém todas as notas.

Entretanto, repara que atualmente a nossa lista está sendo recebida diretamente da resposta da requisição e sendo enviada para a função configureList() que faz a configuração do RecyclerView.

Portanto, para esta situação, podemos criar uma property para armazenar a lista de notas:

Transformando a lista de notas em property

Repara que criamos uma property que inicialmente trata-se de uma lista vazia, ou seja, quando recebemos a lista como resposta de callback da função list(), basicamente o que precisamos fazer, é adicionar todos os elementos nela.

Peculiaridades das Collections do Kotlin

Entretanto, as Collections do Kotlin possui uma característica bem diferente da abordagem vista no Java, pois a interface List no Kotlin é imutável.

Isso significa que a interface List não nos disponibiliza funções que alterem sua estrutura, como por exemplo, o add() ou allAll().

“Então como podemos usar uma lista da maneira como fazemos no Java?”

Caso a nossa intenção é possuir uma lista mutável, o Kotlin nos fornece a interface MutableList. Em outras palavras, podemos modificar a nossa property para ser uma lista mutável:

private val notes: MutableList<Note> = listOf()

Consequentemente, precisamos também criar uma instância de lista vazia para devolver uma lista mutável por meio da função mutableListOf():

private val notes: MutableList<Note> = mutableListOf()

Agora, podemos adicionar as listas que recebemos em resposta do callback da função list() do Web Client:

Adicionando lista que veio do servidor no RecyclerView

Também, podemos adicionar a nota que recebemos de resposta do callback da função insert() do Web Client:

Implementando a função insert() da Web Client

Repara que da mesma forma temos que atualizar a lista do RecyclerView chamando a função configureList().

É válido observar que essa não é a maneira ideal para atualizarmos um RecyclerView, ou seja, normalmente lidamos diretamente com o Adapter notificando-o quando algo é alterado.

Pronto! Conseguimos configurar a nossa lista para que seja modificada assim que adicionarmos uma nota na API, vamos testar pra vê o que acontece?

Adicionando uma nota na API e na App ao mesmo tempo

Uhul! Agora a nossa App, além de pegar as notas da API, também consegue salvá-las!

Código fonte

Caso tenha tido alguma dúvida ou queira ter acesso ao código fonte, fique à vontade em consultar o projeto que deixei no GitHub.

Para saber mais

Por mais que tenhamos deixado o nosso código melhor em relação à primeira abordagem que fizemos, ainda existem pontos que podemos melhorar.

Um deles é fazendo com que o código do Dialog seja separado da mesma maneira como fizemos com o Retrofit e seus callbacks.

Outro ponto é que a implementação dos Callbacks são bem similares e o código é bem verboso… Existem técnicas para deixá-los cada vez mais resumidos! Interessado? Então dê uma olhada na terceira parte dessa série de Retrofit 😉

Conclusão

Neste artigo, aprendemos a separar os callbacks do Retrofit utilizando uma interface responsável em delegar a resposta da call. Também vimos que podemos ter o mesmo comportamento da implementação de classe anônima do Java utilizando o Object Expression.

Além disso, fomos capazes de implementar a funcionalidade para enviar uma nota para a API de tal forma que ela é atualizada na lista de notas. Nesta implementação, aprendemos a usar o FAB e a criar um Dialog. Então, vimos que temos a capacidade de enviar objetos no corpo da requisição com o Retrofit usando a annotation @Body.

Por fim, aplicamos um pouco de generics para viabilizar o uso da interface de resposta, como também, vimos que as collections no Kotlin são imutáveis por padrão.

O que achou do conteúdo que foi abordado neste artigo? Deixe o seu comentário 😄

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.