# Maîtriser les Coroutines Kotlin : Guide complet 2026 > Apprenez à maîtriser les coroutines Kotlin pour le développement Android : suspend functions, scopes, dispatchers et patterns avancés. - Published: 2026-01-04 - Updated: 2026-03-28 - Author: SharpSkill - Tags: kotlin, coroutines, android, async, concurrency - Reading time: 12 min --- Les coroutines Kotlin ont révolutionné la programmation asynchrone sur Android. Fini le callback hell et les AsyncTask obsolètes : avec les coroutines, vous écrivez du code asynchrone qui ressemble à du code synchrone, tout en restant performant et maintenable. > **Pourquoi les coroutines ?** > > Les coroutines sont légères (des milliers peuvent tourner sur un seul thread), annulables nativement, et s'intègrent parfaitement avec Jetpack et l'écosystème Android moderne. ## Comprendre les fondamentaux Avant de plonger dans le code, comprenons ce qui rend les coroutines si puissantes. ### Qu'est-ce qu'une coroutine ? Une coroutine est une instance de calcul suspendable. Contrairement aux threads, les coroutines ne bloquent pas : elles suspendent leur exécution et libèrent le thread pour d'autres tâches. ```kotlin // BasicCoroutine.kt import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(1000L) // Suspend sans bloquer println("World!") } println("Hello,") } // Output: Hello, World! ``` ### Suspend functions : le cœur des coroutines Le mot-clé `suspend` indique qu'une fonction peut suspendre l'exécution de la coroutine sans bloquer le thread. ```kotlin // SuspendFunction.kt suspend fun fetchUserData(userId: String): User { return withContext(Dispatchers.IO) { // Appel réseau - s'exécute sur un thread IO apiService.getUser(userId) } } suspend fun fetchUserWithPosts(userId: String): UserWithPosts { // Exécution séquentielle val user = fetchUserData(userId) val posts = fetchUserPosts(userId) return UserWithPosts(user, posts) } ``` > **Règle d'or** : Une suspend function ne peut être appelée que depuis une autre suspend function ou depuis une coroutine. ## Les Coroutine Scopes Le scope définit le cycle de vie de vos coroutines. C'est crucial pour éviter les memory leaks. ### viewModelScope : le scope pour les ViewModels ```kotlin // UserViewModel.kt class UserViewModel( private val userRepository: UserRepository ) : ViewModel() { private val _uiState = MutableStateFlow(UserUiState.Loading) val uiState: StateFlow = _uiState.asStateFlow() fun loadUser(userId: String) { viewModelScope.launch { _uiState.value = UserUiState.Loading try { val user = userRepository.getUser(userId) _uiState.value = UserUiState.Success(user) } catch (e: Exception) { _uiState.value = UserUiState.Error(e.message) } } } } sealed class UserUiState { object Loading : UserUiState() data class Success(val user: User) : UserUiState() data class Error(val message: String?) : UserUiState() } ``` ### lifecycleScope : pour les Activities et Fragments ```kotlin // UserFragment.kt class UserFragment : Fragment() { private val viewModel: UserViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> when (state) { is UserUiState.Loading -> showLoading() is UserUiState.Success -> showUser(state.user) is UserUiState.Error -> showError(state.message) } } } } } } ``` > **Attention aux leaks** > > N'utilisez jamais `GlobalScope` dans une application Android. Les coroutines lancées avec `GlobalScope` ne sont pas liées au lifecycle et peuvent causer des memory leaks. ## Les Dispatchers : contrôler l'exécution Les dispatchers déterminent sur quel thread s'exécute votre coroutine. ### Les 4 dispatchers principaux ```kotlin // Dispatchers.kt // Main : thread principal (UI) viewModelScope.launch(Dispatchers.Main) { textView.text = "Mise à jour UI" } // IO : opérations I/O (réseau, base de données) viewModelScope.launch(Dispatchers.IO) { val data = repository.fetchFromNetwork() } // Default : calculs CPU intensifs viewModelScope.launch(Dispatchers.Default) { val result = heavyComputation(data) } // Unconfined : hérite du contexte appelant (usage rare) ``` ### withContext : changer de dispatcher ```kotlin // ImageProcessor.kt class ImageProcessor { suspend fun processImage(bitmap: Bitmap): Bitmap { return withContext(Dispatchers.Default) { // Traitement CPU intensif sur Default applyFilters(bitmap) } } suspend fun saveToGallery(bitmap: Bitmap) { withContext(Dispatchers.IO) { // Écriture disque sur IO saveToFile(bitmap) } } } // Usage dans le ViewModel viewModelScope.launch { val processed = imageProcessor.processImage(originalBitmap) imageProcessor.saveToGallery(processed) // Retour automatique sur Main pour update UI _uiState.value = UiState.Success(processed) } ``` ## Exécution parallèle avec async/await Pour exécuter des tâches en parallèle et combiner leurs résultats, utilisez `async`. ```kotlin // ParallelExecution.kt suspend fun loadDashboard(): Dashboard { return coroutineScope { // Lancement parallèle val userDeferred = async { userRepository.getUser() } val statsDeferred = async { statsRepository.getStats() } val notificationsDeferred = async { notificationRepository.getNotifications() } // Attente des résultats Dashboard( user = userDeferred.await(), stats = statsDeferred.await(), notifications = notificationsDeferred.await() ) } } ``` > **Performance** > > Avec `async`, les 3 appels s'exécutent en parallèle. Si chaque appel prend 1 seconde, le total sera ~1 seconde au lieu de 3 secondes en séquentiel. ## Gestion des erreurs ### try/catch classique ```kotlin // ErrorHandling.kt viewModelScope.launch { try { val user = userRepository.getUser(userId) _uiState.value = UiState.Success(user) } catch (e: HttpException) { _uiState.value = UiState.Error("Erreur serveur: ${e.code()}") } catch (e: IOException) { _uiState.value = UiState.Error("Erreur réseau") } catch (e: Exception) { _uiState.value = UiState.Error("Erreur inattendue") } } ``` ### CoroutineExceptionHandler ```kotlin // ExceptionHandler.kt class UserViewModel : ViewModel() { private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> _uiState.value = UiState.Error(throwable.message) Timber.e(throwable, "Erreur dans la coroutine") } fun loadUser(userId: String) { viewModelScope.launch(exceptionHandler) { val user = userRepository.getUser(userId) _uiState.value = UiState.Success(user) } } } ``` ### Result wrapper pattern ```kotlin // ResultPattern.kt sealed class Result { data class Success(val data: T) : Result() data class Error(val exception: Throwable) : Result() } suspend fun safeApiCall(apiCall: suspend () -> T): Result { return try { Result.Success(apiCall()) } catch (e: Exception) { Result.Error(e) } } // Usage class UserRepository(private val api: UserApi) { suspend fun getUser(id: String): Result = safeApiCall { api.getUser(id) } } // Dans le ViewModel viewModelScope.launch { when (val result = userRepository.getUser(userId)) { is Result.Success -> _uiState.value = UiState.Success(result.data) is Result.Error -> _uiState.value = UiState.Error(result.exception.message) } } ``` ## Cancellation : annuler proprement Les coroutines supportent l'annulation coopérative. C'est essentiel pour éviter les fuites de ressources. ### Annulation automatique avec les scopes ```kotlin // SearchViewModel.kt class SearchViewModel : ViewModel() { private var searchJob: Job? = null fun search(query: String) { // Annule la recherche précédente searchJob?.cancel() searchJob = viewModelScope.launch { delay(300) // Debounce val results = searchRepository.search(query) _searchResults.value = results } } } ``` ### Vérifier l'annulation ```kotlin // CancellationCheck.kt suspend fun processLargeList(items: List) { items.forEach { item -> // Vérifie si la coroutine est annulée ensureActive() processItem(item) } } suspend fun downloadFiles(urls: List) = coroutineScope { urls.map { url -> async { try { downloadFile(url) } catch (e: CancellationException) { cleanupPartialDownload(url) throw e // Re-throw pour propager l'annulation } } }.awaitAll() } ``` > **Ne jamais swallow CancellationException** : si vous attrapez `Exception`, re-throw `CancellationException` pour que l'annulation se propage correctement. ## Flow : programmation réactive Flow est l'équivalent coroutines de RxJava Observable, mais plus simple et intégré. ### Créer et collecter un Flow ```kotlin // FlowBasics.kt fun getUsers(): Flow> = flow { while (true) { val users = userApi.getUsers() emit(users) delay(5000) // Polling toutes les 5 secondes } } // Flow depuis Room @Dao interface UserDao { @Query("SELECT * FROM users") fun getAllUsers(): Flow> } // Collecte dans le ViewModel viewModelScope.launch { userDao.getAllUsers() .catch { e -> _uiState.value = UiState.Error(e.message) } .collect { users -> _uiState.value = UiState.Success(users) } } ``` ### StateFlow vs SharedFlow ```kotlin // StateFlowVsSharedFlow.kt class EventViewModel : ViewModel() { // StateFlow : garde la dernière valeur, idéal pour l'état UI private val _uiState = MutableStateFlow(UiState.Initial) val uiState: StateFlow = _uiState.asStateFlow() // SharedFlow : pour les events one-shot (navigation, snackbar) private val _events = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() fun onButtonClick() { viewModelScope.launch { _events.emit(UiEvent.NavigateToDetail) } } } sealed class UiEvent { object NavigateToDetail : UiEvent() data class ShowSnackbar(val message: String) : UiEvent() } ``` ### Opérateurs Flow essentiels ```kotlin // FlowOperators.kt userRepository.getUsers() .map { users -> users.filter { it.isActive } } .distinctUntilChanged() .debounce(300) .flatMapLatest { users -> fetchUserDetails(users) } .catch { e -> emit(emptyList()) } .onEach { users -> analytics.logUserCount(users.size) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) ``` ## Patterns avancés ### Retry avec backoff exponentiel ```kotlin // RetryPattern.kt suspend fun retryWithBackoff( times: Int = 3, initialDelay: Long = 100, maxDelay: Long = 1000, factor: Double = 2.0, block: suspend () -> T ): T { var currentDelay = initialDelay repeat(times - 1) { attempt -> try { return block() } catch (e: Exception) { Timber.w("Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms") } delay(currentDelay) currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) } return block() } // Usage val user = retryWithBackoff { userApi.getUser(userId) } ``` ### Timeout ```kotlin // TimeoutPattern.kt suspend fun fetchWithTimeout() { try { val result = withTimeout(5000L) { api.fetchData() } processResult(result) } catch (e: TimeoutCancellationException) { showError("La requête a pris trop de temps") } } // Ou avec une valeur par défaut val result = withTimeoutOrNull(5000L) { api.fetchData() } ?: defaultValue ``` ## Conclusion Les coroutines Kotlin sont devenues indispensables pour le développement Android moderne. Elles offrent une approche élégante et performante de la programmation asynchrone, parfaitement intégrée à l'écosystème Jetpack. ### Checklist - ✅ Utiliser `viewModelScope` et `lifecycleScope` pour éviter les leaks - ✅ Choisir le bon Dispatcher (Main, IO, Default) - ✅ Gérer les erreurs avec try/catch ou Result wrapper - ✅ Implémenter l'annulation coopérative - ✅ Utiliser Flow pour les streams de données réactifs - ✅ Préférer StateFlow pour l'état UI, SharedFlow pour les events Maîtriser les coroutines vous donnera un avantage significatif dans vos projets Android et lors de vos entretiens techniques. Pratiquez régulièrement et n'hésitez pas à explorer les patterns avancés ! --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/android/maitriser-kotlin-coroutines