See also README in https://github.com/lingxuxiong/JetpackGo2/tree/databinding
Introduction
The Data Binding Library is a support library that allows you to bind
UI components in your layouts to data sources in your app using a
declarative format rather than programmatically.
Assumptions
- Android Studio IDE: v4.1.1
- Android Gradle Plugin: v4.1.1
- Kotlin & Plugin Version: 1.4.10
Terms And Abbr.
- View Binding (VB)
Binding layout views that has a unique ID to a generated file so that
the views can be accessed handily. It can be replacementfindViewById
calls.
- Data Binding (DB)
Binding layout views to model objects so that the bound objects can be
changed directly from the layout, and vice versus, changes on the objects
can be applied to the layout views automatically. Data Binding can be
one-way (V -> M) or Two-way (V <-> M), depending on the binding expression
used.@{}
for one-way binding and@={}
for two-way binding, respectively.While the two-way binding is expected most of the time and it requires the
model data observable and wrapped withLiveData
. As we can see,
one-way binding is kind of static binding, whereas two-way binding
is dynamic binding.
- Observability
Objects that are observable and are capable of notifying UI updates. It
can be implemented as observable fields, Live Data, or observable classes.
How to use
- Enable data-binding from the app Gradle file
plugins {
// If you plan to use data binding in a Kotlin project,
// you should apply the kotlin-kapt plugin
id 'kotlin-kapt'
}
android {
// before AGP 4
buildFeatures {
dataBinding true
}
}
Or
// on before AGP 4+
dataBinding {
enabled true
}
Covert regular layout file to data binding layout Shortcut:
right-click on the root element of the layout file, then choose
"Convert to data binding layout"Inflates root view in a data binding way.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.greeting = "Hello DataBinding"
}
- Changes the value of variables bound
binding.greeting = "Hello DataBinding"
Note: As view binding can coexist with data binding, so be careful enough to NOT mix a VB variable with a DB variable, otherwise you might not get the expected results. For example:
binding.name = viewModel.name // assigns to a DB String variable, named `name`, supporting two-way binding. -> UI will refresh accordingly
binding.plain_name.text = viewModel.name // assigns to a VB TextView variable, named `plain_name`, supporting only one-way binding. -> UI won't refresh accordingly
- Steps to implement ViewModel binding
- Adds dependencies to lifecycle View Model, if not yet
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
- Creates custom ViewModel class
class GreetingViewModel: ViewModel() {
private val _greeting = MutableLiveData("Hello data binding")
val greeting: LiveData<String> = _greeting
fun changeGreeting(greetingMessage: String) {
_greeting.value = greetingMessage
}
}
- Declares ViewModel variable
<data>
<variable name="vm" type="vm.GreetingViewModel" />
</data>
- Uses layout expression to change view attributes
<TextView>
android:text="@{vm.greeting}"
</TextView>
- Inflates and binds ViewModel object
private val viewModel: GreetingViewModel by lazy {
ViewModelProvider(this).get(GreetingViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.vm = viewModel
}
- Changes ViewModel value to refresh UI
override fun onResume() {
super.onResume()
viewModel.changeGreeting("Changed greeting: Haha")
}
- Accepts user input (e.g. click event) and refresh UI elements accordingly
- Defines responding function
fun changeRandomGreeting() { val idx = Random.Default.nextInt(famousSayings.size) _greeting.value = famousSayings[idx] }
- Binds the responding function to specific user event
<TextView> android:onClick="@{() -> vm.changeRandomGreeting()}" </TextView>
-
Adds lifecycle owner of the binding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.vm = viewModel // Sets the {@link LifecycleOwner} that should be used for observing changes of // LiveData in this binding. If a {@link LiveData} is in one of the binding expressions // and no LifecycleOwner is set, the LiveData will not be observed and updates to it // will not be propagated to the UI. check ViewDataBinding.setLifecycleOwner() for more details binding.lifecycleOwner = this }
Note: If failing to add binding lifecycle then the UI won't get refreshed.
- Defines responding function
References
- Android Doc. Data Binding Library Overview. 12/08/2020
- Jose Alcérreca(Jalc). Codelabs - Data Binding in Android. 12/08/2020
- Jose Alcérreca(Jalc), Jeremy Walker. Android Data Binding Library samples. 12/09/2020
- Jose Alcérreca(Jalc). Android Data Binding Library — from Observable Fields to LiveData in two steps. 12/08/2020
- Google Git. Databinding Adapters Classes List. 12/09/2020
- Chris Banes. Data Binding — lessons learned. 12/09/2020
Q & A
-
What's Data Binding Layout
The layout file with data binding enabled. It consists of two sections -
the data section and view section, for layout variables and UI elements, respectively.<layout> <data> <!-- definition of data variables goes here --> </data> <ViewRoot> <!-- view declaration goes here --> </ViewRoot> </layout>
-
What's Layout Variable
Variables defined in the layout file and used to bilaterally exchange
info between the model and view. -
What's Binding Expression
An regular Kotlin/Java expression that starts with a
@
sign and is
wrapped inside curly braces {}. It operates on layout variables and is
placed in the layout file as applicable element attribute values.Basic expression examples:
<!-- Some examples of complex layout expressions --> <TextView> android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}' </TextView>
ViewModel-bound expression examples:
<TextView> android:text="@{viewmodel.name}" <!-- Bind the nameVisible property of the viewmodel to the visibility attribute --> android:visibility="@{viewmodel.nameVisible}" <!-- Call the onLike() method on the viewmodel when the View is clicked. --> android:onClick="@{() -> viewmodel.onLike()}" </TextView>
-
What's Binding Adapter
Binding Adapter is a
@BindingAdapter
annotated static method which
gets called WHEN the binding variable get changed to update the target
view defined by this adapter. For example:Definitions of binding adapter
// This is a built-in binding adapter @BindingAdapter("android:text") // 1 @JvmStatic fun setText(view: TextView, text: CharSequence) { // 2 view.setText(text); // 3 } // This is a custom binding adapter @BindingAdapter("app:hideIfZero") @JvmStatic fun hideIfZero(view: View, number: Int) { view.visibility = if (number == 0) View.GONE else View.Visible } // This is a multi-attributes binding adapter, which is called // whenever any of the bound variables of the attributes get changed. // It won't be used if any of the attributes are missing, which is // determined at compile time. Refer BindingAdapter#requireAll for // more details about the default attribute values. // Generally, the requireAll parameter defines when the binding adapter is used: // When true, all elements must be present in the XML definition. // When false, the missing attributes will be null, false if booleans or 0 if primitives. @BindingAdapter(value = ["app:progressScaled", "android:max"], requireAll = true) @JvmStatic fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) { progressBar.progress = (likes * max / 5).coerceAtMost(max) }
Triggers of the binding adapter
<ViewGroup> <TextView> android:text="@{vm.name}" </TextView> <ProgressBar> app:hideIfZero="@{vm.likes}" app:progressScaled="@{vm.likes}" android:max="@{100}" // 5 </ProgressBar> </ViewGroup>
- 1: Annotates a Binding Adapter and the view attribute it applies to.
- 2: Specifies the binding view and applicable type of value to the attribute.
- 3: Implements how the value is applied to change the view attribute.
- 4: In Kotlin, static methods can be created by adding functions to the top
level of a Kotlin file or as extension functions on the class. - 5: Use a literal integer for a binding expression, otherwise, the DB won't find the defined adapter
What's Binding Class