Como criar um ListAdapter usando Kotlin
Um tutorial para a criação de um ListAdapter utilizado para adaptar o objeto de sua lista possibilitando a exibição com o RecyclerView.
Quando comecei a trabalhar como desenvolvedor, uma dos primeiros desafios que tive era o de popular alguns dados em uma lista. Me lembro ainda o quanto eu apanhei pra essa “simples” tarefa.
Já que praticamente todo App que existe por aí exibe de alguma forma uma lista, vou compartilhar com vocês o que aprendi ao longo do caminho.
Vamos lá!
RecyclerView
Esse artigo não é precisamente sobre o RecyclerView, mas vale uma breve descrição da utilidade desse ViewGroup.
Normalmente para exibir uma lista no Android nós usamos o RecyclerView, que, bem, recicla a view utilizada para exibir cada elemento, quando um item é rolado para fora do tela, sem a necessidade de criar uma view nova. Então o RecyclerView requisita essas views criadas pelo view holder e as associa ao seu objeto da lista.
Adapter
O que descobri recentemente é que, o adapter nada mais é que um padrão de projeto “design pattern” utilizado para converter uma interface possibilitando seu funcionamento com outra, e não algo exclusivo para o RecyclerView.
Por exemplo, seu objeto -> adapter -> RecyclerView. Pode-se entender, então, que seu objeto foi adaptado para ser possível sua exibição pelo RecyclerView, e para ser possível essa conversão, seu adapter precisa saber os seguintes dados:
- De quantos itens a lista é composta, utilizado para determinar o final da lista.
- Como criar um item.
- Como criar uma nova view.
ListAdapter
O ListAdapter é uma classe que implementa o comportamento padrão de um RecyclerView.Adapter para ter acesso e contar os itens da lista. É necessário também providenciar uma DiffUtil, que é uma classe responsável por calcular a diferença entre duas listas e retornar uma lista atualizada, o algoritmo utilizado para essa operação chama-se: Eugene W. Myers’s difference algorithm.
Vamos à implementação!!!
O ListAdapter é definido conforme se vê no código abaixo, onde, Any é o objeto de sua lista e VH é o view holder, que ainda explicarei.
abstract class ListAdapter<T : Any!, VH : RecyclerView.ViewHolder!> : RecyclerView.Adapter<VH>
A implementação é feita da seguinte forma:
class MoviesAdapter(private val click: (MovieDto) -> Unit):ListAdapter<MovieDto, MoviesViewHolder(MoviesAdapter){}
No código acima podemos ver que a classe MoviesAdapter extende a classe ListAdapter, nós tivemos também que informar o objeto da lista MovieDto
, um ViewHolder MoviesViewHolder
e um DiffUtil MoviesAdapter
. A implementação do DiffUtil será demonstrada mais abaixo.
Ao extender o ListAdapter, se faz nescessário implementar dois métodos:
- onCreateViewHolder:
Este método é responsável pela criação de um novo objeto do tipo view holder, sempre que o RecyclerView precisar de um e é implementado da seguinte maneira:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviesViewHolder {
return MoviesViewHolder.from(parent)
}
onCreateViewHolder
pede 2 argumentos, um ViewGroup, no qual uma nova view é adicionada antes der ser exibida na tela; e um ViewType, que é utilizado que se tem múltiplas e diferentes views no mesmo RecyclerView, como por exemplo um header.
- onBindViewholder:
Este, por sua vez, é responsável por vincular os dados e usá-los para preencher o layout do view holder, e é implementado da seguinte forma:
override fun onBindViewHolder(holder: MoviesViewHolder, position: Int) {
holder.bind(getItem(position), click)
}
onBindViewHolder
pede também 2 argumentos, um objeto do tipo view holder, e uma posição do tipo Int. O view holder é a classe responsável por tudo que é relacionado ao gerenciamento das views; e a posição é informada ao view holder para que este exiba o item nessa determinada posição.
Como vimos, o seu adapter precisa de um objeto do tipo view holder, podemos pensar sobre o view holder como a tradução literal nos sugere, um objeto que segura uma view, cada elemento dentro de sua lista é definido pelo view holder. A justificativa para a necessidade do view holder se da pela razão de que o RecyclerView nunca irá interagir diretamente com a sua view. Mas, como é feita a implementação de um ViewHolder?
Vamos à explicação!
Nosso MoviesViewHolder
extende o RecyclerView.ViewHolder e encapsula a lógica para junção das views, para criação do próprio view holder e lida com os eventos de cliques, por exemplo nossa função click: (MovieDto) -> Unit
.
Isso nos da uma forma mais encapsulada e clean para a criação de novos view holders e de todo o trabalho relacionado ao detalha de inflar a view e qual o layout será exibido.
Por fim implementamos o DiffUtil, e, como já sabemos, esta classe é reponsável por calcular a diferença entre duas listas, e nos devolver uma lista atualizada para exibição dos dados. Aqui utilizamos o ItemCallback que calcula basicamente a diferença entre dois itens em uma lista, desde que os itens em questão não sejam nulos, ressaltando que essa comparação é baseada no hashCode da sua data class.
Ao extender ItemCallback também se faz necessária a implementação de 2 métodos:
- areItemsTheSame: Este é chamado para verificar se dois objetos representam o mesmo item.
- areContentsTheSame: Este é utilizado para checar se dois itens tem a mesma data.
A implementação completa desta classe pode ser feita da seguinte forma:
Tenho preferido implementar o DiffUtil dentro de um private companion object
para manter tudo dentro da mesma classe.
E por fim, você precisa passar sua lista de objetos para o ListAdapter. Para tanto, em sua Activity ou Fragment, após setar o seu RecyclerView e criar seu Adapter, chame a função submitList(suaLista) no seu adapter.
private fun displayMovies(movies: List<MovieDto>) {
moviesAdapter.submitList(movies)
}
Aqui está o código completo da implementação do nosso ListAdapter:
TL;DR
O uso do ListAdapter traz benefícios mais nítidos quando trabalhamos com listas que podem ser alteradas em runtime, como por exemplo, a exclusão de um item da lista, pois, a implementação do ListAdapter com o DiffUtil rodará o algoritmo para detectar as mudanças da sua lista em uma Thread secundária e o ListAdapter, como comportamento padrão, implementa uma animação quando um item é adicionado ou removido da lista.
Obrigado por ter lido até aqui.
Nos vemos no próximo post. :D