Vinculação de dados bidirecional

Ao usar a vinculação de dados unidirecional, é possível definir um valor em um atributo e definir um listener que reaja a uma mudança nesse atributo:

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

A vinculação de dados bidirecional fornece um atalho para esse processo:

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

A notação @={}, que inclui o sinal "=", recebe mudanças de dados da propriedade e ouve as atualizações dos usuários ao mesmo tempo.

Para reagir a mudanças nos dados de backup, você pode fazer com que sua variável de layout seja uma implementação de Observable, geralmente BaseObservable, e usar uma anotação @Bindable, conforme mostrado no snippet de código a seguir:

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 o método getter da propriedade vinculável é chamado de getRememberMe(), o método setter correspondente da propriedade usa automaticamente o nome setRememberMe().

Para ver mais informações sobre o uso de BaseObservable e @Bindable, consulte Trabalhar com objetos de dados observáveis.

Vinculação de dados bidirecional usando atributos personalizados

A plataforma oferece implementações de vinculação de dados bidirecional para os atributos bidirecionais e listeners de mudança mais comuns, que podem ser usados como parte do seu app. Se você quiser usar a vinculação de dados bidirecional com atributos personalizados, precisará trabalhar com as anotações @InverseBindingAdapter e @InverseBindingMethod.

Por exemplo, se você quiser ativar a vinculação de dados bidirecional em um atributo "time" em uma visualização personalizada chamada MyView, siga as seguintes etapas:

  1. Anote o método que define o valor inicial e é atualizado quando o valor muda usando @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. Anote o método que lê o valor da visualização 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();
        }

Nesse ponto, a vinculação de dados sabe o que fazer quando os dados mudam (ela chama o método anotado com @BindingAdapter) e o que chamar quando o atributo de visualização muda (ela chama o InverseBindingListener). No entanto, ela não sabe quando ou como o atributo muda.

Para isso, é necessário definir um listener na visualização. Pode ser um listener personalizado associado à sua visualização personalizada ou um evento genérico, como perda de foco ou mudança de texto. Adicione a anotação @BindingAdapter ao método que define o listener de mudanças na propriedade:

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.
    }
    

O listener inclui um InverseBindingListener como parâmetro. O InverseBindingListener é usado para informar ao sistema de vinculação de dados que o atributo mudou. Então, o sistema pode começar a chamar o método anotado usando @InverseBindingAdapter e assim por diante.

Na prática, esse listener inclui uma lógica não trivial, incluindo listeners para vinculação de dados unidirecional. Para ver um exemplo, consulte o adaptador para a mudança de atributo de texto, TextViewBindingAdapter.

Converters

Se a variável vinculada a um objeto View precisa ser formatada, traduzida ou alterada antes de ser exibida, é possível usar um objeto Converter.

Por exemplo, considere um objeto EditText que mostre uma data:

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

O atributo viewmodel.birthDate contém um valor do tipo Long e, portanto, precisa ser formatado usando um conversor.

Como uma expressão bidirecional está sendo usada, também é necessário que haja um conversor inverso para que a biblioteca saiba como converter a string fornecida pelo usuário de volta para o tipo de dados de backup, nesse caso, Long. Esse processo é feito adicionando a anotação @InverseMethod a um dos conversores e fazendo com que essa anotação faça referência ao conversor inverso. Um exemplo dessa configuração é mostrado no snippet de código a seguir:

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.
        }
    }

Loops infinitos usando vinculação de dados bidirecional

Tenha cuidado para não iniciar loops infinitos ao usar a vinculação de dados bidirecional. Quando o usuário muda um atributo, o método anotado usando @InverseBindingAdapter é chamado e o valor é atribuído à propriedade de backup. Isso, por sua vez, chamaria o método anotado usando @BindingAdapter, o que acionaria outra chamada para o método anotado usando @InverseBindingAdapter e assim por diante.

Por esse motivo, é importante quebrar possíveis loops infinitos comparando valores novos e antigos dos métodos anotados usando @BindingAdapter.

Atributos bidirecionais

A plataforma oferece compatibilidade integrada para vinculação de dados bidirecional quando os atributos são usados na tabela a seguir. Para ver informações sobre como a plataforma oferece essa compatibilidade, consulte as implementações dos adaptadores de vinculação correspondentes:

Classe Atributos Adaptador de vinculação
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

Outros recursos

Para saber mais sobre vinculação de dados, consulte os seguintes recursos adicionais.

Amostras

Codelabs

Publicações do blog