Desenvolvimento

15 mai, 2019

Começando com Data Binding

Publicidade

Este artigo foi publicado originalmente em: https://www.concrete.com.br/2019/05/07/comecando-com-data-binding/

***

Você que acompanha o portal já deve ter ouvido falar do Android JetPack, um conjunto de componentes, ferramentas e boas práticas de Android que nos ajuda a implementar apps de forma mais rápida, com fácil manutenção e mais testáveis.

Se você não conhece, dê uma olhada neste artigo da Izabela Araújo, que apresenta uma ótima introdução sobre o assunto.

Neste artigo falaremos um pouco sobre Data Binding, uma biblioteca que vincula (mapeia) e facilita o binding da lógica e o layout da Activity, e que faz parte do pacote Android Jetpack – mais precisamente da camada de Architecture Components.

O Data Binding é uma biblioteca distinta do sistema operacional e pode ser utilizada a partir do Android 2.1 (API 7). A ferramenta é bastante poderosa, mas neste artigo vou mostrar só os primeiros passos para utilizá-la.

No projeto de exemplo vamos usar a linguagem Kotlin, configurar um projeto inicial e criar um simples formulário.

Lembrando que, como o foco aqui é um gatilho para dar os primeiros passos nessa biblioteca, o formulário não vai ter nenhum tipo de validação de campo, requisição ao servidor ou configuração de bancos de dados locais e/ou externos, ok?

Se você tiver interesse, o código deste projeto está no meu GitHub.

Configurando o projeto

Para que o projeto possa suportar essa biblioteca, é necessário habilitá-lo. Faremos da seguinte forma:

1. Abra o arquivo build.gradle à nível de aplicação (módulo do aplicativo).
2. Dentro do elemento Android, adicione o seguinte elemento:

groovy
dataBinding {
enabled = true
}

E pronto!

Após fazer essa pequena alteração e sincronizar, nosso projeto vai suportar o Data Binding.

Criando nossa classe modelo

Criaremos nossa classe modelo simples, só para o nosso aprendizado e nomearemos como “User”. Nela, vamos colocar os atributos de nome e e-mail. Fica assim:

data class User(val nome: String, val email: String)

Criando layout do formulário

Primeiro vou mostrar o layout XML do formulário sem a implementação da biblioteca. Nele, teremos dois campos de digitação, nome e e-mail:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="4dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edNome"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/nome"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edEmail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/email"
app:layout_constraintTop_toBottomOf="@+id/edNome"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/cadastrar"
app:layout_constraintTop_toBottomOf="@+id/edEmail"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Agora que criamos nosso formulário, faremos uma pequena alteração.

Implementando o Data Binding

Adicionaremos uma tag chamada layout, que será nossa tag parent(root) do nosso XML. Fica assim:

<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
//...>
//...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

E é isso! Esse será o layout para podermos usar nossa biblioteca. Em seguida, implementaremos o elemento data e eu vou explicar pra quê serve e como utilizar.

<layout>
<data></data>
//...
</layout>

Bora entender para que servem essas tags que foram adicionadas no layout?

Com essa alteração, será gerada uma classe binding (classe de ligação) automaticamente e que, por padrão, tem a nomenclatura do nome da ViewName + Binding, e no final vai gerar uma classe ActivityMainBinding.

Mas de onde vem o nome dessa classe? Do layout XML que criamos: activity_main.xml.

Ou seja, se criarmos um layout XML chamado de activity_register.xml e fizermos os mesmos procedimentos acima, a biblioteca vai gerar um arquivo chamado ActivityRegisterBinding.

Elemento data

No elemento data é possível inserir imports de classes como, por exemplo, a nossa classe User. Mas e se você quiser importar outra classe que não seja uma classe modelo, é possível? Sim! Podemos importar, por exemplo, a classe nativa View.

Você pode criar condições para que uma view ou view group sejam visíveis ou não para o usuário. Poderíamos usar as constantes View.Gone, View.VISIBLE e, entre outras coisas, utilizamos a tag import.

<layout>
<data>
<import type="android.view.View"/>
</data>
//...
</layout>

Agora que a classe View foi importada, podemos fazer uma pequena validação de algum campo.

No exemplo abaixo, no EditText, vou validar se o usuário é adulto. Caso seja, o campo será visível e a propriedade visibility vai receber o valor da constante VISIBLE. Caso não seja, a propriedade recebe o valor GONE e não será visível para o usuário.

<layout>
<data>
<import type="android.view.View"/>
</data>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edNome"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/nome"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
</layout>

No elemento data podemos criar variáveis para que sejam utilizadas dentro do nosso layout, com objetos instanciados.

Para isso, devemos usar a tag variable dentro da tag data, na qual temos a propriedade name, que nada mais é do que o nome do objeto que vamos trabalhar dentro do XML.

Após essa alteração, faremos o build do projeto e um método será gerado automaticamente, com um objeto do tipo que será declarado na propriedade como type.

É neste objeto que dizemos qual será o tipo dessa variável e passamos a classe ou, para ser mais preciso, todo o caminho no qual a classe se encontra. Para o exemplo, vamos usar a classe modelo que criamos: a User. Segue o trecho do código:

<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="br.com.concrete.databinding.User"/>
</data>
//...
</layout>

Depois de termos criado nossa variável, vamos à anotação no elemento para podermos atribuir um valor para ele. Para isso, implementamos uma anotação @={…} na propriedade android:text=”” do elemento. Ficando assim:

<layout>
<data>
<variable name="user" type="br.com.concrete.databinding.User"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
//...>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edNome"
android:text="@={user.nome}"
//.../>
//...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Note que na anotação e entre as chaves @={}, eu chamo a variável user. Nela, declaramos a tag e a utilizamos como um objeto para acessarmos seus atributos.

Dessa forma, podemos dar um set no campo de digitação ou um get para obter os dados que foram digitados.

Note também que após a anotação @ temos o sinal de igual =. Com ele, podemos receber alterações de dados e observar as alterações feitas ou não pelo usuário.

Isso é o que chamamos de Two-Way, mas para funcionar devemos criar uma classe binding, com atributos ObservableFields. Mais adiante falaremos mais sobre isso.

Observação: depois de ter feito essa alteração, em alguns casos o Android Studio não consegue gerar automaticamente essa classe, então é preciso dar um Clean Project e depois um Rebuild Project, ou até mesmo um Invalidate Caches/Restart. Com esses procedimentos as classes serão geradas normalmente.

Vinculação de dados

Depois de todo aquele pequeno processo, vamos para a activity, que vamos vincular ao layout XML.
Para isso, vamos agora inflar o layout utilizando o método setContentView da classe DataBindingUtil que foi gerada quando fizemos a alteração no layout.

Fica assim:

1. Primeiro vamos remover o método padrão da activity setContentView
2. Depois, criamos uma instância da classe ActivityMainBinding

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
}

O setContentView() foi substituído pelo DataBindingUtil.setContentView(), e esse trecho de código é a nova forma de inflarmos o layout XML e fazer a vinculação do layout e o objeto que vamos criar.

Agora que vinculamos a Activity e o layout XML, vamos instanciar um objeto da classe User, passando como parâmetros o nome e e-mail de um usuário qualquer e, em seguida, fazer o binding entre eles.

Assim:

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
//...
val user = User("Usuario", "usuario@concrete.com.br")
binding.user = user
}
}

Note que, ao chamar a variável binding, chamamos também o método user (em Java seria binding.setUser(user)), que é criado automaticamente a partir do name da variável que criamos no layout XML.

<layout>
<data>
<variable name="user" type="br.com.concrete.databinding.User"/>
</data>
//...
</layout>

Em seguida, passamos como parâmetro o objeto user para o método e ele faz toda a mágica acontecer, exibindo os dados nos campos de digitação.

Fica assim:

Observe que os dados do usuário estão no nosso layout e não foi necessário atribuir os dados do objeto user um a um nos elementos da activity – eles foram inseridos automaticamente nas views.

Nos tópicos anteriores vimos como é fácil atribuir os dados de um objeto nos elementos de uma activity, mas será que é possível formar um objeto com os dados da view no momento em que o usuário preenche os campos? Sim! Vamos ver agora como se faz.

Observáveis

Um observável é uma instância da classe Observable que emite um fluxo de dados e/ou eventos que podemos usar para emitir um dado na view ou executar alguma ação.

Por que usar observáveis? Para tirar da nossa lista de preocupações a criação de um algoritmo de atualização de dados na view.

Tipos de ObservableFields

No Data Binding podemos trabalhar com os ObservableFields, que como o próprio nome já diz, são objetos observáveis e se parecem com um listener que fica observando (ouvindo) alguma mudança de entrada do usuário em um campo da tela. Os tipos de ObservableFields são os seguintes:

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable
  • ObservableField

Vamos criar uma classe binding chamada UserBinding, na qual será implementado o ObservableField. Ela vai ficar assim:

import androidx.databinding.ObservableField
class UserBinding(
val nome: ObservableField<String> = ObservableField(""),
val email: ObservableField<String> = ObservableField("")
)

Por que criar uma classe binding?

Para poder encapsular os tipos em relação à nossa classe modelo. Se na nossa tela tivéssemos mais campos, a classe binding iria “espelhar” os campos como atributos, e também para não precisar usar uma classe modelo, que poderia ter regras de negócios que não seriam necessárias para apenas atualizar dados na view.

​Por exemplo:

<layout
//...>
<data>
//...
</data>
<androidx.constraintlayout.widget.ConstraintLayout
//...>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edNome"
android:text="@={user.nome}"
//... />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edEmail"
android:text="@={user.email}"
//... />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edIdade"
android:text="@={user.idade}"
//... />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edSenha"
android:text="@={user.senha}"
//... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Nossa classe binding ficará assim:

import androidx.databinding.ObservableField

class UserBinding(
val nome: ObservableField,
val email: ObservableField,
val idade: ObservableField,
val senha: ObservableField
)

Após criar a classe UserBinding, vamos fazer um refactoring na aplicação.

Refactoring

Nossa primeira alteração será no layout XML, no qual vamos alterar o tipo da variável na tag, na propriedade type=”” e ainda no type vamos passar a classe binding que criamos.

Assim:

<layout>
<data>
<variable name="user" type="br.com.concrete.databinding.UserBinding"/>
</data>
//...
</layout>

Lembrando que não é necessário fazer alteração no resto do layout, já que a classe UserBinding possui os mesmos atributos que a classe modelo User.

Agora vamos alterar a activity. Não vamos instanciar a classe User, mas sim a classe UserBinding e passar os valores em seus parâmetros.

Fica assim:

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
//...
binding.user = UserBinding(
ObservableField("Usuario"),
ObservableField("usuario@email.com.br"))
}
}
  • O que fizemos? Passamos para o método binding.user o objeto da classe binding, que antes recebia uma instância da classe User. Note que, para passar um valor para os parâmetros do construtor, instanciamos um ObservableField e entre parênteses passamos o valor desejado do tipo que implementamos na classe binding.
  • Note também que os dados foram inseridos normalmente.

Observação: É provável que no momento em que trocarmos o tipo da classe em nosso layout XML, instanciarmos a classe binding na activity e atribuirmos o objeto no método binding.user, a IDE reclame que o tipo de objeto não é compatível com o que o método está querendo receber.

É preciso dar um Clean Project e depois um Rebuild Project. Se necessário, até mesmo um Invalidate Caches/Restart.

Agora que fizemos essa alteração no código, que tal testarmos a funcionalidade da alteração de algum dado do EditText, para vermos se ele é alterado no objeto sem a necessidade de setá-lo manualmente?

Para isso, vou adicionar dois TextViews que vão receber o objeto user (aquela variável que criamos no XML) e atribuir nesses elementos. Você vai notar que apenas alterando/digitando os dados do EditText tudo será alterado automaticamente.

1. Implementando os dois TextViews no layout XML (activity_main):

<?xml version="1.0" encoding="utf-8"?>
<layout
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">
//...
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvNome"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@{user.nome}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvEmail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@{user.email}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvNome"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Pronto!

Após termos implementado esses dois elementos, estamos prontos para testar e vermos que depois de ter feito alguma alteração em ambos os campos (EditText), o objeto user será atualizado e consequentemente os TextViews também.

Veja abaixo:

Graças ao Observable, atualizamos o objeto e a nossa tela, sem a necessidade de chamar cada elemento e setar os dados e até mesmo sem precisar de algum evento de click, por exemplo.

E para obter esse objeto binding, como faríamos? Neste exemplo, vamos obter o objeto depois do click do botão cadastrar. É bastante simples – basta chamar a variável binding que está na activity e chamar o mesmo método a que atribuímos o objeto. Ele servirá como um get().

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
//...
binding.user = UserBinding(
ObservableField("Usuario"),
ObservableField("usuario@email.com.br"))

button.setOnClickListener {
val userBinding = binding.user
userBinding?.let {
Log.d("LOG-NOME", userBinding.nome.get())
Log.d("LOG-EMAIL", userBinding.email.get())
}
}
}
}

Criamos um Log apenas para poder exibir os valores, e depois de pressionar o botão, o log foi criado e os dados foram exibidos.

É possível usar em um Fragment? Mais uma vez: sim! Vamos ver um exemplo.

Fragment com Data Binding

Criei um simples fragment chamado MainFragment. A partir dele vou fazer um pequeno refactoring. Abaixo temos o código da classe e do XML:

class MainFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
}
class MainFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
}

Após a criação do fragment, vamos fazer um pequeno refactoring para que utilizarmos o Data Binding. Não vou entrar muito em detalhes, pois o conceito é o mesmo da Activity.

Refactoring

Primeiro faremos aquela alteração no XML que já conhecemos. Fica assim:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="br.com.concrete.databinding.UserBinding"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainFragment">
<TextView
android:id="@+id/tvNome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="@{user.nome}" />
<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/tvNome"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="@{user.email}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Depois de alterar o layout, é necessário buildar o projeto.

Agora vamos alterar o fragment:

class MainFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentMainBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)

binding.user = UserBinding(
ObservableField("Usuario"),
ObservableField("usuario@email.com.br")
)

return binding.root
}

}

Observe que inflamos o layout chamando o método estático inflate() da classe DataBindingUtil, e é a partir dele que fazemos o binding.

Esse método retorna uma instância ViewDataBinding do layout inflado. Passamos por parâmetro o LayoutInflater, o arquivo de layout, a ViewGroup e um boolean.

Após ter inflado, chamamos a variável binding e atribuímos uma instância da classe UserBinding, passando nome e e-mail do usuário, como fizemos no exemplo anterior com Activity.

Entretanto, o método onCreateView() precisa de uma View como retorno, e a própria biblioteca tem essa função que retorna essa view – a chamada root.

Chamamos esse método a partir da variável binding e atribuímos no return do método onCreateView(). Pronto! Esse foi um pequeno exemplo de como usar a biblioteca com fragment.

E é isso, pessoal!

Vimos neste artigo os conceitos básicos do Data Binding e o que podemos fazer com ele. Se você quiser saber mais sobre essa library, recomendo dar uma olhada na documentação oficial, neste link.

Lembrando que este projeto está disponível no meu Github. Dê uma olhada!

Logo eu volto para falar sobre o Data Binding com projetos mais complexos e exemplos práticos. Tem alguma dúvida ou contribuição? Use os campos abaixo.

Até a próxima!