Pourquoi utiliser les coroutines en Kotlin ?
Les coroutines permettent d'écrire du code asynchrone de manière simple, lisible et structurée. Au lieu d'utiliser des callbacks compliqués ou des threads lourds, Kotlin offre une solution légère pour exécuter des tâches en arrière-plan sans bloquer l'application.
Qu’est-ce qu’une coroutine ?
Une coroutine est une fonction qui peut être suspendue puis reprendre plus tard, sans bloquer le thread. Elles sont idéales pour :
- faire un appel réseau,
- accéder à une base de données,
- effectuer un travail long,
- garder l’interface utilisateur fluide.
Exemple simple de coroutine
Voici un appel réseau effectué sans bloquer l’UI :
suspend fun loadUser(id: UserId): User {
return api.getUser(id)
}
Grâce au mot-clé suspend, Kotlin sait que cette fonction peut prendre du temps
et qu'elle doit être exécutée de manière non bloquante.
Lancer une coroutine
Pour lancer une coroutine, on utilise un scope, qui définit où et comment la coroutine vit.
Exemple avec viewModelScope (Android)
viewModelScope.launch {
val user = repository.getUser()
_uiState.value = UiState(user)
}
launch démarre une coroutine.
viewModelScope annule automatiquement la coroutine quand le ViewModel est détruit.
Les contextes de coroutine
Chaque coroutine s'exécute sur un dispatcher, qui choisit le thread.
- Dispatchers.Main : pour mettre à jour l’UI
- Dispatchers.IO : pour les opérations blocantes (réseau, fichiers, SQL)
- Dispatchers.Default : pour le calcul intensif
Exemple : changer de dispatcher
withContext(Dispatchers.IO) {
val data = dao.getUsers()
}
withContext permet d’exécuter un morceau de code sur un autre dispatcher
sans créer une nouvelle coroutine.
async : exécuter des tâches en parallèle
async est utilisé pour effectuer deux travaux en même temps et récupérer leurs résultats.
val userDeferred = async { api.getUser(id) }
val postsDeferred = async { api.getPosts(id) }
val user = userDeferred.await()
val posts = postsDeferred.await()
async renvoie un Deferred, et await() récupère le résultat.
Gestion des erreurs
Les coroutines utilisent le même modèle d’erreur que Kotlin : les exceptions sont gérées via try/catch.
viewModelScope.launch {
try {
val user = repository.getUser()
_state.value = Success(user)
} catch (e: Exception) {
_state.value = Error("Impossible de charger l'utilisateur")
}
}
CoroutineScope : structurer la vie de tes coroutines
Chaque coroutine appartient à un scope. Si le scope est annulé, toutes les coroutines qu’il contient sont annulées aussi.
- viewModelScope : se termine avec le ViewModel
- lifecycleScope : lié à un Activity ou Fragment
- GlobalScope : à éviter
Bonnes pratiques
- Ne jamais utiliser GlobalScope sauf cas très particulier.
- Préférer viewModelScope ou lifecycleScope sur Android.
- Isoler les appels réseau dans des fonctions
suspend. - Ne pas bloquer une coroutine (éviter
Thread.sleep()). - Utiliser
Dispatcher.IOpour le réseau et la base de données.
Les coroutines apportent un modèle simple et puissant pour gérer l’asynchronicité. Elles rendent ton code plus lisible, plus sûr et plus facile à tester, surtout sur Android.