View Binding Android

View Binding Android
Alex Felipe Victor Vieira
Alex Felipe Victor Vieira

Compartilhe

Neste artigo, vamos explorar o View Binding, uma técnica com o objetivo de substituir a forma como buscamos views no Android, seja pelo findViewById(), ou até mesmo, pelo Synthetic do Kotlin Android Extension que agora é obsoleto.

Projeto de exemplo

Para demonstrar os exemplos, vamos utilizar o Todo, um App capaz de cadastrar e listar tarefas:

  

As implementações foram feitas da seguinte maneira:

  • Lista de tarefas em uma Activity com um RecyclerView
  • Formulário de criação de tarefa em um Fragment

O motivo de utilizar essas entidades, é demonstrar as diferentes implementações com o View Binding.

Caso tenha interesse em acompanhar o conteúdo aplicando os exemplos, você pode acessar o repositório do projeto no GitHub.

Por que utilizar o View Binding?

Como vimos, o View Binding trata-se de uma alternativa para buscar Views do Android, porém, por padrão, temos acesso ao findViewById(). Então, por que deveríamos considerar o View Binding?

Os principais motivos para o uso do View Binding são:

  • Escrever um código mais fácil para interagir com as Views
  • Obter mais segurança ao acessar uma View:
    • segurança de nulo: o View Binding cria diretamente as referências com a View, portanto, não há risco de buscar uma View que não existe no layout.
    • segurança de tipo: cada campo encontrado no layout pelo View Binding, mantém o mesmo tipo apresentado no arquivo de layout, dessa forma, evitamos qualquer problema de casting.

Agora que conhecemos os motivos para utilizar o View Binding, vamos começar com a configuração.

Configurando o View Binding no projeto Android

O View Binding é configurado por módulo de um projeto Android, portanto, dentro do arquivo build.gradle do módulo App, adicionamos a seguinte instrução:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

Basta sincronizar que o View Binding está configurado! Simples assim. 🙂

Como o View Binding funciona?

Após habilitar o View Binding, automaticamente, ele gerará classes que representam cada arquivo de layout do módulo no seguinte padrão: nomeDoLayoutBinding.

Dentro do projeto temos os seguintes arquivos de layout:

  • formulario_nota_activity.xml
  • formulario_nota_fragment.xml
  • item_nota.xml
  • lista_notas_activity.xml

Isso significa que ao configurar o View Binding, ele gera as seguintes classes para os layouts:

  • FormularioNotaActivityBinding
  • FormularioNotaFragmentBinding
  • ItemNotaBinding
  • ListaNotasActivity

A partir dessas referências, podemos acessar as views de cada layout considerando os destaques do View Binding.

Para começar o nosso exemplo, vamos acessar a ListaNotasActivity:

class ListaNotasActivity : AppCompatActivity(R.layout.lista_notas_activity) {

    // restante do código

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val recyclerView = findViewById<RecyclerView>(R.id.lista_notas_activity_recyclerview)
        recyclerView.adapter = adapter
        configuraFab()
    }

    private fun configuraFab() {
        val fab = findViewById<ExtendedFloatingActionButton>(R.id.lista_notas_activity_fab)
        fab.setOnClickListener {
            // restante do código
        }
    }

}

Note que utilizamos o findViewById() para buscar o RecyclerView e o ExtendedFloatingActionButton. Dado esse exemplo, vamos adaptar o código para usar o View Binding.

Utilizando o View Binding na Activity

Para utilizar o View Binding, primeiro precisamos realizar o processo de inflar a View. Para isso, no onCreate(), acesse a referência ListaNotasBinding e chame o método inflate() enviando o layoutInflater da Activity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ListaNotasActivityBinding.inflate(layoutInflater)
    // restante do código
}

Observe que criamos a variável binding, esse padrão de nomeação é comum quando realizamos o processo de binding de View com o View Binding.

A partir da variável binding, podemos acessar as views, como, por exemplo, o RecyclerView:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ListaNotasActivityBinding.inflate(layoutInflater)
    val recyclerView = binding.listaNotasActivityRecyclerview
    recyclerView.adapter = adapter
    configuraFab()
}

E para ter um acesso por todos os membros, podemos até mesmo tornar o binding uma property lazy:

class ListaNotasActivity : AppCompatActivity(R.layout.lista_notas_activity) {

    private val binding by lazy {
        ListaNotasActivityBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val recyclerView = binding.listaNotasActivityRecyclerview
        // restante do código
    }

    // restante do código

    private fun configuraFab() {
        val fab = binding.listaNotasActivityFab
        // restante do código
    }

}

Se testarmos o App apenas com essa modificação, o RecyclerView e o FAB não funcionam! Isso acontece porque precisamos vincular a View do View Binding com a Activity, para isso, podemos chamar o método setContentView() enviando o root do binding

class ListaNotasActivity : AppCompatActivity() {

    private val adapter by lazy {
        ListaNotasAdapter(this)
    }
    private val binding by lazy {
        ListaNotasActivityBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val recyclerView = binding.listaNotasActivityRecyclerview
        recyclerView.adapter = adapter
        configuraFab()
        setContentView(binding.root)
    }

        // restante do código

}

A partir do momento que utilizamos o View Binding, é importante remover a referência do layout no construtor da Activity ou em um setContentView().

Com esse ajuste, o nosso código funciona sem qualquer problema!

Implementando o View Binding no RecyclerView

No ListaNotasAdapter do RecyclerView, o uso do ViewBinding é feito durante a criação do ViewHolder. A técnica é similar ao da Activity, mas seguindo os padrões de inflate do adapter:

class ListaNotasAdapter(
    private val context: Context,
    notas: List<Nota> = listOf()
) : RecyclerView.Adapter<ListaNotasAdapter.ViewHolder>() {

    private val notas = notas.toMutableList()

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {
                val binding = ItemNotaBinding
            .inflate(
                LayoutInflater.from(context),
                parent,
                false
            )
        return ViewHolder(binding)
    }

    // restante do código

    class ViewHolder(binding: ItemNotaBinding) :
        RecyclerView.ViewHolder(binding.root) {

        private val titulo = binding.itemNotaTitulo
        private val descricao = binding.itemNotaDescricao

        fun vincula(nota: Nota) {
            titulo.text = nota.titulo
            descricao.text = nota.descricao
        }

    }

}

Observe que a grande diferença é que o ViewHolder recebe o View Binding do layout desejado, nesse caso, o ItemNotaBinding. Então, é enviada a view com o binding.root para o construtor do RecyclerView.ViewHolder() e o parâmetro binding pode ser utilizado para buscar as views desejadas.

Utilizando o View Binding no Fragment

No Fragment temos um cenário diferente. Pois, devido ao processo de recriar a View cada vez que ele é acessado, somos responsáveis em criar o View Binding e limpá-lo quando a view é destruída:

class FormularioNotaFragment : Fragment() {

    private var _binding: FormularioNotaFragmentBinding? = null
    private val binding: FormularioNotaFragmentBinding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FormularioNotaFragmentBinding
            .inflate(
                inflater,
                container,
                false
            )
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val botao = binding.formularioNotaBotaoSalvarFragment
        botao.setOnClickListener {
            salvaNota()
        }
    }

    private fun salvaNota() {
        val campoTitulo = binding.formularioNotaTituloFragment
        val titulo = campoTitulo.text.toString()
        val campoDescricao = binding.formularioNotaDescricaoFragment
        val descricao = campoDescricao.text.toString()
        val nota = Nota(titulo, descricao)
        NotaDao().salva(nota)
        activity?.onBackPressed()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}

Da mesma maneira que foi feito na Activity, no Fragment também é importante remover a referência de layout no construtor.

Essa possível implementação é sugerida na página da documentação, e explica que mesmo usando o null assertion operator (!!), bastante perigoso por conta de NPE, não corremos o risco de NPE se acessarmos o binding nos estados de ciclo de vida do Fragment entre onCreateView() e onDestroyView():

  

Com base no fluxograma, podemos acessar o binding a partir do onViewCreated(), até o onSaveInstanceState().

Tempo de build e ignorando layouts com View Binding

Por gerar código, o View Binding também custa tempo de build que, segundo a documentação, por não ter anotações durante a configuração, ele é mais rápido que alternativas como o Data Binding.

Além disso, se preferir evitar a geração de código em layouts específicos, seja pelo tempo de build ou por não usar referências do layout, como é o caso da FormularioNotaActivity, que tem apenas um container de Fragment, podemos ignorar layouts adicionando tools:viewBindingIgnore="true" no root da View do layout.

Considerando o exemplo da FormularioNotaActivity, fica da seguinte maneira:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.activity.FormularioNotasActivity"
    tools:viewBindingIgnore="true">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/formulario_notas_fragment_container_activity"
        android:name="br.com.alura.todo.ui.fragment.FormularioNotaFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Com esse ajuste, a classe FormularioNotaActivity não é mais gerada pelo processo de build do projeto.

Conclusão

O View Binding é a técnica mais recente e recomenda pela equipe do Android, portanto, se atualmente você utiliza o synthetic, o recomendado é que migre para o View Binding. Caso mantenha o uso apenas do findViewById(), você pode considerar o uso do View Binding, pois, além de facilitar a busca das views, também existe o benefício de evitar NPE ou problemas de casting.

Código fonte desenvolvido

Caso tenha interesse em consultar as mudanças do projeto, confira este commit.

Alex Felipe Victor Vieira
Alex Felipe Victor Vieira

Alex é instrutor e desenvolvedor e possui experiência em Java, Kotlin, Android. Criador de mais de 40 cursos, como Kotlin, Flutter, Android, persistência de dados, comunicação com Web API, personalização de telas, testes automatizados, arquitetura de Apps e Firebase. É expert em Programação Orientada a Objetos, visando sempre compartilhar as boas práticas e tendências do mercado de desenvolvimento de software. Atuou 2 anos como editor de conteúdo no blog da Alura e hoje ainda escreve artigos técnicos.

Veja outros artigos sobre Mobile