Panique silencieuse : quand Kotlin crie que State ne peut pas être un délégué
\n\nVous écrivez du code en Kotlin, vous utilisez Jetpack Compose, tout se passe comme prévu. Et soudain — boum ! — le compilateur affiche un message qui glace le sang d'un débutant : Type 'State<List<User>?>' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate. Cela ressemble à une condamnation, mais en réalité c'est juste un appel à l'aide de votre code. Comprenons ce qu'il veut dire et comment l'apprivoiser.
Cette erreur est l'une des plus populaires sur Stack Overflow (des milliers de vues !), et elle poursuit les développeurs qui commencent tout juste à maîtriser les propriétés déléguées en Kotlin. Dans cet article, nous ne nous contenterons pas de traduire le message d'erreur, mais nous montrerons trois situations réelles où elle se produit, et nous donnerons des solutions fonctionnelles. Vous apprendrez à utiliser correctement StateFlow dans Compose, à ne pas vous perdre dans les délégués Kotlin et pourquoi LiveData se comporte parfois différemment de ce qui est attendu.
\n\nQu'est-ce qu'un délégué en Kotlin et pourquoi State ne le devient-il pas ?
\n\nPour commencer — une petite introduction rapide. Les propriétés déléguées en Kotlin (mot-clé by) permettent de transférer la logique du getter et du setter à un objet séparé. Par exemple :
var name: String by Delegates.observable("") { prop, old, new ->\n println("$old → $new")\n}\n\n\nPour qu'un objet puisse être un délégué, il doit implémenter les méthodes getValue() et éventuellement setValue() avec une signature spécifique. L'erreur Type 'State<List<User>?>' has no method 'getValue(Nothing?, KProperty<*>)' indique précisément cela : votre objet de type State<List<User>?> n'a pas de méthode getValue appropriée. Le compilateur ne peut pas l'utiliser comme délégué.
Passons maintenant aux trois scénarios spécifiques où cette erreur apparaît le plus souvent.
\n\nErreur 1 : Confusion entre by et l'assignation de StateFlow
\n\nLe cas le plus fréquent est lorsqu'un développeur essaie d'utiliser by pour assigner StateFlow dans un ViewModel, mais le fait incorrectement. Regardez ce code :
// ❌ Erreur ! À ne pas faire\nclass MyViewModel : ViewModel() {\n private val _users = MutableStateFlow<List<User>?>(null)\n val users: StateFlow<List<User>?> by _users // Erreur !\n}\n\n\nIci, _users est un MutableStateFlow, et nous essayons d'en faire un délégué pour la propriété users. Mais StateFlow n'implémente pas getValue pour les propriétés Kotlin. Ce n'est pas un délégué, c'est un flux réactif.
Solution : utilisez asStateFlow()
\n\nLa bonne méthode n'est pas de déléguer, mais de transformer :
\n\n// ✅ Correct\nclass MyViewModel : ViewModel() {\n private val _users = MutableStateFlow<List<User>?>(null)\n val users: StateFlow<List<User>?> = _users.asStateFlow()\n}\n\n\nLa méthode asStateFlow() crée une copie immuable qui peut être exposée en toute sécurité. Pas de délégués, pas d'erreurs.
Erreur 2 : Utilisation de by avec LiveData dans Compose
\n\nLa deuxième situation la plus courante est la tentative d'utiliser LiveData directement avec by dans Jetpack Compose. Les développeurs venant du monde des systèmes View sont habitués à LiveData et essaient d'appliquer la même approche :
// ❌ Erreur !\n@Composable\nfun UserList(viewModel: MyViewModel) {\n val users: List<User>? by viewModel.users // Erreur !\n // ...\n}\n\n\nIci, viewModel.users est de type LiveData<List<User>?>. Et LiveData n'est pas un délégué pour les propriétés Compose. Le compilateur affiche à nouveau la même erreur : Type 'LiveData<List<User>?>' has