Vinculação bidirecional de dados

Ao usar a vinculação unidirecional de dados, é 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 bidirecional de dados 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 apoio, é possível tornar sua variável de layout uma implementação de Observable, geralmente BaseObservable, e usar um @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 como usar BaseObservable e @Bindable, consulte Trabalhar com objetos de dados observáveis.

Vinculação bidirecional de dados usando atributos personalizados

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

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

  1. Anote o método que define o valor inicial e atualiza quando o valor é alterado 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 momento, a vinculação de dados sabe o que fazer quando os dados são alterados (ela chama o método anotado com @BindingAdapter) e o que chamar quando o atributo de visualização mudar (ele chama o InverseBindingListener). No entanto, ele 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 as alterações do listener 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. Use o InverseBindingListener para informar ao sistema de vinculação de dados que o atributo mudou. O sistema pode então 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 unidirecional de dados. Para ver um exemplo, consulte o adaptador da alteração de atributo de texto, TextViewBindingAdapter.

Converters

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

Por exemplo, use 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. Portanto, ele precisa ser formatado usando um conversor.

Como uma expressão bidirecional está sendo usada, também é necessário haver um conversor inverso para permitir que a biblioteca saiba como converter a string fornecida pelo usuário de volta ao tipo de dados de backup, neste caso Long. Esse processo é feito adicionando a anotação @InverseMethod a um dos conversores e fazendo com que ela 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")
        @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.
        }
    }

Loops infinitos usando vinculação de dados bidirecional

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

Por esse motivo, é importante interromper possíveis loops infinitos comparando valores novos e antigos nos 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

Postagens do blog