Utiliser les objets de données observables

L'observabilité fait référence à la capacité d'un objet à avertir les autres utilisateurs des modifications apportées à ses données. La bibliothèque Data Binding vous permet de rendre des objets, des champs ou des collections observables.

Vous pouvez utiliser n'importe quel objet pour la liaison de données, mais la modification de l'objet n'entraîne pas automatiquement la mise à jour de l'interface utilisateur. Vous pouvez utiliser la liaison de données pour permettre à vos objets de données d'informer d'autres objets (appelés "écouteurs") lorsque leurs données changent. Il existe trois types de classes observables : les champs, les collections et les objets.

Lorsque l'un de ces objets de données observables est lié à l'UI et qu'une propriété de l'objet de données change, l'UI est automatiquement mise à jour.

Champs observables

Si vos classes ne possèdent que quelques propriétés, il peut s'avérer inutile de créer des classes qui implémentent l'interface Observable. Dans ce cas, vous pouvez utiliser la classe générique Observable et les classes spécifiques aux primitives suivantes pour rendre les champs observables:

Les champs observables sont des objets observables autonomes possédant un seul champ. Les versions primitives évitent le boxing et le déballage lors des opérations d'accès. Pour utiliser ce mécanisme, créez une propriété public final dans le langage de programmation Java ou une propriété en lecture seule en Kotlin, comme illustré dans l'exemple suivant:

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

Pour accéder à la valeur du champ, utilisez les méthodes d'accesseur set() et get(), ou la syntaxe des propriétés Kotlin:

Kotlin

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

Java

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

Collections observables

Certaines applications utilisent des structures dynamiques pour stocker les données. Les collections observables permettent d'accéder à ces structures à l'aide d'une clé. La classe ObservableArrayMap est utile lorsque la clé est un type de référence, tel que String, comme illustré dans l'exemple suivant:

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

Dans la mise en page, vous pouvez trouver la carte à l'aide de clés de chaîne, comme illustré dans l'exemple suivant:

<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 classe ObservableArrayList est utile lorsque la clé est un entier, comme suit:

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

Dans la mise en page, vous pouvez accéder à la liste via les index, comme illustré dans l'exemple suivant:

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

Objets observables

Une classe qui implémente l'interface Observable permet d'enregistrer les écouteurs qui souhaitent être informés des modifications de propriétés de l'objet observable.

L'interface Observable dispose d'un mécanisme permettant d'ajouter et de supprimer des écouteurs, mais vous décidez quand les notifications sont envoyées. Pour faciliter le développement, la bibliothèque Data Binding fournit la classe BaseObservable, qui implémente le mécanisme d'enregistrement de l'écouteur. La classe de données qui implémente BaseObservable est chargée d'envoyer une notification lorsque les propriétés changent. Pour ce faire, attribuez une annotation Bindable au getter et appelez la méthode notifyPropertyChanged() dans le setter, comme illustré dans l'exemple suivant:

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 liaison de données génère une classe nommée BR dans le package de module, qui contient les ID des ressources utilisées pour la liaison de données. L'annotation Bindable génère une entrée dans le fichier de classe BR lors de la compilation. Si la classe de base des classes de données ne peut pas être modifiée, vous pouvez implémenter l'interface Observable à l'aide d'un objet PropertyChangeRegistry pour enregistrer et avertir efficacement les écouteurs.

Objets tenant compte des cycles de vie

Les mises en page de votre application peuvent également être associées à des sources de liaison de données qui informent automatiquement l'interface utilisateur des modifications apportées aux données. Ainsi, vos liaisons tiennent compte du cycle de vie et ne sont déclenchées que lorsque l'interface utilisateur est visible à l'écran.

La liaison de données est compatible avec StateFlow et LiveData. Pour en savoir plus sur l'utilisation de LiveData dans la liaison de données, consultez Utiliser LiveData pour informer l'interface utilisateur des modifications de données.

Utiliser StateFlow

Si votre application utilise Kotlin avec des coroutines, vous pouvez utiliser des objets StateFlow comme source de liaison de données. Pour utiliser un objet StateFlow avec votre classe de liaison, spécifiez un propriétaire du cycle de vie afin de définir le champ d'application de l'objet StateFlow. L'exemple suivant spécifie l'activité en tant que propriétaire du cycle de vie une fois la classe de liaison instanciée:

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

Comme décrit dans la section Lier des vues de mise en page aux composants d'architecture, la liaison de données fonctionne parfaitement avec les objets ViewModel. Vous pouvez utiliser StateFlow et ViewModel ensemble comme suit:

class ScheduleViewModel : ViewModel() {

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

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

Dans votre mise en page, attribuez les propriétés et les méthodes de votre objet ViewModel aux vues correspondantes à l'aide d'expressions de liaison, comme illustré dans l'exemple suivant:

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

L'interface utilisateur est automatiquement mise à jour chaque fois que le nom de l'utilisateur change.

Désactiver la prise en charge de StateFlow

Pour les applications qui utilisent Kotlin et AndroidX, la prise en charge de StateFlow est automatiquement incluse dans la liaison de données. Cela signifie que la dépendance des coroutines est automatiquement incluse dans votre application si elle n'est pas déjà disponible.

Vous pouvez désactiver cette fonctionnalité en ajoutant le code suivant à votre fichier build.gradle:

Groovy

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

Kotlin

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

Vous pouvez également désactiver StateFlow globalement dans votre projet en ajoutant la ligne suivante au fichier gradle.properties:

Groovy

android.defaults.databinding.addKtx = false

Kotlin

android.defaults.databinding.addKtx = false

Ressources supplémentaires

Pour en savoir plus sur la liaison de données, consultez les ressources suivantes:

Exemples

Ateliers de programmation

Articles de blog