Cómo trabajar con objetos de datos observables

La observabilidad se refiere a la capacidad de un objeto para notificar a otros sobre cambios en sus datos. La biblioteca de vinculación de datos te permite hacer que objetos, campos o colecciones sean observables.

Puedes usar cualquier objeto para la vinculación de datos, pero modificar el objeto no hace que la IU se actualice automáticamente. Puedes usar la vinculación de datos para darles a tus objetos de datos la capacidad de notificar a otros objetos (conocidos como objetos de escucha) cuando cambian sus datos. Hay tres tipos de clases observables: campos, colecciones y objetos.

Cuando uno de estos objetos de datos observables está vinculado a la IU y una propiedad del objeto de datos cambia, la IU se actualiza automáticamente.

Campos observables

Si tus clases solo tienen algunas propiedades, es posible que no valga la pena crear clases que implementen la interfaz Observable. En este caso, puedes usar la clase genérica Observable y las siguientes clases primitivas específicas para que los campos sean observables:

Los campos observables son objetos observables independientes que tienen un solo campo. Las versiones primitivas evitan la conversión boxing y unboxing durante las operaciones de acceso. Para usar este mecanismo, crea una propiedad public final en el lenguaje de programación Java o una propiedad de solo lectura en Kotlin, como se muestra en el siguiente ejemplo:

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 acceder al valor del campo, usa los métodos de acceso set() y get(), o bien la sintaxis de la propiedad Kotlin:

Kotlin

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

Java

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

Colecciones observables

Algunas apps usan estructuras dinámicas para almacenar datos. Las colecciones observables permiten el acceso a estas estructuras mediante una clave. La clase ObservableArrayMap es útil cuando la clave es un tipo de referencia (por ejemplo, String), como se muestra en el siguiente ejemplo:

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);

En el diseño, puedes encontrar el mapa con las claves de string, como se muestra en el siguiente ejemplo:

<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"/>

La clase ObservableArrayList es útil cuando la clave es un número entero, por ejemplo:

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);

En el diseño, puedes acceder a la lista a través de los índices, como se muestra en el siguiente ejemplo:

<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 observables

Una clase que implementa la interfaz Observable permite registrar objetos de escucha que desean recibir notificaciones sobre cambios de propiedad del objeto observable.

La interfaz Observable tiene un mecanismo para agregar y quitar objetos de escucha, pero tú decides cuándo se envían las notificaciones. Para facilitar el desarrollo, la biblioteca de vinculación de datos proporciona la clase BaseObservable, que implementa el mecanismo de registro del objeto de escucha. La clase de datos que implementa BaseObservable es responsable de notificar cuándo cambian las propiedades. Para ello, asigna una anotación Bindable al método get y llama al método notifyPropertyChanged() en el método set, como se muestra en el siguiente ejemplo:

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);
    }
}

La vinculación de datos genera una clase llamada BR en el paquete del módulo, que contiene los IDs de los recursos utilizados para la vinculación de datos. La anotación Bindable genera una entrada en el archivo de la clase BR durante la compilación. Si la clase base para las clases de datos no se puede cambiar, puedes implementar la interfaz Observable con un objeto PropertyChangeRegistry para registrar y notificar a los objetos de escucha de manera eficiente.

Objetos optimizados para ciclos de vida

Los diseños de tu app también pueden vincularse a fuentes de vinculación de datos que notifican automáticamente a la IU sobre cambios en los datos. De esta manera, tus vinculaciones están optimizadas para el ciclo de vida y solo se activan cuando la IU es visible en la pantalla.

La vinculación de datos admite StateFlow y LiveData. Si quieres obtener más información sobre el uso de LiveData en la vinculación de datos, consulta Cómo usar LiveData para notificar a la IU los cambios en los datos.

Usa StateFlow

Si tu app usa Kotlin con corrutinas, puedes usar objetos StateFlow como fuente de vinculación de datos. Para usar un objeto StateFlow con tu clase de vinculación, especifica un propietario del ciclo de vida para definir el alcance del objeto StateFlow. En el siguiente ejemplo, se especifica la actividad como propietario del ciclo de vida una vez que se crea una instancia de la clase de vinculación:

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

Como se describe en Cómo vincular vistas de diseño con componentes de arquitectura, la vinculación de datos funciona sin problemas con objetos ViewModel. Puedes usar StateFlow y ViewModel juntos de la siguiente manera:

class ScheduleViewModel : ViewModel() {

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

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

En el diseño, asigna las propiedades y los métodos del objeto ViewModel a las vistas correspondientes utilizando expresiones de vinculación, como se muestra en el siguiente ejemplo:

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

La IU se actualiza automáticamente cada vez que cambia el valor del nombre del usuario.

Inhabilita la compatibilidad con StateFlow

Para las apps que utilizan Kotlin y AndroidX, la compatibilidad con StateFlow se incluye automáticamente con la vinculación de datos. Eso significa que la dependencia de corrutinas se incluye automáticamente en tu app si aún no está disponible.

Para inhabilitar esta función, agrega lo siguiente a tu archivo build.gradle:

Groovy

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

Kotlin

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

Como alternativa, puedes inhabilitar StateFlow a nivel global en tu proyecto si agregas la siguiente línea al archivo gradle.properties:

Groovy

android.defaults.databinding.addKtx = false

Kotlin

android.defaults.databinding.addKtx = false

Recursos adicionales

Si deseas obtener más información sobre la vinculación de datos, consulta los siguientes recursos adicionales:

Ejemplos

Codelabs

Entradas de blog