Android Jetpack — Data Binding, ViewModel e LiveData
Fala Droids!
Vejo que existe muita dúvida em como usar o DataBinding com LiveData, uma vez que o natural é encontrar implementações observando seus dados da ViewModel direto no código da Activity/Fragment.
E se existisse uma maneira de atualizar sua tela de forma reativa sem a necessidade de ter que colocar um milhão de observers no código da sua Activity/Fragment ?
Sim. Existe. Trabalhando com DataBinding em conjunto com os components do Architecture Components LiveData e ViewModel, você ganha esse poder!
Nesse artigo vou criar um app simples que mostrará um contador de cliques.
Vamos criar um novo projeto no Android Studio 3.1+
Criando o projeto
1 — Crie um projeto com Empty Activity
2 — Selecione Kotlin como linguagem e escolha o nome do projeto que achar melhor
Habilitando o DataBinding
Com o projeto criado, vamos habilitar o DataBinding no build.gradle (app)
apply plugin: 'kotlin-kapt'android {
...
dataBinding {
enabled = true
}
}
De o sync e pronto. Já podemos usar o DataBinding no app.
Agora, abra sua tela, activity_main.xml, gerada na criação do projeto. Esse arquivo representa a relacionada a sua MainActivity.
Se você passar o mouse em cima do seu layout root, o Android Studio vai mostrar a "lâmpadazinha" com a opção de converter sua tela para um layout de DataBinding.
É exatamente isso que faremos. Clique nessa opção.
Seu código irá se transformar nesse:
<?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">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
O que aconteceu ?
Primeiro, seu layout root, no caso o ConstraintLayout, foi envolvido por uma tag layout, que é do DataBindg. Isso indica que você agora pode trabalhar com variáveis dentro do seu layout.
<?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">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
...
A tag <data></data> é onde você vai criar suas variáveis que serão usadas para popular os valores na tela. No nosso caso, de forma reativa usando o LiveData.
Inclua um Button e um TextView na tela. Pode colocar na posição que achar melhor. O foco aqui não é o design.
Mas se quiser pode seguir o que criei, segue abaixo:
<?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">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_clique_me"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Pronto. Por hora, vamos deixar nossa tela assim.
ViewModel
Antes de criarmos nossa ViewModel, precisamos adicionar a dependência de Lifecycle do Architecture Components ao nosso projeto.
Abra o arquivo build.gradle (app) novamente, adicione a dependência abaixo e de sync.
dependencies {
...
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
}
Top. Agora sim, crie uma classe Kotlin chamada MainViewModel.
class MainViewModel: ViewModel() {
private var number = 0 private val _count: MutableLiveData<String> =
MutableLiveData("0") val count: LiveData<String>
get() = _count
fun counter() {
_count.value = (++number).toString()
}
}
Gzuis!! O que rolou ??
Primeiro, para que nossa classe utilize os poderes do Architecture Component ViewModel, nossa classe precisa herdar a classe ViewModel.
class MainViewModel: ViewModel() {
...
}
Logo em seguida, criamos os estados/variáveis que terão seu valor/dado vivo apresentando a quantidade de cliques na nossa tela de forma.
Seguindo as boas práticas, não queremos que nosso LiveData mutável possa ser alterado fora da ViewModel, então definimos ela como privada.
private val _count: MutableLiveData<String> =
MutableLiveData("0")
"Ainnn, Tiago.. você é noob.. como vou acessar o valor dela se a variável é privada ??"
Simples. Criamos uma outra variável imutável (LiveData) que irá expor apenas o valor a ser lido/observado.
val count: LiveData<String>
get() = _count
E para finalizar, criamos uma função que usa da variável number para atualizar nosso MutableLiveData.
...private var number = 0...fun counter() {
_count.value = (++number).toString()
}
Nossa ViewModel esta pronta.
DataBinding + LiveData
Nosso objetivo agora é ver o DataBinding fazendo um dueto com o LiveData.
Vamos voltar para nosso layout/tela. Abra o arquivo xml activity_main.xml.
<?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">
<data>
<variable
name="viewModel"
type="br.com.ht7.databindviewmodel.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
...
android:text="@{viewModel.count}"
... />
<Button
...
android:onClick="@{() -> viewModel.counter()}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Vamos por partes:
Primeiro, criamos uma variável do tipo MainViewModel.
<variable
name="viewModel"
type="br.com.ht7.databindviewmodel.MainViewModel" />
Depois, é onde a magia acontece.. conseguimos usar essas variáveis nas View apenas usando a sintaxe @{minhaVariavel} ou @{minhaVariavel.campo} caso seja um objeto, no lugar da string.
<TextView
...
android:text="@{viewModel.count}"
... />
Para o Button, podemos usar a propriedade onClick e invocar nosso método criado na ViewModel através da variável DataBinding criada no layout.
<Button
...
android:onClick="@{() -> viewModel.counter()}"
... />
Show, nossa tela esta pronta!
Último passo - Preparando nossa Activity
Primeiro vamos ver o código como ficou.
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 tipo ActivityMainBinding é criado automaticamente pelo DataBinding ao habilita-lo no projeto, para todas as Activities criadas.
private lateinit var binding: ActivityMainBinding
Utilizando o DataBindUtils no lugar do setContentView, nos possibilita a usar todo o poder do DataBinding.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
Agora, vamos prover nossa ViewModel na Activity e passar ela para nosso DatabaBind.
Utilizando a versão do lifecycle 1.1.0, podemos prover a ViewModel da seguinte forma:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
...
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
...
}
}
Na versão 2.2.0, a implementação ficou um pouco diferente. O contexto é passado via construtor e não temos mais o of.
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
...
}
Mas existe uma maneira muito mais interessante de injetar as ViewModels.
Para isso, usaremos algumas sintaxes de Java 8. Adicione a seguinte configuração em build.gradle (app).
android {
kotlinOptions {
jvmTarget = '1.8'
}
dataBinding {
enabled = true
}
}
Adicione agora a dependência:
dependencies {
...
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.fragment:fragment-ktx:1.2.4'
...
}
Agora, injetamos na declaração da variável de forma muito simples usando a sintaxe by viewModels().
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
...
}
Agora vamos juntar os pontos e prover o ViewModel para o DataBinding.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
Primeiro, passamos nossa ViewModel injetada na variável viewModel criada na activity_main.xml.
binding.viewModel = viewModel
Depois dizemos para o DataBinding, quem será responsável pelo ciclo de vida desse DataBinding.
binding.lifecycleOwner = this
Show.. estamos prontos para testar.
Vendo o Resultado
Ao executar o app, podemos ver que tudo esta funcionando!
Parabéns e obrigado por ter chego até aqui!!
Vocês podem baixar o código do projeto no meu Github.