Dans Compose, l'UI est non modifiable. Il n'est pas possible de la mettre à jour après sa conception. Vous pouvez contrôler l'état de votre interface utilisateur. À chaque modification de l'interface utilisateur, Compose recrée les parties de l'arborescence qui ont été modifiées. Les composables peuvent accepter des événements d'état et d'exposition. Par exemple, un objet TextField
accepte une valeur et expose un rappel onValueChange
qui demande au gestionnaire de rappel de modifier la valeur.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Étant donné que les composables acceptent des événements "state" et "expose", le modèle de flux de données unidirectionnel s'adapte bien à Jetpack Compose. Ce guide explique comment implémenter le modèle de flux de données unidirectionnel, implémenter des événements et des conteneurs d'état, et utiliser les ViewModels dans Compose.
Flux de données unidirectionnel
Un flux de données unidirectionnel (UDF) est un modèle de conception dans lequel l'état redescend et les événements remontent. En suivant le flux de données unidirectionnel, vous pouvez dissocier les composables qui affichent l'état dans l'interface utilisateur des parties de votre application qui stockent et modifient l'état.
La boucle de mise à jour de l'UI pour une application utilisant un flux de données unidirectionnel se présente comme suit :
- Événement : une partie de l'interface utilisateur génère un événement et le fait remonter (p. ex. un clic sur le bouton transmis au ViewModel qui va le gérer), ou un événement transmis à partir d'autres couches de votre application (p. ex. un message qui indique l'expiration de la session utilisateur).
- Mettre à jour l'état : un gestionnaire d'événements peut modifier l'état.
- État de l'affichage : le conteneur d'état transmet l'état, et l'interface utilisateur l'affiche.
Ce mode d'utilisation de Jetpack Compose offre plusieurs avantages :
- Facilité des tests : en dissociant l'état de l'UI qui l'affiche, il est plus facile de tester l'état et l'UI de façon isolée.
- Encapsulation de l'état : comme l'état peut uniquement être actualité à un seul endroit et qu'il n'existe qu'une seule référence fiable pour l'état d'un composable, il y aura moins de risque d'introduire un bug créé par des états contradictoires.
- Cohérence de l'UI : toutes les modifications d'état sont immédiatement reflétées dans l'UI grâce à l'utilisation de conteneurs d'état observables, comme
StateFlow
ouLiveData
.
Flux de données unidirectionnel dans Jetpack Compose
Le fonctionnement des composables repose sur un état et des événements. Par exemple, un TextField
n'est actualisé que lorsque son paramètre value
est mis à jour et qu'il expose un rappel onValueChange
, un événement qui demande la modification de la valeur. Compose définit l'objet State
comme un conteneur de valeurs, et toute modification apportée à la valeur d'état déclenche une recomposition. Vous pouvez conserver l'état dans un remember { mutableStateOf(value) }
ou un rememberSaveable { mutableStateOf(value)
selon la durée de conservation de la valeur.
Le type de la valeur du composable TextField
est String
. Elle peut donc provenir de n'importe quelle source : d'une valeur codée en dur, d'un ViewModel ou transmise à partir du composable parent. Vous n'avez pas besoin de le conserver dans un objet State
, mais vous devez mettre à jour la valeur lorsque onValueChange
est appelé.
Définir les paramètres du composable
Lorsque vous définissez les paramètres d'état d'un composable, tenez compte des questions suivantes :
- Le composable est-il réutilisable ou flexible ?
- Comment les paramètres d'état affectent-ils les performances de ce composable ?
Pour encourager leur dissociation et leur réutilisation, chaque composable doit contenir le moins d'informations possible. Par exemple, lorsque vous créez un composable qui contient l'en-tête d'un article d'actualité, ne transmettez que les informations à afficher, plutôt que l'article entier :
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
L'utilisation de paramètres individuels améliore parfois les performances. Par exemple, si News
contient plus d'informations que title
et subtitle
, chaque fois qu'une nouvelle instance de News
est transmise Header(news)
, le composable se recompose, même si title
et subtitle
n'ont pas changé.
Réfléchissez bien au nombre de paramètres que vous transmettez. L'excès de paramètres dans une fonction réduit son ergonomie. Dans ce cas, il est préférable de les regrouper dans une classe.
Les événements dans Compose
Chaque entrée de votre application doit être représentée comme un événement : les appuis, les modifications de texte, et même les minuteurs ou autres mises à jour. Comme ces événements modifient l'état de votre UI,
ViewModel
doit être celui qui va les gérer et mettre à jour l'état de l'UI.
La couche de l'UI ne doit jamais changer d'état en dehors d'un gestionnaire d'événements, car cela peut entraîner des incohérences et des bugs dans votre application.
Préférez les valeurs immuables pour les lambdas d'état et de gestionnaire d'événements. Cette approche présente les avantages suivants :
- Vous améliorez la réutilisation.
- Vous vous assurez que votre UI ne modifie pas directement la valeur de l'état.
- Vous éviterez les problèmes de simultanéité en vous assurant que l'état n'est pas modifié à partir d'un autre thread.
- Bien souvent, cela permet de réduire la complexité du code.
Par exemple, un composable qui accepte un String
et un lambda comme paramètres peut être appelé à partir de nombreux contextes et est hautement réutilisable. Supposons que la barre d'application supérieure de votre application affiche toujours du texte et dispose d'un bouton "Retour". Vous pouvez définir un composable MyAppTopAppBar
plus générique qui reçoit le texte et le bouton "Retour" en tant que paramètres :
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ViewModels, états et événements : exemple
En utilisant ViewModel
et mutableStateOf
, vous pouvez également introduire un flux de données unidirectionnel dans votre application si l'une des conditions suivantes est remplie :
- L'état de votre interface utilisateur est exposé via des conteneurs d'état observables, tels que
StateFlow
ouLiveData
. ViewModel
gère les événements provenant de l'interface utilisateur ou d'autres couches de votre application, et met à jour le conteneur d'état en fonction des événements.
Par exemple, lorsque vous implémentez un écran de connexion, le fait d'appuyer sur un bouton Se connecter doit entraîner l'affichage d'un élément de chargement et d'un appel réseau. Si la connexion aboutit, l'application accède à un autre écran. En cas d'erreur, l'application affiche une snackbar. Pour modéliser l'état de l'écran et l'événement, procédez comme suit :
L'écran présente quatre états :
- Déconnecté : lorsque l'utilisateur ne s'est pas encore connecté.
- En cours : lorsque votre application tente actuellement de connecter l'utilisateur en effectuant un appel réseau.
- Erreur : une erreur s'est produite lors de la connexion.
- Connecté : l'utilisateur est connecté.
Vous pouvez modéliser ces états sous la forme d'une classe scellée. ViewModel
expose l'état en tant que
un State
, définit l'état initial et le met à jour si nécessaire. La
ViewModel
gère également l'événement de connexion en exposant une méthode onSignIn()
.
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
En plus de l'API mutableStateOf
, Compose fournit des extensions pour enregistrer LiveData
, Flow
et Observable
en tant qu'écouteur et représenter la valeur sous forme d'état.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
En savoir plus
Pour en savoir plus sur l'architecture dans Jetpack Compose, consultez les ressources suivantes :
Exemples
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé.
- États et Jetpack Compose
- Enregistrer l'état de l'interface utilisateur dans Compose
- Gérer les entrées utilisateur