Présentation de LiveData Inclus dans Android Jetpack.

LiveData est une classe observable de conteneur de données. Contrairement à une classe observable classique, la classe LiveData tient compte du cycle de vie, c'est-à-dire qu'elle respecte le cycle de vie des autres composants d'application, comme les activités, les fragments ou les services. Cette prise en compte du cycle de vie permet de garantir que LiveData ne met à jour que les observateurs de composants d'application qui sont actifs.

LiveData considère un observateur, représenté par la classe Observer, comme étant actif si son cycle de vie présente l'état STARTED ou RESUMED. LiveData informe les observateurs actifs des modifications uniquement. Les observateurs inactifs enregistrés pour la surveillance des objets LiveData ne sont pas informés des modifications.

Vous pouvez enregistrer un observateur associé à un objet qui implémente l'interface LifecycleOwner. Cette relation permet de supprimer l'observateur lorsque l'état de l'objet Lifecycle correspondant passe à DESTROYED. Cela est particulièrement utile pour les activités et les fragments, car ils peuvent observer des objets LiveData de façon sécurisée sans se soucier des fuites : le suivi cesse instantanément pour les activités et les fragments lorsque leur cycle de vie est détruit.

Pour en savoir plus sur l'utilisation de LiveData, consultez la page Utiliser des objets LiveData.

Avantages de LiveData

L'utilisation de LiveData offre les avantages suivants :

Correspondance entre l'UI et l'état de vos données
LiveData suit le modèle d'observateur. LiveData informe les objets Observer des modifications de données sous-jacentes. Vous pouvez consolider votre code pour mettre à jour l'interface utilisateur (UI) dans ces objets Observer. Ainsi, vous n'avez pas besoin de mettre à jour l'UI chaque fois que les données de l'application changent, car l'observateur le fait pour vous.
Aucune fuite de mémoire
Les observateurs sont liés aux objets Lifecycle et nettoient derrière eux une fois leur cycle de vie associé détruit.
Aucun plantage en raison d'activités interrompues
Si le cycle de vie de l'observateur est inactif, dans le cas d'une activité dans la pile "Retour" par exemple, il ne reçoit aucun événement LiveData.
Plus aucune gestion manuelle du cycle de vie
Les composants de l'UI observent simplement les données pertinentes, et ne terminent pas, ni ne reprennent l'observation. LiveData gère automatiquement tout cela, car elle tient compte des changements pertinents concernant l'état du cycle de vie du projet pendant l'observation.
Données toujours à jour
Si un cycle de vie devient inactif, il reçoit les données les plus récentes lorsqu'il est réactivé. Par exemple, une activité qui se trouvait en arrière-plan reçoit les données les plus récentes dès qu'elle repasse au premier plan.
Changements de configuration appropriés
Si une activité ou un fragment sont recréés en raison d'une modification de la configuration (telle qu'une rotation de l'appareil par exemple), ils reçoivent immédiatement les dernières données disponibles.
Partage des ressources
Vous pouvez développer un objet LiveData à l'aide du modèle Singleton pour encapsuler les services système afin qu'ils puissent être partagés dans votre application. L'objet LiveData se connecte une seule fois au service système, et tout observateur ayant besoin de la ressource peut simplement surveiller l'objet LiveData. Pour en savoir plus, consultez la section Développer LiveData.

Utiliser des objets LiveData

Pour utiliser des objets LiveData :

  1. Créez une instance de LiveData pour stocker un certain type de données. Cette opération s'effectue généralement dans la classe ViewModel.
  2. Créez un objet Observer qui définit la méthode onChanged(), laquelle contrôle ce qui se passe lorsque les données conservées de l'objet LiveData changent. Un objet Observer est généralement créé dans un contrôleur d'UI, tel qu'une activité ou un fragment.
  3. Associez l'objet Observer à l'objet LiveData à l'aide de la méthode observe(). La méthode observe() utilise un objet LifecycleOwner. L'objet Observer suit l'objet LiveData afin d'être alerté des changements. L'objet Observer est généralement associé à un contrôleur d'UI, tel qu'une activité ou un fragment.

Lorsque vous mettez à jour la valeur stockée dans l'objet LiveData, tous les observateurs enregistrés sont déclenchés tant que le LifecycleOwner associé est actif.

LiveData permet aux observateurs de contrôleurs d'UI de suivre les mises à jour. Lorsque les données détenues par l'objet LiveData changent, l'UI est automatiquement mise à jour en réponse.

Créer des objets LiveData

LiveData est un wrapper qui peut être utilisé avec toutes les données, y compris les objets qui implémentent Collections, comme List. Un objet LiveData est généralement stocké dans un objet ViewModel et est accessible via une méthode getter, comme l'illustre l'exemple suivant :

Kotlin

class NameViewModel : ViewModel() {

    // Create a LiveData with a String
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    // Rest of the ViewModel...
}

Java

public class NameViewModel extends ViewModel {

    // Create a LiveData with a String
    private MutableLiveData<String> currentName;

    public MutableLiveData<String> getCurrentName() {
        if (currentName == null) {
            currentName = new MutableLiveData<String>();
        }
        return currentName;
    }

    // Rest of the ViewModel...
}

Au départ, les données d'un objet LiveData ne sont pas définies.

Pour en savoir plus sur les avantages et l'utilisation de la classe ViewModel, consultez le guide consacré à ViewModel.

Observer des objets LiveData

Dans la plupart des cas, la méthode onCreate() d'un composant d'application est l'endroit idéal pour commencer à observer un objet LiveData afin de s'assurer que :

  • le système n'effectue pas d'appels redondants à partir de la méthode onResume() d'une activité ou d'un fragment ;
  • l'activité ou le fragment disposent de données qu'ils peuvent afficher dès qu'ils deviennent actifs. Dès qu'un composant d'application présente l'état STARTED, il reçoit la valeur la plus récente des objets LiveData qu'il observe. Cela ne se produit que si l'objet LiveData à observer a été défini.

En général, LiveData ne transmet des mises à jour que si les données sont modifiées et uniquement aux observateurs actifs. Il existe une exception à ce comportement : les observateurs reçoivent également une mise à jour lorsqu'ils passent d'un état inactif à un état actif. De plus, si l'observateur passe de l'état inactif à l'état actif une deuxième fois, il ne reçoit une mise à jour que si la valeur a été modifiée depuis le dernier passage à l'état actif.

L'exemple de code suivant montre comment commencer à observer un objet LiveData :

Kotlin

class NameActivity : AppCompatActivity() {

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Other code to setup the activity...

        // Create the observer which updates the UI.
        val nameObserver = Observer<String> { newName ->
            // Update the UI, in this case, a TextView.
            nameTextView.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.currentName.observe(this, nameObserver)
    }
}

Java

public class NameActivity extends AppCompatActivity {

    private NameViewModel model;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        model = new ViewModelProvider(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                nameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.getCurrentName().observe(this, nameObserver);
    }
}

Une fois que observe() est appelé avec nameObserver transmis en tant que paramètre, onChanged() est immédiatement appelé à fournir la valeur la plus récente stockée dans mCurrentName. Si l'objet LiveData n'a pas défini de valeur dans mCurrentName, onChanged() n'est pas appelé.

Mettre à jour des objets LiveData

LiveData ne propose aucune méthode publique pour mettre à jour les données stockées. La classe MutableLiveData expose publiquement les méthodes setValue(T) et postValue(T), et vous devez les utiliser s'il vous faut modifier la valeur stockée dans un projet LiveData. Généralement, MutableLiveData est utilisé dans ViewModel, puis ViewModel n'expose que les objets LiveData ne pouvant pas être modifiés par les observateurs.

Une fois la relation d'observateur configurée, vous pouvez mettre à jour la valeur de l'objet LiveData, comme illustré dans l'exemple suivant, qui déclenche tous les observateurs lorsque l'utilisateur appuie sur un bouton :

Kotlin

button.setOnClickListener {
    val anotherName = "John Doe"
    model.currentName.setValue(anotherName)
}

Java

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        model.getCurrentName().setValue(anotherName);
    }
});

Si vous appelez setValue(T) dans l'exemple, les observateurs appellent leurs méthodes onChanged() avec la valeur John Doe. Bien que l'exemple illustre une pression de bouton, setValue() ou postValue() peuvent être appelés pour mettre à jour mName pour diverses raisons : en réponse à une requête réseau ou à la fin du chargement de la base de données. Dans tous les cas, l'appel vers setValue() ou postValue() déclenche des observateurs et met à jour l'UI.

Utiliser LiveData avec Room

La bibliothèque de persistance de Room accepte les requêtes observables qui renvoient des objets LiveData. Les requêtes observables sont écrites dans un objet d'accès aux bases de données (DAO).

Room génère tout le code nécessaire pour mettre à jour l'objet LiveData lorsqu'une base de données est mise à jour. Le code généré exécute la requête de manière asynchrone sur un thread d'arrière-plan si nécessaire. Ce modèle est utile pour synchroniser les données affichées dans une UI avec les données stockées dans une base de données. Pour en savoir plus sur Room et les DAO, consultez le guide concernant la bibliothèque de persistance Room.

Utiliser des coroutines avec LiveData

LiveData est compatible avec les coroutines Kotlin. Pour en savoir plus, consultez la page Utiliser des coroutines Kotlin avec des composants d'architecture Android.

LiveData dans l'architecture d'une application

LiveData tient compte du cycle de vie et suit le cycle de vie d'entités telles que les activités et les fragments. Utilisez LiveData pour communiquer entre ces propriétaires de cycle de vie et d'autres objets présentant une durée de vie différente tels que les objets ViewModel. La principale responsabilité de la classe ViewModel est de charger et gérer les données liées à l'UI, ce qui la rend adaptée à la conservation des objets LiveData. Créez des objets LiveData dans ViewModel et utilisez-les pour exposer l'état à la couche de l'UI.

Les activités et les fragments ne doivent pas conserver d'instances LiveData, car leur rôle est d'afficher les données, et non de stocker l'état. En outre, le fait d'éviter que les activités et les fragments ne conservent les données facilite l'écriture de tests unitaires.

S'il peut avérer tentant de travailler avec des objets LiveData dans votre classe de couche de données, LiveData n'est pas conçu pour gérer des flux de données asynchrones. Bien que vous puissiez utiliser les transformations LiveData et MediatorLiveData pour ce faire, cette approche présente des inconvénients. La capacité à combiner des flux de données est en effet très limitée et tous les objets LiveData (y compris ceux créés via des transformations) sont observés sur le thread principal. Le code ci-dessous montre comment le fait de conserver LiveData dans Repository peut bloquer le thread principal :

Kotlin

class UserRepository {

    // DON'T DO THIS! LiveData objects should not live in the repository.
    fun getUsers(): LiveData<List<User>> {
        ...
    }

    fun getNewPremiumUsers(): LiveData<List<User>> {
        return getUsers().map { users ->
            // This is an expensive call being made on the main thread and may
            // cause noticeable jank in the UI!
            users
                .filter { user ->
                  user.isPremium
                }
          .filter { user ->
              val lastSyncedTime = dao.getLastSyncedTime()
              user.timeCreated > lastSyncedTime
                }
    }
}

Java

class UserRepository {

    // DON'T DO THIS! LiveData objects should not live in the repository.
    LiveData<List<User>> getUsers() {
        ...
    }

    LiveData<List<User>> getNewPremiumUsers() {
    return Transformations.map(getUsers(),
        // This is an expensive call being made on the main thread and may cause
        // noticeable jank in the UI!
        users -> users.stream()
            .filter(User::isPremium)
            .filter(user ->
                user.getTimeCreated() > dao.getLastSyncedTime())
            .collect(Collectors.toList()));
    }
}

Si vous devez utiliser des flux de données dans d'autres couches de votre application, envisagez d'utiliser des flux Kotlin, puis convertissez-les en LiveData dans ViewModel avec asLiveData(). Pour en savoir plus sur l'utilisation des flux (Flow) Kotlin avec LiveData, consultez cet atelier de programmation. Pour le codebase créé avec Java, envisagez d'utiliser des exécuteurs conjointement avec des rappels ou RxJava.

Développer LiveData

LiveData considère qu'un observateur est actif si son cycle de vie est défini sur l'état STARTED ou RESUMED. L'exemple de code suivant montre comment développer la classe LiveData :

Kotlin

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }
}

Java

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

Dans cet exemple, l'écouteur de prix comprend les méthodes clés suivantes :

  • La méthode onActive() est appelée lorsque l'objet LiveData dispose d'un observateur actif. Cela signifie que vous devez commencer à observer les fluctuations du cours des actions à l'aide de cette méthode.
  • La méthode onInactive() est appelée lorsque l'objet LiveData ne dispose pas d'un observateur actif. Comme aucun observateur ne l'écoute, nul besoin de rester connecté au service StockManager.
  • La méthode setValue(T) met à jour la valeur de l'instance LiveData et informe tous les observateurs actifs de la modification.

Vous pouvez utiliser la classe StockLiveData comme suit :

Kotlin

public class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val myPriceListener: LiveData<BigDecimal> = ...
        myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }
}

Java

public class MyFragment extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(getViewLifecycleOwner(), price -> {
            // Update the UI.
        });
    }
}

La méthode observe() transmet le LifecycleOwner associé à la vue du fragment en tant que premier argument. Cela indique que cet observateur est lié à l'objet Lifecycle associé au propriétaire, ce qui signifie que :

  • Si l'objet Lifecycle n'est pas actif, l'observateur n'est pas appelé même si la valeur change.
  • Une fois l'objet Lifecycle détruit, l'observateur est automatiquement supprimé.

Le fait que les objets LiveData tiennent compte du cycle de vie signifie que vous pouvez les partager entre plusieurs activités, fragments et services. Pour simplifier cet exemple, vous pouvez implémenter la classe LiveData en tant que Singleton comme suit :

Kotlin

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager: StockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }

    companion object {
        private lateinit var sInstance: StockLiveData

        @MainThread
        fun get(symbol: String): StockLiveData {
            sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
            return sInstance
        }
    }
}

Java

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

Et utiliser cette classe dans le fragment comme suit :

Kotlin

class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })

    }

Java

public class MyFragment extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        StockLiveData.get(symbol).observe(getViewLifecycleOwner(), price -> {
            // Update the UI.
        });
    }
}

Plusieurs fragments et activités peuvent observer l'instance MyPriceListener. LiveData ne se connecte au service système que si un ou plusieurs d'entre eux sont visibles et actifs.

Transformer LiveData

Vous pouvez modifier la valeur stockée dans un objet LiveData avant de la distribuer aux observateurs, ou vous devrez peut-être renvoyer une instance LiveData différente basée sur la valeur d'une autre instance. Le package Lifecycle fournit la classe Transformations qui inclut des méthodes d'assistance compatibles avec ces scénarios.

Transformations.map()
Applique une fonction sur la valeur stockée dans l'objet LiveData et propage le résultat en aval.

Kotlin

val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = userLiveData.map {
    user -> "${user.name} ${user.lastName}"
}

Java

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});
Transformations.switchMap()
À l'instar de map(), cette classe applique une fonction à la valeur stockée dans l'objet LiveData, puis désencapsule et envoie le résultat en aval. La fonction transmise à switchMap() doit renvoyer un objet LiveData, comme illustré dans l'exemple suivant :

Kotlin

private fun getUser(id: String): LiveData<User> {
  ...
}
val userId: LiveData<String> = ...
val user = userId.switchMap { id -> getUser(id) }

Java

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

Vous pouvez utiliser des méthodes de transformation pour transférer des informations tout au long du cycle de vie de l'observateur. Les transformations ne sont pas calculées, sauf si un observateur surveille l'objet LiveData renvoyé. Étant donné que les transformations sont calculées en différé, le comportement lié au cycle de vie est implicitement transmis sans que des appels ou des dépendances explicites supplémentaires ne soient nécessaires.

Si vous pensez avoir besoin d'un objet Lifecycle dans un objet ViewModel, il est peut-être préférable d'effectuer une transformation. Par exemple, supposons que vous ayez un composant d'UI qui accepte une adresse et renvoie le code postal de cette adresse. Vous pouvez implémenter la classe ViewModel simpliste pour ce composant, comme illustré dans l'exemple de code suivant :

Kotlin

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

    private fun getPostalCode(address: String): LiveData<String> {
        // DON'T DO THIS
        return repository.getPostCode(address)
    }
}

Java

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

Le composant d'UI doit ensuite cesser d'enregistrer l'objet LiveData précédent et enregistrer la nouvelle instance chaque fois qu'il appelle getPostalCode(). En outre, si le composant d'UI est recréé, il déclenche un autre appel vers la méthode repository.getPostCode() au lieu d'utiliser le résultat de l'appel précédent.

À la place, vous pouvez implémenter la recherche de code postal en tant que transformation de la saisie d'adresse, comme illustré dans l'exemple suivant :

Kotlin

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
    private val addressInput = MutableLiveData<String>()
    val postalCode: LiveData<String> = addressInput.switchMap {
            address -> repository.getPostCode(address) }


    private fun setInput(address: String) {
        addressInput.value = address
    }
}

Java

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

Dans ce cas, le champ postalCode est défini comme une transformation de addressInput. Tant qu'un observateur actif est associé à votre champ postalCode, la valeur du champ est recalculée et récupérée chaque fois que addressInput change.

Ce mécanisme permet aux niveaux inférieurs de l'application de créer des objets LiveData qui sont calculés à la demande en différé. Un objet ViewModel peut facilement obtenir des références à des objets LiveData, puis définir des règles de transformation en plus.

Créer des transformations

Il existe une dizaine de transformations spécifiques pouvant s'avérer utiles dans votre application, mais elles ne sont pas fournies par défaut. Pour implémenter votre propre transformation, vous pouvez utiliser la classe MediatorLiveData, laquelle écoute d'autres objets LiveData et traite les événements émis par ceux-ci. MediatorLiveData propage correctement son état dans l'objet source LiveData. Pour en savoir plus sur ce modèle, consultez la documentation de référence de la classe Transformations.

Fusionner plusieurs sources LiveData

MediatorLiveData est une sous-classe de LiveData qui vous permet de fusionner plusieurs sources LiveData. Les observateurs d'objets MediatorLiveData sont ensuite déclenchés chaque fois que l'un des objets sources LiveData d'origine change.

Par exemple, si l'un des objets LiveData de votre UI peut être mis à jour à partir d'une base de données ou d'un réseau local, vous pouvez ajouter les sources suivantes à l'objet MediatorLiveData :

  • Un objet LiveData associé aux données stockées dans la base de données
  • Un objet LiveData associé aux données consultées depuis le réseau

Votre activité doit seulement observer l'objet MediatorLiveData pour recevoir les mises à jour des deux sources. Pour obtenir un exemple détaillé, consultez la section Annexe : exposer l'état du réseau du Guide de l'architecture des applications.

Ressources supplémentaires

Pour en savoir plus sur la classe LiveData, consultez les ressources suivantes.

Exemples

Ateliers de programmation

Blogs

Vidéos