Listener de clique no RecyclerView com Kotlin
Tags: android, kotlin, recyclerview, listener
Published: 2021-07-06 18:31:42

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](https://medium.com/collabcode/boas-pr%C3%A1ticas-com-higher-order-functions-no-kotlin-1de278d47dbf)) 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](https://www.alura.com.br/). 

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](https://github.com/alura-cursos/android-com-kotlin-personalizando-ui), ou então, [pode baixar diretamente a partir do código que vou utilizar](https://github.com/alura-cursos/android-com-kotlin-personalizando-ui/archive/refs/heads/aula-5.zip). Abaixo segue uma amostra de execução do App.

![https://github.com/alura-cursos/android-com-kotlin-personalizando-ui/raw/master/img/amostra.gif](https://github.com/alura-cursos/android-com-kotlin-personalizando-ui/raw/master/img/amostra.gif)

## Resumo do código já implementado

O App Orgs possui uma implementação de `RecyclerView.Adapter` para apresentar os produtos cadastrados, a `ListaProdutosAdapter`:

```kotlin
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](https://www.alura.com.br/artigos/view-binding-android) 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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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áveis `lateinit`, certifique-se que a mesma foi inicializada, conforme a verificação via `if`. 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:

```kotlin
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:

```kotlin
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:

```kotlin
    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](https://github.com/alura-cursos/android-com-kotlin-personalizando-ui/commit/f112db2d71bb9f74c752adb8740d4a87872a5113).

## 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](https://www.alura.com.br/cursos-online-mobile/android). Caso você ainda não conhece a Alura e tem interesse, tenho uma boa notícia pra você também, a [partir deste link](https://www.alura.com.br/promocao/alexfelipe), você tem 10% de desconto na sua assinatura 😉