Ao utilizar o RecyclerView
no Android, muito provavelmente você se depara com a necessidade de implementar um listener de clique em um dos items do RecyclerView.Adapter
. Porém, não existe uma interface ou método padrão para fazer essa implementação, ou seja, somos obrigados a implementar manualmente!
Dada essa situação, eu vou mostrar pra você duas possibilidades comuns de implementação: interface ou o tipo função (muito utilizados em Higher-Order Function) do Kotlin.
Projeto de exemplo
Para esse exemplo, vou utilizar o Orgs, um App que simula um e-commerce de produtos naturais e bastante utilizado nos conteúdos da Alura.
Se tiver interesse em acompanhar o artigo com o exemplo, você pode obter mais informações do projeto a partir do repotório do GitHub, ou então, pode baixar diretamente a partir do código que vou utilizar. Abaixo segue uma amostra de execução do App.
Resumo do código já implementado
O App Orgs possui uma implementação de RecyclerView.Adapter
para apresentar os produtos cadastrados, a ListaProdutosAdapter
:
class ListaProdutosAdapter( private val context: Context, produtos: List<Produto> ) : RecyclerView.Adapter<ListaProdutosAdapter.ViewHolder>() { private val produtos = produtos.toMutableList() class ViewHolder(private val binding: ProdutoItemBinding) : RecyclerView.ViewHolder(binding.root) { fun vincula(produto: Produto) { // vincula produto com as views } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(context) val binding = ProdutoItemBinding.inflate(inflater, parent, false) return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val produto = produtos[position] holder.vincula(produto) } override fun getItemCount(): Int = produtos.size fun atualiza(produtos: List<Produto>) { // atualiza produtos ao receber novos produtos } }
Note que nesta implementação, utilizamos o View Binding para fazer o processo de vínculo de View. Caso seja a sua primeira vez vendo ele, recomendo a leitura deste artigo que explica com mais detalhes o que é o View Binding e como ele funciona.
Como podemos notar, a implementação deste adapter é relativamente simples e comum às implementações de adapters para RecyclerView
. Mas agora vem a questão:
"Onde eu devo modificar o código para obter um listener nos itens?"
Implementando o listener de clique na View do ViewHolder
Considerando que queremos um listener para cada item, a implementação deles precisa ser feita em cada View criada para o ViewHolder
. Em outras palavras, ao criar um ViewHolder
e atribuir uma View para ele, podemos também implementar o listener de clique na View:
class ViewHolder(private val binding: ProdutoItemBinding) : RecyclerView.ViewHolder(binding.root) { init { itemView.setOnClickListener { Log.i("ListaProdutosAdapter", "clicando no item") } // ou também // binding.root.setOnClickListener { // Log.i("ListaProdutosAdapter", ": clicando no item") // } } }
Com essa implementação, ao clicar em algum produto da lista de produtos, temos o seguinte resultado no log:
br.com.alura.orgs I/ListaProdutosAdapter: clicando no item
Agora a questão que fica é:
"Como podemos, por exemplo, reagir com esse listener na Activity que cria o Adapter?"
Criando o próprio listener
Para permitir que Activities ou qualquer outra classe reaja ao listener de um RecyclerView.Adapter
, precisamos criar o nosso próprio listener! Ele pode ser criado a partir de:
- Interfaces
- Tipo função
Listeners com interfaces
De uma maneira geral, o uso de interfaces é o mais conhecido pela comunidade do Android, pois, via Java, é a maneira mais utilizada! Portanto, vamos começar com essa implementação:
class ListaProdutosAdapter( private val context: Context, produtos: List<Produto> ) : RecyclerView.Adapter<ListaProdutosAdapter.ViewHolder>() { private val produtos = produtos.toMutableList() interface QuandoClicaNoItemListener { fun quandoClica() } // restante do código }
Note que neste exemplo, eu criei uma interface interna, justamente para indicar que o evento de clique é vinculado com o Adapter. Mas não significa que a interface deve ser criada dentro do adapter, fica a seu critério.
Com a interface criada, o próximo passo é criar uma property para que seja possível a implementação da interface:
class ListaProdutosAdapter( private val context: Context, produtos: List<Produto>, var quandoClicaNoItemListener: QuandoClicaNoItemListener = object : QuandoClicaNoItemListener { override fun quandoClica() { } } ) : RecyclerView.Adapter<ListaProdutosAdapter.ViewHolder>() { private val produtos = produtos.toMutableList() interface QuandoClicaNoItemListener { fun quandoClica() } inner class ViewHolder(private val binding: ProdutoItemBinding) : RecyclerView.ViewHolder(binding.root) { init { itemView.setOnClickListener { Log.i("ListaProdutosAdapter", "clicando no item") quandoClicaNoItemListener() } } } // restante do código }
Observe que o listener tem uma implementação padrão para evitar a obrigação de implementação ao criar a instância do adapter! Essa implementação padrão não tem comportamento adicional!
A partir do momento que temos acesso à property de listener (quandoClica()
), dentro do listener de clique da View
do ViewHolder
, podemos chamar o método quandoClica()
da interface para permitir que, quem implementar a interface, vai conseguir executar um código quando houver o clique!
Podemos testar esse código fazendo a seguinte implementação na Activity:
class ListaProdutosActivity : AppCompatActivity() { private val dao = ProdutosDao() private val adapter = ListaProdutosAdapter( context = this, produtos = dao.buscaTodos() ) private val binding by lazy { ActivityListaProdutosActivityBinding.inflate(layoutInflater) } // restante do código private fun configuraRecyclerView() { val recyclerView = binding.activityListaProdutosRecyclerView recyclerView.adapter = adapter adapter.quandoClicaNoItemListener = object : ListaProdutosAdapter.QuandoClicaNoItemListener { override fun quandoClica() { Log.i("ListaProdutosActivity", "quandoClica: ") } } } }
Veja que temos uma implementação de classe anônima a partir de um Object Expression! Ao testar o App, temos o seguinte resultado ao clicar em um produto:
br.com.alura.orgs I/ListaProdutosAdapter: clicando no item br.com.alura.orgs I/ListaProdutosActivity: quandoClica:
Agora somos capazes de reagir com o listener do Adapter! Inclusive, podemos personalizar a interface para, por exemplo, receber o produto clicado:
class ListaProdutosAdapter( private val context: Context, produtos: List<Produto>, var quandoClicaNoItemListener: QuandoClicaNoItemListener = object : QuandoClicaNoItemListener { override fun quandoClica(produto: Produto) { } } ) : RecyclerView.Adapter<ListaProdutosAdapter.ViewHolder>() { private val produtos = produtos.toMutableList() interface QuandoClicaNoItemListener { fun quandoClica(produto: Produto) } inner class ViewHolder(private val binding: ProdutoItemBinding) : RecyclerView.ViewHolder(binding.root) { private lateinit var produto: Produto init { itemView.setOnClickListener{ Log.i("ListaProdutosAdapter", "clicando no item") if(::produto.isInitialized) { quandoClicaNoItemListener.quandoClica(produto) } } } fun vincula(produto: Produto) { this.produto = produto // restante do código } // restante do código } // restante do código }
Caso seja a sua primeira vez vendo uma property
lateinit
, não se assuste! Basicamente, é um recurso do Kotlin para criar properties que podem ser inicializadas posteriormente, dessa forma, não precisamos colocar um valor padrão inválido ou trabalhar com nullables. Mas, antes de utilizar variáveislateinit
, certifique-se que a mesma foi inicializada, conforme a verificação viaif
. Caso contrário, será lançada uma exception e o App vai quebrar!
Com essa modificação, o ViewHolder
agora tem acesso a uma property mutável do tipo Produto
. Ela é atualizada cada vez que o método vincula()
é acionado.
Isso é necessário, pois ViewHolder
em RecyclerView.Adapter
são reutlizados, ou seja, sem a atualização da property, podemos enviar um produto errado! Sempre lembre-se disso!
Além disso, o método quandoClica()
do listener recebe um Produto
e, ao chamá-lo, enviamos o produto como argumento para que seja acessível por quem implementar o listener.
A partir dessa mudança, veja que a implementação padrão no construtor da ListaProdutosAdapter
modificou o quandoClica()
, pois agora ele vai receber o produto ao chamar o método quandoClica()
. Isso vale também para a Activity:
adapter.quandoClicaNoItemListener = object : ListaProdutosAdapter.QuandoClicaNoItemListener { override fun quandoClica(produto: Produto) { Log.i("ListaProdutosActivity", "quandoClica: ${produto.nome}") } }
Ao rodar o código e clicar no produto, temos um resultado diferente:
br.com.alura.orgs I/ListaProdutosAdapter: clicando no item br.com.alura.orgs I/ListaProdutosActivity: quandoClica: Salada de frutas
Agora temos acesso ao produto! E podemos fazer a ação que desejamos com o produto, como por exemplo, enviar para uma nova Activity que vai exibir o seu conteúdo!
"Ok, aprendemos a fazer a implementação com a interface, mas como fica com o tipo função do Kotlin?"
Listener com o tipo função do Kotlin
Com o tipo função é bastante similar, a diferença é que temos um código mais simplificado, pois não precisamos criar uma estrutura como uma interface, e toda a implementação pode ser via expressão lambda! Vamos começar com o adapter:
class ListaProdutosAdapter( private val context: Context, produtos: List<Produto>, var quandoClicaNoItemListener: (produto: Produto) -> Unit = {} // var quandoClicaNoItemListener: QuandoClicaNoItemListener = // object : QuandoClicaNoItemListener { // override fun quandoClica(produto: Produto) { // // } // } ) : RecyclerView.Adapter<ListaProdutosAdapter.ViewHolder>() { private val produtos = produtos.toMutableList() // interface QuandoClicaNoItemListener { // fun quandoClica(produto: Produto) // } inner class ViewHolder(private val binding: ProdutoItemBinding) : RecyclerView.ViewHolder(binding.root) { private lateinit var produto: Produto init { itemView.setOnClickListener{ Log.i("ListaProdutosAdapter", "clicando no item") if(::produto.isInitialized) { quandoClicaNoItemListener(produto) } } } fun vincula(produto: Produto) { this.produto = produto // restante do código } // restante do código } // restante do código }
Dê 9 linhas e uma leitura mais complexa, considerando a implementação padrão, fomos para 1 linha de uma forma mais simplificada com o tipo função! E temos mais resultados na Activity:
private fun configuraRecyclerView() { val recyclerView = binding.activityListaProdutosRecyclerView recyclerView.adapter= adapter adapter.quandoClicaNoItemListener = { Log.i("ListaProdutosActivity", "quandoClica: ${it.nome}") } //object : ListaProdutosAdapter.QuandoClicaNoItemListener, (produto: Produto) -> Unit { // override fun quandoClica(produto: Produto) { // Log.i("ListaProdutosActivity", "quandoClica: ${produto.nome}") // } //} }
De uma leitura mais complexa com o Object Expression, agora temos uma expressão lambda bem mais simplificada!
Conclusão
Com o acesso ao Kotlin, o uso do tipo função é mais desejado para implementações de listeners próprios! Seja pela simplicidade ou até mesmo pela forma mais idiomática de escrever código em Kotlin.
E você, o que achou dessas técnicas para criar listeners próprios no Kotlin? Se gostou, deixe like, comentário e compartilhe com a comunidade este conteúdo 😄
Código final
Agora que finalizou a leitura, se preferir, você pode consultar este commit para verificar o código finalizado.
Aprender mais
Que tal aprender mais sobre Android, RecyclerView
, Kotlin entre outras técnicas e tecnologias deste mundo de mobile? Além dos conteúdos abertos, também produzo cursos de Android na Alura seja para quem está iniciando ou para quem quer aprimorar mais ainda o conhecimento. Se você já assina a Alura, seguem os cursos. Caso você ainda não conhece a Alura e tem interesse, tenho uma boa notícia pra você também, a partir deste link, você tem 10% de desconto na sua assinatura 😉
Top comments (1)
Muito obrigado pelo artigo, não entendi quase nada, mas isso é porque sou iniciante, mas 10% consegui absorver.