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=true
Layout
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.setContentView
atonCreate
replacingsetContentView
, 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.item
if 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
MutableLiveData
for two way binding (UI input will update data, and vice versa) item
is the actual data model (true source used for data saving),liveItem
is 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 onlyname
is relavant, ascount
is not used for data binding), you need to callcopyItemToLive
to make sureviewModel.liveItem
has the same value. - Changes to
viewModel.item.name
will auto updateviewModel.item
with thesetupObserver
code. If you don't need real-time update, you can skip the observer and copyviewModel.liveItem
toviewModel.item
when 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: