Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Présentation de ViewModel   Inclus dans Android Jetpack.

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 interface utilisateur 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'enregistrement de l'état de l'instance. ViewModel fournit une API pratique pour assurer la persistance des données qui résout ce problème.

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 survie à la fois par l'état qu'il détient et par 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é, d'un fragment, ou de tout autre type qui implémente l'interface. Votre ViewModel s'applique ensuite au cycle de vie du ViewModelStoreOwner. Il reste en mémoire jusqu'à ce que son ViewModelStoreOwner disparaisse définitivement.

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, Fragment et NavBackStackEntry. Pour obtenir la liste complète des sous-classes indirectes, consultez la documentation de référence sur ViewModelStoreOwner.

Lorsque le fragment ou l'activité auxquels ViewModel s'applique sont détruits, le travail asynchrone se poursuit dans le ViewModel concerné. C'est la clé de la persistance.

Pour en savoir plus, consultez la section ci-dessous sur le cycle de vie de ViewModel.

SavedStateHandle

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

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.

Jetpack Compose

Lorsque vous utilisez Jetpack Compose, ViewModel constitue le moyen principal d'exposer l'état de l'interface utilisateur à vos composables. Dans une application hybride, les activités et les fragments hébergent simplement vos fonctions modulables. Il s'agit d'un changement par rapport aux approches passées, dans lesquelles il n'était pas aussi simple et intuitif de créer des éléments d'interface utilisateur réutilisables avec des activités et des fragments, ce qui les rendait beaucoup plus actifs comme contrôleurs d'UI.

L'essentiel à garder à l'esprit lorsque vous utilisez ViewModel avec Compose est que vous ne pouvez pas appliquer un ViewModel à un composable. En effet, un composable n'est pas un ViewModelStoreOwner. Deux instances du même composable dans la composition, ou deux composables différents accédant au même type ViewModel dans le même ViewModelStoreOwner, reçoivent la même instance du ViewModel, ce qui souvent n'est pas le comportement attendu.

Pour bénéficier des avantages de ViewModel dans Compose, hébergez chaque écran dans un fragment ou une activité, ou utilisez Compose Navigation et des ViewModels dans des fonctions modulables le plus près possible de la destination de navigation. En effet, vous pouvez appliquer un ViewModel aux destinations et aux graphiques de navigation, aux activités et aux fragments.

Pour en savoir plus, consultez le guide États et Jetpack Compose.

Implémenter un ViewModel

Voici un exemple d'implémentation d'un ViewModel. Il s'agit ici d'afficher une liste d'utilisateurs.

Kotlin

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

Java

public class MyViewModel extends ViewModel {

    // Expose screen UI state
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    // Handle business logic
    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Vous pouvez ensuite accéder à la liste à partir d'une activité comme suit :

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

Jetpack Compose

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. Comme indiqué ci-dessus, un ViewModel est 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'un fragment, lorsqu'il se détache.
  • Dans le cas d'une entrée de navigation, lorsqu'elle est supprimée de la pile "Retour".

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é. Les mêmes états de base s'appliquent au cycle de vie d'un fragment.

Illustre le cycle de vie d'un ViewModel à mesure que l'activité change d'état.

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.

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.
  • 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 de votre activité, de votre fragment ou de votre fonction modulable au niveau de l'écran. 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

Exemples