Two-way data binding
Stay organized with collections
Save and categorize content based on your preferences.
Using one-way data binding, you can set a value on an attribute and set a listener that reacts to a change in that attribute:
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>Two-way data binding provides a shortcut to this process:
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"
/>The @={} notation, which importantly includes the "=" sign, receives data
changes to the property and listen to user updates at the same time.
In order to react to changes in the backing data, you can make your layout
variable an implementation of Observable, usually
BaseObservable, and use a
@Bindable annotation, as shown in
the following code snippet:
Kotlin
classLoginViewModel:BaseObservable{ // val data = ... @Bindable fungetRememberMe():Boolean{ returndata.rememberMe } funsetRememberMe(value:Boolean){ // Avoids infinite loops. if(data.rememberMe!=value){ data.rememberMe=value // React to the change. saveData() // Notify observers of a new value. notifyPropertyChanged(BR.remember_me) } } }
Java
publicclass LoginViewModelextendsBaseObservable{ // private Model data = ... @Bindable publicBooleangetRememberMe(){ returndata.rememberMe; } publicvoidsetRememberMe(Booleanvalue){ // Avoids infinite loops. if(data.rememberMe!=value){ data.rememberMe=value; // React to the change. saveData(); // Notify observers of a new value. notifyPropertyChanged(BR.remember_me); } } }
Because the bindable property's getter method is called getRememberMe(), the
property's corresponding setter method automatically uses the name
setRememberMe().
For more information on using BaseObservable and @Bindable, see Work with
observable data objects.
Two-way data binding using custom attributes
The platform provides two-way data binding implementations for the most common
two-way attributes and change listeners, which you can use
as part of your app. If you want to use two-way data binding with custom
attributes, you need to work with the
@InverseBindingAdapter
and
@InverseBindingMethod
annotations.
For example, if you want to enable two-way data binding on a "time" attribute
in a custom view called MyView, complete the following steps:
Annotate the method that sets the initial value and updates when the value changes using
@BindingAdapter:Kotlin
@BindingAdapter("time") @JvmStaticfunsetTime(view:MyView,newValue:Time){ // Important to break potential infinite loops. if(view.time!=newValue){ view.time=newValue } }
Java
@BindingAdapter("time") publicstaticvoidsetTime(MyViewview,TimenewValue){ // Important to break potential infinite loops. if(view.time!=newValue){ view.time=newValue; } }
Annotate the method that reads the value from the view using
@InverseBindingAdapter:Kotlin
@InverseBindingAdapter("time") @JvmStaticfungetTime(view:MyView):Time{ returnview.getTime() }
Java
@InverseBindingAdapter("time") publicstaticTimegetTime(MyViewview){ returnview.getTime(); }
At this point, data binding knows what to do when the data changes (it calls the
method annotated with
@BindingAdapter) and what to
call when the view attribute changes (it calls the
InverseBindingListener).
However, it doesn't know when or how the attribute changes.
For that, you need to set a listener on the view. It can be a custom listener
associated with your custom view, or it can be a generic event, such as a loss
of focus or a text change. Add the @BindingAdapter annotation to the method
that sets the listener for changes on the property:
Kotlin
@BindingAdapter("app:timeAttrChanged") @JvmStaticfunsetListeners( view:MyView, attrChange:InverseBindingListener ){ // Set a listener for click, focus, touch, etc. }
Java
@BindingAdapter("app:timeAttrChanged") publicstaticvoidsetListeners( MyViewview,finalInverseBindingListenerattrChange){ // Set a listener for click, focus, touch, etc. }
The listener includes an InverseBindingListener as a parameter. You use the
InverseBindingListener to tell the data binding system that the attribute has
changed. The system can then start calling the method annotated using
@InverseBindingAdapter, and so on.
In practice, this listener includes some non-trivial logic, including listeners
for one-way data binding. For an example, see the adapter for the text attribute
change,
TextViewBindingAdapter.
Converters
If the variable that's bound to a View object
needs to be formatted, translated, or changed somehow before being displayed,
it's possible to use a Converter object.
For example, take an EditText object that shows a date:
<EditText
android:id="@+id/birth_date"
android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>
The viewmodel.birthDate attribute contains a value of type Long, so it needs
to be formatted by using a converter.
Because a two-way expression is being used, there also needs to be an inverse
converter to let the library know how to convert the user-provided string back
to the backing data type, in this case Long. This process is done by adding
the @InverseMethod annotation
to one of the converters and have this annotation reference the inverse
converter. An example of this configuration appears in the following code
snippet:
Kotlin
objectConverter{ @InverseMethod("stringToDate") @JvmStaticfundateToString( view:EditText,oldValue:Long, value:Long ):String{ // Converts long to String. } @JvmStaticfunstringToDate( view:EditText,oldValue:String, value:String ):Long{ // Converts String to long. } }
Java
publicclass Converter{ @InverseMethod("stringToDate") publicstaticStringdateToString(EditTextview,longoldValue, longvalue){ // Converts long to String. } publicstaticlongstringToDate(EditTextview,StringoldValue, Stringvalue){ // Converts String to long. } }
Infinite loops using two-way data binding
Be careful not to introduce infinite loops when using two-way data binding. When
the user changes an attribute, the method annotated using
@InverseBindingAdapter is called, and the value is assigned to the backing
property. This, in turn, would call the method annotated using
@BindingAdapter, which would trigger another call to the method annotated
using @InverseBindingAdapter, and so on.
For this reason, it's important to break possible infinite loops by comparing
new and old values in the methods annotated using @BindingAdapter.
Two-way attributes
The platform provides built-in support for two-way data binding when you use the attributes in the following table. For details on how the platform provides this support, see the implementations for the corresponding binding adapters:
| Class | Attribute(s) | Binding adapter |
|---|---|---|
AdapterView
|
android:selectedItemPositionandroid:selection |
AdapterViewBindingAdapter
|
CalendarView |
android:date |
CalendarViewBindingAdapter
|
CompoundButton |
android:checked
|
CompoundButtonBindingAdapter
|
DatePicker
|
android:yearandroid:monthandroid:day |
DatePickerBindingAdapter
|
NumberPicker |
android:value
|
NumberPickerBindingAdapter
|
RadioButton
|
android:checkedButton |
RadioGroupBindingAdapter
|
RatingBar
|
android:rating
|
RatingBarBindingAdapter
|
SeekBar
|
android:progress |
SeekBarBindingAdapter
|
TabHost
|
android:currentTab |
TabHostBindingAdapter
|
TextView
|
android:text
|
TextViewBindingAdapter
|
TimePicker
|
android:hourandroid:minute |
TimePickerBindingAdapter
|
Additional resources
To learn more about data binding, consult the following additional resources.
Samples
Codelabs
Blog posts
Recommended for you
- Note: link text is displayed when JavaScript is off
- Work with observable data objects
- Layouts and binding expressions
- Bind layout views to Architecture Components