Integrate ViewModel with DataBinding
This avoids us the need to explicitly declare .setOnClickListener()
manually and integrate it into the view.
Up until now, this was our MainActivity
following MVVM architecture.
class MainActivity: AppCompatActivity() {
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
viewModel.count.observe(this, Observer {
binding.countText.text = it.toString()
})
// we will remove methods such as this one
binding.button.setOnClickListener {
viewModel.updateCount()
}
}
}
We’re going to connect the ViewModel object with the DataBinding object and declare the method directly inside the .xml
.
Open the layout activity_main.xml
, for this case, and add <data>
tags to assign the view model Object we created there. Be careful as the .xml
layouts need to be surrounded by <layout>
tags.
<layout>
<data>
<variable
name="myViewModel"
type="es.codes.mario.templates.MainActivityViewModel"/>
</data>
</layout>
We go to the buttons declaration inside the same .xml
file and change the onClick=""
variable. This will bind the expression.
(!) To do this, our gradle version has to be higher than 2.0
(!)
<Button
...
android:onClick="@{()->myViewModel.updateCount()}"/>
Now we go back to our class, add the code to bind it, and remove the code for the listener.
class MainActivity: AppCompatActivity() {
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
// add this here to bind it
binding.myViewModel = viewModel
viewModel.count.observe(this, Observer {
binding.countText.text = it.toString()
})
// delete setOnClickListener method
}
}
Integrate LiveData with DataBinding
This avoids us the need to explicitly declare the Observer
.
class MainActivity: AppCompatActivity() {
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
binding.myViewModel = viewModel
// now we will remove methods such as this one
viewModel.count.observe(this, Observer {
binding.countText.text = it.toString()
})
}
}
Instead we will use LiveData as a DataBinding source in the .xml
layout file.
We want to display the following count
variable, but it’s an Int
. To display it, we need to convert that Int
to String
.
class MainActivityViewModel: ViewModel() {
var count = MutableLiveData<Int>()
...
}
We go to activity_main.xml
and declare android:text
tag. Then we will use LiveData directly as the DataBinding source.
<TextView
...
android:text="@{String.valueOf(myViewModel.count)}"
... />
There’s one special thing left to do. LiveData is always associated with the lifecycle of an activity or a service, so we have to provide the actual lifecycle owner to the view model object. In MainActivity we need to set the current activity as the lifecycle owner of the binding object.
class MainActivity: AppCompatActivity() {
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
// add this
binding.lifecycleOwner = this
binding.myViewModel = viewModel
}
}
encapsulate data
If we want to encapsulate data for more security, we may declare it as private
and create a public
variable to access it.
class MainActivityViewModel: ViewModel() {
private var count = MutableLiveData<Int>()
val countData: LiveData<Int>
get() = count
...
}
We must also change the .xml
value.
<TextView
...
android:text="@{String.valueOf(myViewModel.countData)}"
... />
Two way data binding
We use one-way DataBinding to show the user some data or get user input.
With two way data binding when the value of the object changes, the UI changes, and when the UI changes, the value of the object changes.
We have the following ViewModel
.
class MainActivityViewModel: ViewModel() {
val userName = MutableLiveData<String>()
init {
userName.value = "Frank"
}
}
Open main_activity.xml
and surround the existing contents by <layout>
tags.
Write <data>
tags to reference MainActivityViewModel
.
Bind the value of userName MutableLiveData
to the TextView
.
<layout>
<data>
<variable
name="viewModel"
type="es.codes.mario.templates.MainActivityViewModel"/>
</data>
<android.constraintlayout.widget.ConstraintLayout>
<TextView
...
android:text="@{viewModel.userName}"
... />
</android.constraintlayout.widget.ConstraintLayout>
</layout>
Go to MainActivity
and
- Define a DataBinding object.
- Replace
setContentView()
. - Define a reference variable for the view model.
- Since we are using livedata with data binding, set this activity as the lifecycle owner.
class MainActivity: AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
}
Up until here this is one way data binding. The view displays LiveData values.
For two way data binding, bind the data in an EditText
the same way, but use the expression @={}
instead of @{}
.
<layout>
<data>
<variable
name="viewModel"
type="es.codes.mario.templates.MainActivityViewModel"/>
</data>
<android.constraintlayout.widget.ConstraintLayout>
<TextView
...
android:text="@{viewModel.userName}"
... />
<EditText
...
android:text="@={viewModel.userName}"
... />
</android.constraintlayout.widget.ConstraintLayout>
</layout>