Why Use Data Binding?
Usually I am not a fan of data binding, as there are too many "magic" (hard to debug).
One thing I like though is two-way binding, where the value changed in EditText is updated to the backing data/variable. Basically I have reactivity like React/Vue.js, where UI input changes the data automatically and vice versa.
Android's data binding is pretty flexible as well, supporting evaluations and expressions (e.g. android:transitionName='@{"Hello " + name}') or use data binding to change visibility (e.g. android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}").
If you need something more complex there are adapters, but I prefer not to introduce so much complexity.
Setup Data Binding
Configuration
Edit app module build.gradle.
android {
...
dataBinding {
enabled = true
}
}Edit gradle.properties.
android.databinding.enableV2=trueLayout
Change the layout file of Activity/Fragment.
- Adding
<layout>as root tag. - Add
<data>tag for variables to be used in data binding - Use
android:text='@{VARIABLE_NAME}'to show the value
<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="com.luasoftware.pixpin.view.AlbumViewViewModel"/>
<variable name="test" type="String" />
</data>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.AlbumViewActivity">
...
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:text='@{test}'
/>
</android.support.design.widget.CoordinatorLayout>
</layout>If you are using <include> tag, do the following to pass the variable.
<include layout="@layout/albumview_content" app:viewModel="@{viewModel}" />You need to declare <layout> and <data> in albumview_content.xml as well.
<?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="com.luasoftware.pixpin.view.AlbumViewViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.liveItem.name}"/>
<EditText
android:id="@+id/testEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.liveItem.name}"/>
</LinearLayout>
</layout>Code (Activity, ViewModel, LiveData)
Edit Activity class.
- Call
DataBindingUtil.setContentViewatonCreatereplacingsetContentView, andbinding.setLifecycleOwner(If you are using LiveData for data binding, not necessary if you are using observable). - Assign variable for data binding through
binding.VARIABLE_NAME = .... - Observe changes on
viewModel.liveItem.name(used for data binding), and updateviewModel.itemif changes detected
class AlbumViewActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // replace setContentView with DataBindingUtil.setContentView // setContentView(R.layout.albumview_activity) // AlbumviewActivityBinding is auto generated based on the layout file (albumview_activity.xml) // val binding = DataBindingUtil.setContentView<AlbumviewActivityBinding>(this, R.layout.albumview_activity) val binding = AlbumviewActivityBinding.inflate(layoutInflater) binding.setLifecycleOwner(this) // must call this // I am using Dagger2 to get ViewModel, you can stick with whatever way you are using viewModel = ViewModelProviders.of(this, viewModelFactory).get(AlbumViewViewModel::class.java) // assign variable for data binding binding.viewModel = viewModel binding.test = "Hello World" setupObserver() // create data val item = Album(name = "Test", count=0) viewModel.copyItemToLive(item) } private fun setupObserver() { // make sure changed on viewModel.liveItem (used for data binding) will update viewModel.item // viewModel.item is the true source used for saving the data viewModel.liveItem.name.observe(this, Observer { it?.also { viewModel.item.name = it } }) }}Code of ViewModel.
- I use
MutableLiveDatafor two way binding (UI input will update data, and vice versa) itemis the actual data model (true source used for data saving),liveItemis the reactive model (a bridge between UI input and the actual data model -item)
NOTE: Binding to LiveData require Android Studio 3.1 with com.android.tools.build:gradle:3.1.x
data class Album(val name: String, val count: Int = 0)// I remove my dagger2 injection code// class AlbumViewViewModel @Inject constructor(private val dataSource: AlbumDao): ViewModel() {class AlbumViewViewModel : ViewModel() { inner class LiveItem { val name = MutableLiveData<String>() } var item: Album = Album(name="Original") val liveItem: LiveItem = LiveItem() fun copyItemToLive(item: Album) { // store data this.item = item // copy to liveItem liveItem.apply { name.value = item.name } } fun save() { // save item to database }}Assumption and behaviour
- If changes is made to
viewModel.item(for this case onlynameis relavant, ascountis not used for data binding), you need to callcopyItemToLiveto make sureviewModel.liveItemhas the same value. - Changes to
viewModel.item.namewill auto updateviewModel.itemwith thesetupObservercode. If you don't need real-time update, you can skip the observer and copyviewModel.liveItemtoviewModel.itemwhen saving the data.
Layout Binding
test is a regular binding (android:text='@{test}') to a string value (it can be an object as well).
<variable name="test" type="String" /><TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:text='@{test}'
/>viewModel.liveItem.name is a regular binding (android:text="@{viewModel.liveItem.name}) to a LiveData.
Whenever the value of LiveData changed, the view shall be updated.
You can observe viewModel.liveItem.name for changes.
<variable name="viewModel" type="com.luasoftware.pixpin.view.AlbumViewViewModel"/>class AlbumViewViewModel : ViewModel() {
inner class LiveItem {
val name = MutableLiveData<String>()
}
val liveItem: LiveItem = LiveItem()
}<TextView
android:id="@+id/testTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.liveItem.name}"/>viewModel.liveItem.name (a LiveData) can be used for 2-way binding (android:text="@={viewModel.liveItem.name}").
Whenever the EditText value changed, viewModel.liveItem.name is updated (if viewModel.liveItem.name is binded to another TextView, TextView value will change as well).
<EditText
android:id="@+id/testEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.liveItem.name}"/>References: