mutableStateOf est une fonction dans Jetpack Compose qui crée un conteneur d'état mutable observable. Lorsque la valeur à l'intérieur de ce conteneur change, Compose déclenche automatiquement la recomposition de tous les composants qui lisent cet état. Sans mutableStateOf, l'interface utilisateur ne pourrait pas se mettre à jour de manière réactive lors des changements de données. Selon Google Android Developers, 2026, mutableStateOf est le bloc de construction principal pour l'état local dans Compose.
Points clés
mutableStateOf est une fonction du package compose.runtime qui crée un objet MutableState<T> stockant une valeur et pouvant notifier Compose Runtime des changements. Signature: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Le paramètre policy détermine quand un changement est considéré comme significatif — à l'égalité structurelle, à l'égalité référentielle, ou jamais.
MutableState est une interface avec une seule propriété value: un getter pour la lecture et un setter pour l'écriture. Lorsque le setter est appelé, Compose Runtime enregistre le changement dans un snapshot et marque toutes les fonctions Composable qui lisent cette variable State comme nécessitant une recomposition. Ce processus se produit de manière synchrone au sein d'un seul cycle de snapshot, ce qui élimine les états intermédiaires lors des changements en cascade.
Paramètre policy — le deuxième argument de mutableStateOf, définit le comportement de comparaison. structuralEquality() vérifie equals() — c'est le comportement par défaut. referentialEquality() vérifie === (égalité référentielle). neverEqual() considère chaque affectation comme un changement. Le choix de la policy affecte si la recomposition est déclenchée lors de l'affectation de la même valeur.
La façon la plus simple de déclarer un état observable est d'utiliser mutableStateOf avec remember. Sans remember, chaque recomposition créerait un nouveau State et tous les changements précédents seraient perdus. remember garantit que le même MutableState survit à une série de recompositions tant que la fonction Composable reste dans la composition.
@Composable
fun Counter() {
// Sans délégation: lecture/écriture via .value
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// Avec délégation: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
La différence entre les deux approches est syntaxique. Property Delegation (by) utilise une convention Kotlin: le compilateur génère des appels getValue() et setValue() pour la lecture et l'écriture. Cela équivaut à accéder directement à count.value mais semble fonctionner avec une variable normale. Les deux approches sont fonctionnellement identiques: Compose suit la lecture dans le getter et l'écriture dans le setter indépendamment de la notation.
| Forme | Code | Lecture | Écriture |
|---|---|---|---|
| Sans délégation | val count = mutableStateOf(0) | count.value | count.value = n |
| Avec délégation | var count by mutableStateOf(0) | count | count = n |
Le mécanisme des propriétés déléguées de Kotlin n'est pas une fonctionnalité de Compose mais une capacité intégrée du langage. N'importe quelle classe peut implémenter les opérateurs getValue(thisRef, property) et setValue(thisRef, property, value), après quoi son instance peut être utilisée avec le mot-clé by. MutableState fonctionne exactement ainsi: getValue retourne la valeur actuelle et setValue en assigne une nouvelle.
Une distinction importante: val vs var. mutableStateOf peut être assigné à la fois à val et var. Avec val (val count = mutableStateOf(0)), l'objet MutableState lui-même est immuable, mais sa propriété value peut être modifiée. Avec var (var count by mutableStateOf(0)), la délégation crée l'illusion de travailler avec une variable primitive, mais le setter appelle en réalité setValue sur MutableState. Le choix entre val et var est un choix entre un accès explicite et implicite à .value.
State Delegation est du sucre syntaxique qui simplifie le code mais ne change pas la mécanique. Le compilateur Kotlin traduit var x by mutableStateOf(0) en getter/setter qui appellent mutableStateOf.getValue() et mutableStateOf.setValue(). Dans le bytecode généré, il n'y a pas de différence entre val et var avec by — les deux fonctionnent via le même conteneur MutableState.
// Délégué personnalisé pour Compose State
class ValidatedState<T>(initialValue: T) {
private val state = mutableStateOf(initialValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>) = state.value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (value != state.value) {
state.value = value
}
}
}
@Composable
fun Test() {
var text by remember { ValidatedState("") }
}
Snapshot est un mécanisme de Compose Runtime qui garantit la cohérence de lecture de l'State lors des changements parallèles. Lorsqu'une fonction Composable lit mutableStateOf, le snapshot enregistre la valeur actuelle. Si pendant la composition un autre changement écrit dans le même State, le snapshot voit l'écriture mais ne permet pas de lire des données incohérentes — la lecture retourne toujours la valeur valide au début du snapshot.
Lorsque le setter mutableStateOf.value = newValue est appelé, Compose Runtime ne déclenche pas immédiatement la recomposition. Au lieu de cela, le changement est enregistré dans le snapshot actuel. Lorsque le snapshot est appliqué (à la limite de trame), Compose parcourt la liste des States modifiés et marque les composants lecteurs comme Invalid. Ce n'est que dans la trame suivante que la recomposition commence. Cela garantit que l'interface utilisateur n'est pas redessinée des dizaines de fois lors de changements en cascade.
Snapshots globaux et locaux: par défaut, mutableStateOf fonctionne dans le snapshot global, qui s'applique automatiquement. Vous pouvez créer un snapshot local via Snapshot.takeSnapshot() pour une lecture isolée sans effets secondaires. Ceci est utilisé à l'intérieur de Modifier lorsque vous devez lire State sans vous abonner aux changements. Cette approche optimise les performances et évite les recompositions inattendues.
Considérons un scénario réel — un formulaire de connexion avec trois champs: email, mot de passe et statut de chargement. Les trois champs utilisent mutableStateOf, mais avec différentes policy et différents niveaux d'imbrication. email utilise la délégation, password utilise l'accès direct.
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// État unique pour le formulaire, policy = referentialEquality
var formState by remember {
mutableStateOf(LoginState(), SnapshotMutationPolicy.referentialEquality())
}
val isValid = remember(formState) {
formState.email.contains("@") && formState.password.length() >= 6
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = formState.email,
onValueChange = { formState = formState.copy(email = it) },
label = { Text("E-mail") }
)
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("Mot de passe") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("Connexion")
}
}
}
Dans cet exemple, mutableStateOf est utilisé avec une classe de données personnalisée LoginState et la policy referentialEquality. Cela signifie que la recomposition ne sera déclenchée que lorsqu'une nouvelle instance de LoginState est assignée via copy(). isValid est calculé à partir de formState et n'est recalculé que lorsqu'il change. Cette approche offre un contrôle clair sur les recompositions: chaque champ du formulaire ne change qu'en créant une nouvelle copie.
Foire aux questions
mutableStateOf est un conteneur spécifique à Compose qui fonctionne dans les snapshots. StateFlow vient de kotlinx.coroutines.flow et n'est pas lié à Compose. mutableStateOf déclenche automatiquement la recomposition, tandis que StateFlow nécessite collectAsState(). Pour l'état d'interface utilisateur dans Composable, mutableStateOf est préférable.
Oui, mutableStateOf peut être appelé en dehors des fonctions Composable, mais il ne sera pas suivi. Pour la réactivité dans l'interface utilisateur, l'State doit être lu à l'intérieur de Composable. De nombreuses ViewModels utilisent MutableStateField (une enveloppe autour de mutableStateOf) pour passer l'état à l'interface utilisateur via StateFlow.
Le système Snapshot garantit la cohérence: chaque recomposition voit un état cohérent au début du snapshot. Les changements provenant de différents threads sont appliqués atomiquement à la limite de trame, éliminant les conditions de course lors des lectures au sein d'une seule composition.
Assignez une nouvelle valeur: count.value = 0 (ou count = 0 avec délégation). Si vous devez recréer complètement l'State, utilisez remember avec une clé: remember(key) { mutableStateOf(initial) } — lorsque la clé change, l'State sera créé à nouveau.
Composer utilise des snapshots qui regroupent les changements: même avec des centaines d'assignations dans une seule trame, la recomposition n'est exécutée qu'une seule fois. Pour les mises à jour très fréquentes (animations), utilisez Animatable ou animate*AsState — ils sont optimisés pour les mises à jour trame par trame.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi