Selon l'emplacement sur lequel l'état est hissé et selon la logique requise, différentes API permettent de stocker et de restaurer l'état de l'UI. Pour ce faire, chaque application utilise une combinaison d'API.
Toute application Android est susceptible de perdre son état d'UI en raison de la recréation de l'activité ou du processus. Cette perte d'état peut être causée par les événements suivants :
- Modifications de la configuration. L'activité est détruite et recréée, sauf si la modification de la configuration est gérée manuellement.
- Arrêt de processus initié par le système. L'application est exécutée en arrière-plan, et l'appareil libère des ressources (telles que la mémoire) pour d'autres processus.
La préservation de l'état après ces événements est essentielle pour une expérience utilisateur positive. Le choix de l'état qui perdurera dépend des parcours utilisateur uniques de votre application. Nous vous recommandons de conserver au moins l'entrée utilisateur et l'état lié à la navigation. Il peut s'agir de la position de défilement d'une liste, de l'ID de l'élément pour lequel l'utilisateur souhaite obtenir plus d'informations, de la sélection en cours des préférences utilisateur ou de la saisie de données dans des champs de texte.
Cette page récapitule les API disponibles pour stocker l'état de l'UI en fonction de l'emplacement sur lequel l'état est hissé et de la logique sous-jacente.
Logique d'UI
Si l'état est hissé dans l'UI, que ce soit dans des fonctions modulables ou des classes de conteneurs d'état simples limitées à la composition, vous pouvez utiliser rememberSaveable
pour conserver l'état de la recréation de l'activité et du processus.
Dans l'extrait de code suivant, rememberSaveable
permet de stocker un seul état booléen pour l'élément d'interface utilisateur :
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails
est une variable booléenne qui est stockée si l'info-bulle de chat est réduite ou développée.
rememberSaveable
stocke l'état de l'élément d'interface utilisateur dans un Bundle
via le mécanisme d'enregistrement de l'état d'instance.
Il peut stocker automatiquement des types primitifs dans le groupe. Si l'état est conservé dans un type qui n'est pas primitif, comme une classe de données, vous pouvez utiliser différents mécanismes de stockage, comme l'annotation Parcelize
, avec des API Compose telles que listSaver
et mapSaver
, ou via l'implémentation d'une classe de saver personnalisé qui étend la classe Saver
de l'environnement d'exécution Compose. Pour en savoir plus sur ces méthodes, consultez la documentation intitulée Comment stocker l'état.
Dans l'extrait de code suivant, l'API Compose rememberLazyListState
stocke LazyListState
, qui est l'état de défilement d'un objet LazyColumn
ou LazyRow
, avec rememberSaveable
. Elle utilise un LazyListState.Saver
, qui est un saver personnalisé capable de stocker et de restaurer l'état de défilement. Après la recréation d'une activité ou d'un processus (par exemple, après un changement de configuration telle que la modification de l'orientation de l'appareil), l'état de défilement est préservé.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Bonne pratique
rememberSaveable
utilise un Bundle
pour stocker l'état de l'UI. Celui-ci est partagé par d'autres API qui en tirent également parti, comme les appels onSaveInstanceState()
dans l'activité. Cependant, la taille de ce Bundle
est limitée, et le stockage d'objets volumineux peut entraîner des exceptions TransactionTooLarge
lors de l'exécution. Cela peut s'avérer particulièrement problématique dans les applications Activity
uniques dans lesquelles un seul et même Bundle
est utilisé.
Pour éviter ce type de plantage, évitez de stocker des objets ou des listes d'objets complexes et volumineux dans le bundle.
Stockez plutôt l'état minimal requis, tel que les ID ou les clés, et utilisez-le pour déléguer la restauration d'un état d'UI plus complexe à d'autres mécanismes, tels que le stockage persistant.
Ces choix de conception dépendent des cas d'utilisation spécifiques de votre application et du comportement attendu par vos utilisateurs.
Vérifier la restauration d'état
Vous pouvez vérifier que l'état stocké avec rememberSaveable
dans vos éléments Compose est correctement restauré lorsque l'activité ou le processus est recréé. Pour cela, il existe des API spécifiques, telles que StateRestorationTester
. Pour en savoir plus, consultez la documentation spécifique aux tests.
Logique métier
Si l'état de votre élément d'interface utilisateur est hissé dans ViewModel
, car il est requis par la logique métier, vous pouvez utiliser les API de ViewModel
.
L'un des principaux avantages de l'utilisation d'un ViewModel
dans une application Android est qu'il permet de gérer gratuitement les modifications de configuration. En cas de modification de la configuration, et si l'activité est détruite et recréée, l'état de l'UI hissé dans ViewModel
est conservé en mémoire. Après la recréation, l'ancienne instance de ViewModel
est associée à la nouvelle instance d'activité.
Toutefois, une instance ViewModel
ne survit pas à l'arrêt d'un processus initié par le système.
Pour que l'état de l'UI persiste, utilisez le module Saved State pour ViewModel, qui contient l'API SavedStateHandle
.
Bonne pratique
SavedStateHandle
utilise également le mécanisme Bundle
pour stocker l'état de l'UI. Vous ne devez donc y recourir que pour stocker un état d'élément d'interface utilisateur simple.
L'état de l'UI de l'écran, qui est obtenu en appliquant des règles métier et en accédant à des couches de votre application autre que l'UI, ne doit pas être stocké dans SavedStateHandle
en raison des problèmes potentiels en termes de complexité et de taille. Vous pouvez utiliser différents mécanismes pour stocker des données complexes ou volumineuses, comme le stockage persistant local. Après la recréation d'un processus, l'état temporaire de l'écran qui était stocké dans SavedStateHandle
(le cas échéant) est restauré, et l'état de l'UI de l'écran est à nouveau généré à partir de la couche de données.
API SavedStateHandle
SavedStateHandle
dispose de différentes API permettant de stocker l'état des éléments de l'interface utilisateur, en particulier :
Compose State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Compose State
Utilisez l'API saveable
de SavedStateHandle
pour lire et écrire l'état de l'élément d'interface utilisateur en tant que MutableState
, pour qu'il persiste après la recréation de l'activité et du processus avec une configuration minimale du code.
L'API saveable
prend directement en charge les types primitifs et reçoit un paramètre stateSaver
pour utiliser les savers personnalisés, tout comme rememberSaveable()
.
Dans l'extrait de code suivant, message
stocke les types d'entrée utilisateur dans un TextField
:
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
Pour en savoir plus sur l'utilisation de l'API saveable
, consultez la documentation SavedStateHandle
.
StateFlow
Optez pour getStateFlow()
afin de stocker l'état des éléments d'interface utilisateur et de l'utiliser en tant que flux à partir de SavedStateHandle
. StateFlow
est en lecture seule, et l'API exige que vous spécifiiez une clé afin que vous puissiez remplacer le flux pour émettre une nouvelle valeur. Avec la clé que vous avez configurée, vous pouvez récupérer StateFlow
et collecter la dernière valeur.
Dans l'extrait de code suivant, savedFilterType
est une variable StateFlow
qui stocke un type de filtre appliqué à une liste de canaux d'une application de chat :
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
Chaque fois que l'utilisateur sélectionne un nouveau type de filtre, setFiltering
est appelé. Cela permet d'enregistrer une nouvelle valeur dans SavedStateHandle
, stockée avec la clé _CHANNEL_FILTER_SAVED_STATE_KEY_
. savedFilterType
est un flux émettant la dernière valeur stockée dans la clé. filteredChannels
est abonné au flux pour effectuer le filtrage des canaux.
Pour en savoir plus sur l'API getStateFlow()
, consultez la documentation SavedStateHandle
.
Résumé
Le tableau suivant récapitule les API abordées dans cette section et indique quand les utiliser pour enregistrer l'état de l'UI :
Événement | Logique d'UI | Logique métier dans un ViewModel |
---|---|---|
Modifications de la configuration | rememberSaveable |
Automatique |
Arrêt de processus initié par le système | rememberSaveable |
SavedStateHandle |
L'API à utiliser dépend de l'emplacement de l'état et de la logique requise. Pour l'état utilisé dans la logique de l'interface utilisateur, optez pour rememberSaveable
. Pour l'état utilisé dans la logique métier, enregistrez-le à l'aide de SavedStateHandle
s'il se trouve dans un ViewModel
.
Utilisez les API de bundle (rememberSaveable
et SavedStateHandle
) pour stocker un faible volume de données correspondant à l'état de l'UI. Ces données constituent le minimum nécessaire pour restaurer l'état d'UI précédent, en plus d'autres mécanismes de stockage. Par exemple, si vous stockez dans le bundle l'ID d'un profil consulté par l'utilisateur, vous pouvez récupérer des données volumineuses (telles que des détails du profil) à partir de la couche de données.
Pour en savoir plus sur les différentes façons d'enregistrer l'état de l'UI, consultez la documentation sur l'enregistrement de l'état de l'UI et la page sur la couche de données du guide de l'architecture.
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Où hisser l'état
- États et Jetpack Compose
- Listes et grilles