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.IO pour 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.