Vinculación de datos en dos direcciones

Con la vinculación de datos unidireccional, puedes establecer un valor en un atributo y establecer un objeto de escucha que reaccione a un cambio en ese atributo:

    <CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:checked="@{viewmodel.rememberMe}"
        android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
    />
    

La vinculación de datos bidireccional proporciona un acceso directo a este proceso:

    <CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:checked="@={viewmodel.rememberMe}"
    />
    

La notación @={}, que incluye el signo "=", recibe cambios en los datos de la propiedad y escucha las actualizaciones de los usuarios al mismo tiempo.

Para reaccionar a los cambios en los datos de copia de seguridad, puedes hacer que tu variable de diseño sea una implementación de Observable, generalmente BaseObservable, y usar una anotación @Bindable, como se muestra en el siguiente fragmento de código:

Kotlin

    class LoginViewModel : BaseObservable {
        // val data = ...

        @Bindable
        fun getRememberMe(): Boolean {
            return data.rememberMe
        }

        fun setRememberMe(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

    public class LoginViewModel extends BaseObservable {
        // private Model data = ...

        @Bindable
        public Boolean getRememberMe() {
            return data.rememberMe;
        }

        public void setRememberMe(Boolean value) {
            // 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);
            }
        }
    }

Debido a que el método captador de la propiedad vinculante se llama getRememberMe(), el método establecedor correspondiente de la propiedad usa automáticamente el nombre setRememberMe().

Para obtener más información sobre el uso de BaseObservable y @Bindable, consulta Cómo trabajar con objetos de datos observables.

Vinculación de datos bidireccional con atributos personalizados

La plataforma proporciona implementaciones de vinculación de datos bidireccional para los atributos bidireccionales más comunes y objetos de escucha de cambio, que puedes usar como parte de la app. Si deseas utilizar la vinculación de datos bidireccional con atributos personalizados, debes trabajar con las anotaciones @InverseBindingAdapter y @InverseBindingMethod.

Por ejemplo, si deseas habilitar la vinculación de datos bidireccional en un atributo "time" en una vista personalizada llamada MyView, sigue estos pasos:

  1. Anota el método que establece el valor inicial y se actualiza cuando el valor cambia con @BindingAdapter:

    Kotlin

        @BindingAdapter("time")
        @JvmStatic fun setTime(view: MyView, newValue: Time) {
            // Important to break potential infinite loops.
            if (view.time != newValue) {
                view.time = newValue
            }
        }

    Java

        @BindingAdapter("time")
        public static void setTime(MyView view, Time newValue) {
            // Important to break potential infinite loops.
            if (view.time != newValue) {
                view.time = newValue;
            }
        }
  2. Anota el método que lee el valor de la vista usando @InverseBindingAdapter:

    Kotlin

        @InverseBindingAdapter("time")
        @JvmStatic fun getTime(view: MyView) : Time {
            return view.getTime()
        }

    Java

        @InverseBindingAdapter("time")
        public static Time getTime(MyView view) {
            return view.getTime();
        }

En este punto, la vinculación de datos sabe qué hacer cuando los datos cambian (llama al método anotado con @BindingAdapter) y a qué llamar cuando cambia el atributo de vista (llama a InverseBindingListener). Sin embargo, no sabe cuándo ni cómo cambia el atributo.

Para eso, necesitas establecer un objeto de escucha en la vista. Puede ser un objeto de escucha personalizado asociado con la vista personalizada o puede ser un evento genérico, como una pérdida de enfoque o un cambio de texto. Agrega la anotación @BindingAdapter al método que establece el objeto de escucha para los cambios en la propiedad:

Kotlin

    @BindingAdapter("app:timeAttrChanged")
    @JvmStatic fun setListeners(
            view: MyView,
            attrChange: InverseBindingListener
    ) {
        // Set a listener for click, focus, touch, etc.
    }

Java

    @BindingAdapter("app:timeAttrChanged")
    public static void setListeners(
            MyView view, final InverseBindingListener attrChange) {
        // Set a listener for click, focus, touch, etc.
    }
    

El objeto de escucha incluye un parámetro InverseBindingListener como parámetro. Usas InverseBindingListener para indicarle al sistema de vinculación de datos que el atributo cambió. Luego, el sistema puede comenzar a llamar al método anotado usando @InverseBindingAdapter, y así sucesivamente.

En la práctica, este objeto de escucha incluye alguna lógica no trivial, incluidos los objetos de escucha para la vinculación de datos unidireccional. Para ver un ejemplo, consulta el adaptador del cambio de atributo de texto, TextViewBindingAdapter.

Convertidores

Si la variable vinculada a un objeto View debe formatearse, traducirse o modificarse de alguna manera antes de mostrarse, es posible usar un objeto Converter.

Por ejemplo, tomemos un objeto EditText que muestra una fecha:

<EditText
        android:id="@+id/birth_date"
        android:text="@={Converter.dateToString(viewmodel.birthDate)}"
    />
    

El atributo viewmodel.birthDate contiene un valor de tipo Long, por lo que debe formatearse con un convertidor.

Debido a que se está utilizando una expresión bidireccional, también debe haber un convertidor inverso para que la biblioteca sepa cómo convertir la string proporcionada por el usuario de nuevo al tipo de datos de copia de seguridad (en este caso, Long). Este proceso se realiza agregando la anotación @InverseMethod a uno de los convertidores y haciendo que esta anotación haga referencia al convertidor inverso. En el siguiente fragmento de código, se incluye un ejemplo de este proceso:

Kotlin

    object Converter {
        @InverseMethod("stringToDate")
        fun dateToString(
            view: EditText, oldValue: Long,
            value: Long
        ): String {
            // Converts long to String.
        }

        fun stringToDate(
            view: EditText, oldValue: String,
            value: String
        ): Long {
            // Converts String to long.
        }
    }

Java

    public class Converter {
        @InverseMethod("stringToDate")
        public static String dateToString(EditText view, long oldValue,
                long value) {
            // Converts long to String.
        }

        public static long stringToDate(EditText view, String oldValue,
                String value) {
            // Converts String to long.
        }
    }

Bucles infinitos con vinculación de datos bidireccional

Ten cuidado de no introducir bucles infinitos cuando uses la vinculación de datos bidireccional. Cuando el usuario cambia un atributo, se llama al método anotado usando @InverseBindingAdapter, y el valor se asigna a la propiedad de copia de seguridad. Esto, a su vez, llamaría al método anotado usando @BindingAdapter, que activa otra llamada al método anotado usando @InverseBindingAdapter, y así sucesivamente.

Por esa razón, es importante romper posibles bucles infinitos comparando valores nuevos y antiguos en los métodos anotados con @BindingAdapter.

Atributos bidireccionales

La plataforma proporciona compatibilidad integrada para la vinculación de datos bidireccional cuando utilizas los atributos de la siguiente tabla. Para obtener detalles sobre cómo la plataforma proporciona esta compatibilidad, consulta las implementaciones para los adaptadores de vinculación correspondientes:

Clase Atributo(s) Adaptador de vinculación
AdapterView android:selectedItemPosition
android:selection
AdapterViewBindingAdapter
CalendarView android:date CalendarViewBindingAdapter
CompoundButton android:checked CompoundButtonBindingAdapter
DatePicker android:year
android:month
android: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:hour
android:minute
TimePickerBindingAdapter

Recursos adicionales

Para obtener más información sobre la vinculación de datos, consulta los siguientes recursos adicionales.

Muestras

Codelabs

Entradas de blog (en inglés)