Trabalhar com objetos de dados observáveis

Observabilidade refere-se à capacidade de um objeto de notificar outras pessoas sobre mudanças nos dados dele. A Data Binding Library permite tornar objetos, campos ou coleções observáveis.

Você pode usar qualquer objeto para vinculação de dados, mas a modificação do objeto não faz com que a IU seja atualizada automaticamente. É possível usar a vinculação de dados para que seus objetos de dados notifiquem outros objetos, conhecidos como listeners, quando os dados mudarem. Há três tipos de classes observáveis: campos, coleções e objetos.

Quando um desses objetos de dados observáveis é vinculado à interface e uma propriedade do objeto de dados muda, a interface é atualizada automaticamente.

Campos observáveis

Caso suas classes tenham apenas algumas propriedades, talvez não valha a pena criar classes que implementem a interface Observable. Nesse caso, você pode usar a classe genérica Observable e as classes específicas de primitivos abaixo para tornar os campos observáveis:

Os campos observáveis são objetos observáveis independentes que têm um único campo. As versões primitivas evitam o boxe e o unboxing durante as operações de acesso. Para usar esse mecanismo, crie uma propriedade public final na linguagem de programação Java ou uma propriedade somente leitura em Kotlin, conforme mostrado no exemplo abaixo:

Kotlin

class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

Java

private static class User {
    public final ObservableField<String> firstName = new ObservableField<>();
    public final ObservableField<String> lastName = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}

Para acessar o valor do campo, use os métodos do acessador set() e get() ou use a sintaxe de propriedade do Kotlin:

Kotlin

user.firstName = "Google"
val age = user.age

Java

user.firstName.set("Google");
int age = user.age.get();

Coleções observáveis

Alguns apps usam estruturas dinâmicas para reter dados. As coleções observáveis permitem acessar essas estruturas usando uma chave. A classe ObservableArrayMap é útil quando a chave é um tipo de referência, por exemplo, String, conforme mostrado no exemplo a seguir.

Kotlin

ObservableArrayMap<String, Any>().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}

Java

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

No layout, você encontra o mapa usando as chaves de string, como mostrado no exemplo a seguir:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
    android:text="@{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text="@{String.valueOf(1 + (Integer)user.age)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

A classe ObservableArrayList é útil quando a chave é um número inteiro, como mostrado a seguir:

Kotlin

ObservableArrayList<Any>().apply {
    add("Google")
    add("Inc.")
    add(17)
}

Java

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

No layout, é possível acessar a lista pelos índices, conforme mostrado no exemplo a seguir:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
    android:text='@{user[Fields.LAST_NAME]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Objetos observáveis

Uma classe que implementa a interface Observable permite o registro de listeners que querem ser notificados sobre mudanças de propriedade do objeto observável.

A interface Observable tem um mecanismo para adicionar e remover listeners, mas você decide quando as notificações são enviadas. Para facilitar o desenvolvimento, a biblioteca Data Binding fornece a classe BaseObservable, que implementa o mecanismo de registro de listener. A classe de dados que implementa BaseObservable é responsável por notificar quando as propriedades mudam. Para fazer isso, atribua uma anotação Bindable ao getter e chame o método notifyPropertyChanged() no setter, conforme mostrado no exemplo a seguir.

Kotlin

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}

Java

private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

A vinculação de dados gera uma classe chamada BR no pacote do módulo, que contém os IDs dos recursos usados para a vinculação de dados. A anotação Bindable gera uma entrada no arquivo de classe BR durante a compilação. Se não for possível mudar a classe base das classes de dados, implemente a interface Observable usando um objeto PropertyChangeRegistry para registrar e notificar os listeners de forma eficiente.

Objetos com reconhecimento de ciclo de vida

Os layouts do app também podem se vincular a fontes de vinculação de dados que notificam automaticamente a interface sobre mudanças nos dados. Dessa forma, as vinculações reconhecem o ciclo de vida e só são acionadas quando a interface está visível na tela.

A vinculação de dados oferece suporte a StateFlow e LiveData. Para saber mais sobre o uso de LiveData na vinculação de dados, consulte Usar LiveData para notificar a interface sobre mudanças de dados.

Usar o StateFlow

Se o app usa o Kotlin com corrotinas, você pode usar objetos StateFlow como a fonte de vinculação de dados. Para usar um objeto StateFlow com sua classe de vinculação, especifique um proprietário do ciclo de vida para definir o escopo do objeto StateFlow. O exemplo a seguir especifica a atividade como a proprietária do ciclo de vida após a classe de vinculação ser instanciada:

class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Inflate view and obtain an instance of the binding class.
        val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

        // Specify the current activity as the lifecycle owner.
        binding.lifecycleOwner = this
    }
}

Conforme descrito em Vincular visualizações de layout a componentes de arquitetura, a vinculação de dados funciona perfeitamente com objetos ViewModel. Você pode usar StateFlow e ViewModel juntos da seguinte maneira:

class ScheduleViewModel : ViewModel() {

    private val _username = MutableStateFlow<String>("")
    val username: StateFlow<String> = _username

    init {
        viewModelScope.launch {
            _username.value = Repository.loadUserName()
        }
    }
}

No layout, atribua as propriedades e os métodos do objeto ViewModel às visualizações correspondentes usando expressões de vinculação, conforme mostrado no exemplo a seguir:

<TextView
    android:id="@+id/name"
    android:text="@{viewmodel.username}" />

A interface é atualizada automaticamente sempre que o valor do nome do usuário muda.

Desativar o suporte ao StateFlow

Para apps que usam o Kotlin e o AndroidX, o suporte ao StateFlow é incluído automaticamente na vinculação de dados. Isso significa que a dependência de corrotinas será incluída automaticamente no app se ela ainda não estiver disponível.

Você pode desativar essa funcionalidade adicionando o seguinte ao seu arquivo build.gradle:

Groovy

android {
    ...
    dataBinding {
        addKtx = false
    }
}

Kotlin

android {
    ...
    dataBinding {
        addKtx = false
    }
}

Como alternativa, você pode desativar StateFlow globalmente no seu projeto adicionando a seguinte linha ao arquivo gradle.properties:

Groovy

android.defaults.databinding.addKtx = false

Kotlin

android.defaults.databinding.addKtx = false

Outros recursos

Para saber mais sobre a vinculação de dados, consulte os recursos a seguir:

Exemplos

Codelabs

Postagens do blog