Criando lista com RecyclerView no Android com Kotlin

Alex Felipe
CollabCode
Published in
10 min readSep 18, 2017

No artigo onde aprendemos a criar uma App com suporte ao Kotlin, criamos o projeto Ceep com o intuito de fazer uma App similar ao Google Keep, porém, ainda não implementamos a lista para conter as notas da nossa App…

Considerando essa necessidade, neste artigo, veremos como podemos implementar a nossa lista utilizando a API RecyclerView do Google.

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 😉

Caso não tenha a mínima ideia do que seja essa API, recomendo este artigo que ensina passo a passo o que é o RecyclerView e como é possível implementá-lo com Java no Android.

Preparando o ambiente

Para criarmos essa tela, iremos utilizar tanto o RecyclerView como também o componente CardView, portanto vamos adicionar as seguintes dependências no arquivo build.gradle:

Adição do recyclerview e cardview no build.gradle do módulo app

Em seguida, precisamos apenas sincronizar o Gradle que teremos ambas as APIs disponíveis.

É válido notar que estou usando a versão 3 do AS que faz uso do gradle wrapper versão 4.1, por isso foi utilizado o implementation ao invés do compile para definir as dependências.

Também é importante observar que usei a versão 26.0.2 em ambas as libs para o exemplo, porém, se tiver alguma versão mais atualizada, fique à vontade em utilizá-la.

Adicionado o RecyclerView no layout

Agora que temos o RecyclerView no projeto, vamos modificar o nosso layout activity_note_list para que agora ele contenha o componente RecyclerView:

layout principal da activity com o RecyclerView

Com o RecyclerView disponível, podemos começar com a configuração do Adapter que vai conter as notas.

Criando o Adapter para representar as notas

Para isso, vamos criar a classe NoteListAdapter e fazer extensão da classe RecyclerView.Adapter:

Realizando a extensão do RecyclerView.Adapter

Veja que o compilador acaba reclamando no momento que fazemos a extensão do Adapter justamente porque ele exige que passemos uma classe que faça a extensão de um RecyclerView.ViewHolder via generics, ou seja, o nosso famoso ViewHolder que precisamos criar para o Adapter de um RecyclerView.

Sendo assim, vamos criar a classe ViewHolder que vai estender a classe RecyclerView.ViewHolder:

Criando o ViewHolder

Calma aí! Como sabemos, no Kotlin, por padrão, todos os membros dentro de um arquivo Kotlin são public, ou seja, estamos com duas classes públicas em um único arquivo e ele compila! Algo que no Java não é pertimido, certo?

Entretanto, esse é um comportamento comum dentro do Kotlin, isto é, podemos definir mais de uma classe pública dentro de um único arquivo…

Cuidados ao criar mais de uma classe dentro de um arquivo Kotlin

Por mais que a linguagem permita esse tipo de comportamento, é muito importante tomarmos cuidado e não abusarmos dessa abordagem, pois existe a possibilidade criarmos um código cada vez mais difícil de ler ou manter.

A minha recomendação é que evite o máximo possível criar mais de uma classe pública dentro do mesmo arquivo, ou então, simplesmente não crie. Dessa forma, evitamos mais complexidade desnecessária.

Criando uma Nested class no Kotlin

Considerando a ideia de que precisamos da classe ViewHolder que vai servir apenas para o nosso NoteListAdapter, nada mais justo do que torná-la uma Nested class:

Tornando o Viewholder uma Nested class

Observe que agora, caso alguém, além da classe NoteListAdapter, quiser acessar a nossa classe ViewHolder não será possível.

Já que a princípio o nosso ViewHolder não vai acessar nenhum membro da classe NoteListAdapter, como também, não faz sentido algum alguém acessá-la de fora, mantê-lo como Nested class, ao invés de uma Inner class por exemplo, é uma solução bem vinda!

Peculiaridades entre o Adapter e o ViewHolder do RecyclerView

Bacana, agora que criamos o nosso ViewHolder, precisamos resolver os problemas de compilação que surgiram.

O primeiro deles é justamente fazer com que o RecyclerView.Adapter receba o ViewHolder via generics:

Adicionando o ViewHolder via generics no Adapter

Agora, precisamos também enviar uma View para o construtor do RecyclerView.ViewHolder pois é partir dela que o Adapter conseguirá reciclar as views:

Enviando a View via construtor para o ViewHolder

Pronto! Basta apenas pedirmos para o AS inserir as funções do RecyclerView.Adapter que precisam ser sobrescritas:

Sobrescrevendo as funções do RecyclerView.Adapter

O nosso próximo passo é começar a implementar as 3 funções que foram sobrescritas. Vamos lá?

Note que ao pedirmos para o AS sobrescrever as funções do Adapter, surgiu a chamada da função TODO(), não se preocupe!

Essa função quando executada, basicamente da um crash na App e envia uma exception indicando que não existe a implementação da função chamada.

Portanto, podemos mantê-la enquanto não finalizarmos a implementação de cada da função e, quando finalizarmos a implementação, basta apenas removermos essa chamada.

Implemetando a função getItemCount()

Começaremos pela getItemCount() que basicamente precisa do tamanho da lista que iremos enviar para o Adapter.

Para isso, podemos criar a property notes para representar a nossa lista e retornamos o seu tamanho na função getItemCount():

Enviando a lista de notas via construtor e implementando a função getItemCount()

Repara que ao mesmo tempo que recebemos a lista de notas via construtor primário, a tornamos uma property da NoteListAdapter no momento que adicionamos a keyword val.

Também, fazemos com que essa property seja privada, pois não há a necessidade de alguém, a não ser o nosso próprio Adapter, saber da existência dela.

Criando o modelo para representar cada nota

Mas tem um detalhe nesse passo… Em nenhum momento criamos a classe Note, sendo assim, vamos criá-la agora:

Modelo para representar uma nota

Como a ideia do exemplo é ser uma App simples, ter apenas o título e a descrição é o suficiente. Em seguida, basta apenas fazer o import da classe Note dentro do NoteListAdapter.

Agora que temos a nossa lista de notas, podemos continuar com a implementação das funções sobrescritas.

Implementando o onCreateViewHolder()

Dessa vez, iremos implementar a função responsável por criar o nosso ViewHolder, ou seja, a onCreateViewHolder().

Dentro dela, precisamos inflar o layout para representar cada uma das notas. Mas até o momento não criamos um layout pra isso, ou seja, vamos criar o layout note_item com o seguinte código:

Layout para representar cada nota do RecyclerView

Em seguinda, vamos inflar o layout, porém, para que isso seja possível, precisamos também receber um Context via construtor:

Inflando a view que representa cada nota do layout

E então, podemos retorna a instância do nosso ViewHolder enviando a view via construtor:

Retornando o ViewHolder dentro do onCreateViewHolder

Implementando o onBindViewHolder()

Agora nos resta implementar a última função que foi sobrescrita que tem a finalidade de realizar a junção das informações contida no ViewHolder com os componentes do layout, ou seja, a onBindViewHolder().

Para isso, primeiramente, precisamos fazer com que o nosso ViewHolder contenha os componentes do layout que inflamos, ou seja, os TextViews do note_item.

Utilizando o Synthetic para buscar as views dentro do Adapter

Uma das abordagens que costumamos fazer, é justamente chamar o findViewById a partir do objeto itemView que nada mais é do que a View que criamos.

Mas, como vimos, por meio do Kotlin Android Extensions, podemos evitar esse tipo de tarefa utilizando o Synthetic, ou seja, por meio do itemView podemos chamar os componentes pelos seus ids:

Buscando os componentes com o synthetic

Portanto, o que precisamos fazer agora é justamente pegar uma nota por meio da posição recebida do onBindViewHolder() e realizar o processo de bind, ou seja, colocar as informações do objeto devolvido em cada um dos TextViews:

Utilizando o holder para realizar o bind

Ops! Veja que nesse momento o AS apresenta um problema de compilação! Isso acontece, pois o holder recebido por parâmetro, pode ser null

Nunca tivemos esse tipo de comportamento quando fazíamos no Java, certo? Então porque no Kotlin isso acontece?

Entendendo o Null Safety

O Kotlin foi desenvolvido baseando-se em diversas features encontradas em várias linguagens de programação, como é o caso do C#, Swift, JavaScript entre outras…

Dentre as features disponíveis, temos a Null Safety que tem a proposta de evitar que o programador tome a famosa NPE, ou seja, a exceção NullPointerException.

Em outras palavras, temos diversas restrições quando estamos lidando com valores null ou que podem ser null dentro do Kotlin.

De modo geral, quando um valor for null ou existir a possibilidade de ser null o código não vai compilar!

“Mas por que isso só surgiu agora?”

Se observarmos o parâmetro holder veremos que ele está declarado da seguinte maneira:

holder: ViewHolder?

O operador ?, durante a declaração de uma variável, parâmetro, property ou retorno de função, nos indica que existe uma possibilidade que o valor seja null, portanto, o Kotlin exige que a gente trate esse objeto e garanta que ele não seja null.

Em Java, garantimos que um valor não é null da seguinte maneira:

if (holder != null) {
holder.title.text = note.title
holder.description.text = note.description
}

Em Kotlin, esse código também é válido, porém, existem técnicas mais sucintas que veremos a seguir.

Utilizando Safe Calls

O Kotlin nos fornece um operador conhecido como Safe Call, a partir dele, temos a capacidade de garantir que um membro de um objeto (property, funções ou classes) vai ser chamado apenas se ele não for null.

Para usarmos esse operador, basta apenas incluir novamente o operador ? antes de chamar o membro do objeto que pode ser null, como é o caso do holder:

holder?.title.text = note.title
holder?.description.text = note.description

Dessa forma, não precisamos verificar se o holder é null, ou seja, tanto a property title como a description serão chamadas apenas se o holder não for null, logo, estamos evitando o NPE.

Mas repara que agora, quando chamamos a propertytext de ambos os TextViews temos o mesmo problema deles poderem ser null… E se analisarmos a nossa property title ela não indica que pode ser null

“Por que isso acontece?”

Quando utilizamos uma Safe Call e tentamos realizar mais uma chamada de um membro de forma encadeada, precisamos garantir que o membro não seja null também. E Agora?

Realizando uma Safe Call em modo chain

Simples! Podemos fazer uma Safe Call em modo chain, ou seja, de forma encadeada:

holder?.title?.text = note.title
holder?.description?.text = note.description

Bacana, esse ajuste resolve o nosso problema, mas veja que estamos deixando o nosso código cada vez mais sujo com essa quantidade de Safe Calls, e mais, caso tivéssemos que chamar mais membros, seria uma meleca e tanto… Será que não tem uma alternativa ainda mais sucinta?

Se analisarmos o nosso código, sabemos que o único membro que precisamos garantir que não é null é o holder, concorda?

Considerando essa analise, uma abordagem ideal seria da seguinte maneira:

  1. Verificar se o holder não é null;
  2. Se isso for verdade, todo o código executado a seguir, deve considerar o holder como não null.

Será que isso é possível?

Dando permissão de execução com o let

Podemos realizar esse tipo de comportamento por meio da função let que pode ser chamada por um objeto, como é o caso do holder, e então, executa um bloco de função com o objeto que a chamou embutido dentro dela.

Traduzindo para o código, temos o seguinte resultado:

holder?.let {
holder.title.text = note.title
holder.description.text = note.description
}

Veja que nessa abordagem o nosso código compila! Porém, nos deixa na seguinte situação:

“Que diabos aconteceu aqui?”

De uma forma mais carinhosa, quando fizemos a Safe Call e chamamos o let com o nosso holder, o nosso holder foi para dentro do escopo do let como um objeto que não é null.

Portanto, ao utilizá-lo dentro do escopo do let, não existe mais a necessidade de verificar se ele ou seus membros são null! Algo incrível, né?

Utilizando o objeto it

Além disso, quando realizamos esse tipo de abordagem, o objeto enviado pode ser chamado utilizando a keyword it que nada mais é do que o próprio objeto embutido dentro do let, nesse caso o nosso próprio holder:

Garantindo a integridade do código com as safe call e a função let

Veja que agora estamos fazendo o processo de bind de uma forma bem mais sucinta e elegante. Claro, é importante ressaltar que se o holder for null, o let não será executado!

Delegando a responsabilidade

Bacana, conseguimos fazer o processo de bind mas perceba que esse tipo de comportamento, faz todo o sentido dentro do próprio ViewHolder, ou melhor, a boa prática nessa situação é fazer com que o nosso próprio ViewHolder faça isso pra gente!

Para isso, basta apenas criarmos a função bindView() que recebe uma nota dentro do ViewHolder e, dentro dela, a gente faz o processo de bind:

Implementando a função de bindView

Observe que dessa forma, apenas a própria classe ViewHolder sabe da existência dos componentes, como também, ela mesma ficou responsável em computar as informações dos mesmos.

Configurando o RecyclerView

Agora que já temos o nosso Adapter implementado, precisamos apenas utilizá-lo e terminar a configuração do nosso RecyclerView. Sendo assim, na nossa NoteListActivity, vamos buscar o RecyclerView e settar o Adapter:

Settando o Adapter no RecycleView

Repara que estamos retornando uma lista hardcoded apenas para teste, mas poderia ser uma busca por um banco de dados ou via Web Service.

Em seguida, precisamos apenas indicar o layout do RecyclerView por meio do LayoutManager:

Definindo o LayoutManager para o RecyclerView

Utilizamos StaggeredGridLayoutManager para termos o mesmo aspecto visto no Google Keep. Agora ao rodarmos a nossa App, temos o seguinte resultado:

Primeira amostra do Ceep

Uhul! Finalmente conseguimos implementar a primeira amostra da nossa App. 😄

Código fonte

Se preferir consultar o código fonte desenvolvido no artigo, deixo aqui o link do GitHub. 😉

Para saber mais

Além de implementar a lista no Android, uma das atividades comuns é fazer com que as informações destas listas venham, por exemplo, de um servidor, muito conhecido também como API.

Considerando essas características, vou deixar alguns artigos que dão continuidade na App Ceep visando o conteúdo sobre APIs REST:

  • Criando CRUD API com Spring Boot
  • Consumindo CRUD API no Android com Retrofit

Conclusão

Nesse artigo aprendemos a implementar o RecyclerView em um projeto Android com Kotlin.

Sendo mais específico, vimos passo a passo como fazemos a implementação do Adapter do RecyclerView e conseguimos compreender todas as peculiaridades que precisamos lidar durante a implementação como é o caso de:

  • Definir o ViewHolder via generics no Adapter para que as funções implementadas utilizem como referência
  • Criar o ViewHolder com uma Nested class para garantir que apenas o Adapter vai usá-la
  • Utilização do Synthetic para facilitar a busca dos componentes do layout
  • Garantir que o ViewHolder não seja null e depois realizar o processo de bind

Aproveite também e deixe o seu comentário sobre o que achou da implementação do RecyclerView, como também, o seu feedback sobre o que achou do Kotlin e como ele nos ajuda durante o desenvolvimento.

--

--