Vinculación de datos en dos direcciones

Con la vinculación de datos unidireccional, puedes establecer un valor en un atributo y 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);
        }
    }
}

Como el método get de la propiedad vinculante se llama getRememberMe(), el método set 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 cambios, que puedes usar como parte de tu app. Si deseas usar 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, completa los siguientes 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 con @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 cambian los datos (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 tu vista personalizada o puede ser un evento genérico, como una pérdida de enfoque o un cambio en el 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 InverseBindingListener como parámetro. Usa el 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.

Usuario que generó una conversión

Si la variable que está vinculada a un objeto View necesita formatearse, traducirse o cambiarse de alguna manera antes de mostrarse, es posible usar un objeto Converter.

Por ejemplo, toma 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 usa 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 nuevamente 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 muestra un ejemplo de esta configuración:

Kotlin

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

    @JvmStatic 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 activaría otra llamada al método anotado con @InverseBindingAdapter, y así sucesivamente.

Por este motivo, 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 usas 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.

Ejemplos

Codelabs

Entradas de blog