Présentation de ViewModel   Inclus dans Android Jetpack.

Essayer avec Kotlin Multiplatform
Kotlin Multiplatform permet de partager la logique métier avec d'autres plates-formes. Découvrez comment configurer et utiliser ViewModel dans KMP.

La classe ViewModel est une logique métier ou un conteneur d'état au niveau de l'écran. Elle expose l'état au niveau de l'UI et encapsule la logique métier associée. Son principal avantage est qu'elle assure la mise en cache et la persistance de l'état en cas de modification de la configuration. Cela signifie que votre UI n'a pas besoin de récupérer à nouveau les données lorsque vous passez d'une activité à une autre ou suite à une modification de la configuration, par exemple en cas de rotation de l'écran.

Pour plus d'informations sur les conteneurs d'état, lisez les conseils sur les conteneurs d'état. De même, pour en savoir plus sur la couche UI de manière générale, consultez les conseils sur la couche d'interface utilisateur.

Avantages de ViewModel

L'alternative à un ViewModel est une classe simple qui contient les données que vous affichez dans l'interface utilisateur. Cette méthode peut s'avérer problématique lors du passage d'une activité ou d'une destination de navigation à une autre. Elle détruit ces données si vous ne les stockez pas au moyen du mécanisme d'état de l'instance enregistrée. ViewModel fournit une API pratique pour assurer la persistance des données qui résout ce problème.

Pour les détenteurs d'état pur, Compose propose des fonctionnalités retain qui permettent aux classes simples de survivre aux modifications de configuration sans l'infrastructure complète d'un ViewModel. Bien que les deux mécanismes aident à la conservation de l'état, il est généralement plus sûr de fournir un ViewModel à une instance conservée que l'inverse, car leurs cycles de vie et leurs comportements de nettoyage diffèrent.

La classe ViewModel présente principalement deux avantages :

  • Elle vous permet de conserver l'état de l'UI.
  • Elle donne accès à la logique métier.

Persistance

ViewModel permet la persistance à la fois via l'état qu'il détient et via les opérations qu'il déclenche. Cette mise en cache vous évite d'avoir à récupérer à nouveau les données lors des modifications de configuration courantes comme la rotation de l'écran.

Champ d'application

Lorsque vous instanciez un ViewModel, vous lui transmettez un objet qui implémente l'interface ViewModelStoreOwner. Il peut s'agir d'une destination ou d'un graphique de navigation, d'une activité, ou de tout autre type qui implémente l'interface. Vous pouvez également définir la portée d'un ViewModel directement sur un composable à l'aide de l'API rememberViewModelStoreOwner. Votre ViewModel s'applique ensuite au cycle de vie du ViewModelStoreOwner. Il reste en mémoire jusqu'à ce que son ViewModelStoreOwner disparaisse définitivement (par exemple, lorsque le propriétaire du composable quitte la composition).

Une plage de classes représente soit des sous-classes directes, soit des sous-classes indirectes de l'interface ViewModelStoreOwner. Les sous-classes directes sont ComponentActivity et NavBackStackEntry. Pour obtenir la liste complète des sous-classes indirectes, consultez la documentation de référence sur ViewModelStoreOwner. Pour définir la portée des ViewModels sur des éléments individuels dans un LazyList ou un Pager, utilisez rememberViewModelStoreProvider() pour transférer la gestion du propriétaire au parent.

Lorsque l'activité hôte subit une modification de configuration, le travail asynchrone se poursuit dans le ViewModel, qu'il soit limité à l'activité ou à un composable spécifique. C'est la clé de la persistance.

Pour en savoir plus, consultez la section suivante sur le cycle de vie de ViewModel, les API de champ d'application ViewModel et le guide sur le hissage d'état pour Jetpack Compose.

SavedStateHandle

SavedStateHandle vous permet de conserver les données non seulement lors des modifications de la configuration, mais également lors de l'arrêt du processus. Autrement dit, il vous permet de conserver l'état de l'UI intact même lorsque l'utilisateur ferme l'application et l'ouvre plus tard.

Pour en savoir plus sur l'enregistrement de l'état de l'UI, consultez Enregistrer l'état de l'UI dans Compose.

Accès à la logique métier

Même si la majeure partie de la logique métier se trouve dans la couche de données, la couche d'interface utilisateur peut également en contenir une partie. Ce peut être le cas lorsque vous combinez des données provenant de plusieurs dépôts pour créer l'état de l'UI à l'écran, ou lorsqu'un type de données particulier ne nécessite pas de couche de données.

ViewModel est l'endroit idéal pour gérer la logique métier dans la couche d'interface utilisateur. ViewModel gère également les événements et leur délégation à d'autres couches de la hiérarchie lorsque la logique métier doit être appliquée pour modifier les données d'application.

Implémenter un ViewModel

Voici un exemple d'implémentation d'un ViewModel pour un écran permettant à l'utilisateur de lancer des dés.

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Vous pouvez ensuite accéder au ViewModel à partir d'un composable au niveau de l'écran comme suit :

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

Utiliser des coroutines avec ViewModel

ViewModel est compatible avec les coroutines Kotlin. Il peut conserver un travail asynchrone de la même manière qu'il conserve l'état de l'UI.

Pour en savoir plus, consultez Utiliser des coroutines Kotlin avec des composants d'architecture Android.

Cycle de vie d'un ViewModel

Le cycle de vie d'un ViewModel est directement lié à son champ d'application. Un ViewModel reste en mémoire jusqu'à ce que le ViewModelStoreOwner auquel il s'applique disparaisse. Cela peut se produire dans les cas suivants :

  • Dans le cas d'une activité, lorsqu'elle se termine.
  • Dans le cas d'une entrée de navigation, lorsqu'elle est supprimée de la pile "Retour".
  • Dans le cas d'un composable, lorsqu'il quitte la composition. Vous pouvez utiliser rememberViewModelStoreOwner pour limiter le champ d'application d'un ViewModel directement à une partie arbitraire de votre UI (comme un Pager ou un LazyList).

Les ViewModels constituent donc une solution idéale pour stocker des données qui résistent aux modifications de la configuration.

La figure 1 illustre les différents états de cycle de vie d'une activité lorsqu'elle est soumise à une rotation, et lorsqu'elle est terminée. L'image montre également la durée de vie du ViewModel à côté du cycle de vie de l'activité associé. Ce diagramme illustre les différents états d'une activité.

Illustre le cycle de vie d&#39;un ViewModel à mesure que l&#39;activité change d&#39;état.
Figure 1. États du cycle de vie d'une activité et d'un ViewModel.

Vous demandez généralement un objet ViewModel la première fois que le système appelle la méthode onCreate() d'un objet d'activité. Le système peut appeler onCreate() plusieurs fois au cours de l'existence d'une activité, par exemple lors de la rotation de l'écran d'un appareil. Le ViewModel existe entre le moment où vous demandez un ViewModel pour la première fois et le moment où l'activité est terminée et détruite.

Effacer les dépendances de ViewModel

ViewModel appelle la méthode onCleared lorsque ViewModelStoreOwner la détruit au cours de son cycle de vie. Cela vous permet de nettoyer toutes les tâches ou dépendances qui suivent le cycle de vie de ViewModel.

L'exemple suivant présente une alternative à viewModelScope. viewModelScope est une CoroutineScope intégrée qui suit automatiquement le cycle de vie de ViewModel. ViewModel l'utilise pour déclencher des opérations liées à l'entreprise. Si vous souhaitez utiliser un champ d'application personnalisé au lieu de viewModelScope pour des faciliter les tests, le ViewModel peut recevoir une CoroutineScope en tant que dépendance dans son constructeur. Lorsque le ViewModelStoreOwner efface le ViewModel à la fin de son cycle de vie, le ViewModel annule également la CoroutineScope.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

À partir de la version 2.5 du cycle de vie, vous pouvez transmettre un ou plusieurs objets Closeable au constructeur du ViewModel, qui se ferme automatiquement lorsque l'instance ViewModel est effacée.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

Bonnes pratiques

Voici quelques bonnes pratiques clés à suivre lorsque vous implémentez ViewModel :

  • En raison de leur champ d'application, utilisez les ViewModels comme détails d'implémentation d'un conteneur d'état au niveau de l'écran. Ne les utilisez pas comme conteneurs d'état de composants d'UI réutilisables, tels que des groupes de puces ou des formulaires. Sinon, vous obtiendrez la même instance ViewModel dans différentes utilisations du même composant d'UI sous le même ViewModelStoreOwner, sauf si vous utilisez une clé ViewModel explicite par chip.
  • Les ViewModels ne doivent pas connaître les détails de l'implémentation de l'interface utilisateur. Les noms des méthodes exposées par l'API ViewModel et ceux des champs d'état de l'interface utilisateur doivent être aussi génériques que possible. De cette façon, votre ViewModel peut s'adapter à tout type d'UI : un téléphone mobile, un appareil pliable, une tablette ou même un Chromebook.
  • Comme ils peuvent potentiellement durer plus longtemps que le ViewModelStoreOwner, les ViewModels ne doivent contenir aucune référence des API liées au cycle de vie, comme Context ou Resources, et ce afin d'éviter les fuites de mémoire.
  • Ne transmettez pas de ViewModels à d'autres classes, fonctions ou composants d'UI. Comme la plate-forme les gère, vous devez les garder aussi près que possible, c'est-à-dire près de votre activité, de votre fonction composable au niveau de l'écran ou de votre destination de navigation. Cela permet d'éviter que les composants de niveau inférieur n'accèdent à plus de données et de logique qu'ils n'en ont besoin.

Informations supplémentaires

À mesure que vos données gagnent en complexité, vous pouvez choisir de disposer d'une classe distincte pour les charger. L'objectif de ViewModel est d'encapsuler les données pour un contrôleur d'interface utilisateur afin de permettre aux données de survivre aux modifications de configuration. Pour en savoir plus sur le chargement, la conservation et la gestion des données lors des modifications de configuration, consultez États d'interface utilisateur enregistrés.

Le Guide de l'architecture des applications Android suggère de créer une classe de dépôt pour gérer ces fonctions.

Ressources supplémentaires

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

Documentation

Afficher le contenu

Exemples