Kotlin Coroutines per Android: Guida Completa 2026
Guida approfondita alle coroutine Kotlin per lo sviluppo Android: funzioni suspend, scope, dispatcher, Flow e pattern avanzati.

Le coroutine Kotlin hanno trasformato radicalmente la programmazione asincrona su Android. Il periodo dei callback annidati e delle deprecate AsyncTask appartiene al passato: con le coroutine, lo sviluppatore scrive codice asincrono dall'aspetto sincrono, mantenendo prestazioni elevate e leggibilità del codice.
Le coroutine sono leggere (migliaia possono girare su un singolo thread), supportano la cancellazione nativa e si integrano perfettamente con Jetpack e l'ecosistema Android moderno.
Comprendere i Fondamenti
Prima di analizzare il codice, è fondamentale capire cosa rende le coroutine uno strumento così potente.
Cos'è una Coroutine?
Una coroutine è un'istanza di computazione sospendibile. A differenza dei thread, le coroutine non bloccano: sospendono la propria esecuzione e liberano il thread per altre operazioni.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L) // Suspend without blocking
println("World!")
}
println("Hello,")
}
// Output: Hello, World!Funzioni Suspend: Il Cuore delle Coroutine
La parola chiave suspend indica che una funzione può sospendere l'esecuzione della coroutine senza bloccare il thread.
suspend fun fetchUserData(userId: String): User {
return withContext(Dispatchers.IO) {
// Network call - runs on an IO thread
apiService.getUser(userId)
}
}
suspend fun fetchUserWithPosts(userId: String): UserWithPosts {
// Sequential execution
val user = fetchUserData(userId)
val posts = fetchUserPosts(userId)
return UserWithPosts(user, posts)
}Regola fondamentale: una funzione suspend può essere chiamata solo da un'altra funzione suspend o dall'interno di una coroutine.
Coroutine Scope
Lo scope definisce il ciclo di vita delle coroutine. Si tratta di un aspetto cruciale per evitare memory leak.
viewModelScope: Lo Scope per i ViewModel
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _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: Per Activity e Fragment
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)
}
}
}
}
}
}Non utilizzare mai GlobalScope in un'applicazione Android. Le coroutine lanciate con GlobalScope non sono legate ad alcun ciclo di vita e possono causare memory leak.
Dispatcher: Controllare l'Esecuzione
I dispatcher determinano su quale thread viene eseguita la coroutine.
I 4 Dispatcher Principali
// Main: main thread (UI)
viewModelScope.launch(Dispatchers.Main) {
textView.text = "UI update"
}
// IO: I/O operations (network, database)
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchFromNetwork()
}
// Default: CPU-intensive computations
viewModelScope.launch(Dispatchers.Default) {
val result = heavyComputation(data)
}
// Unconfined: inherits caller context (rare use)withContext: Cambiare Dispatcher
class ImageProcessor {
suspend fun processImage(bitmap: Bitmap): Bitmap {
return withContext(Dispatchers.Default) {
// CPU-intensive processing on Default
applyFilters(bitmap)
}
}
suspend fun saveToGallery(bitmap: Bitmap) {
withContext(Dispatchers.IO) {
// Disk write on IO
saveToFile(bitmap)
}
}
}
// Usage in ViewModel
viewModelScope.launch {
val processed = imageProcessor.processImage(originalBitmap)
imageProcessor.saveToGallery(processed)
// Automatic return to Main for UI update
_uiState.value = UiState.Success(processed)
}Esecuzione Parallela con async/await
Per eseguire operazioni in parallelo e combinarne i risultati, si utilizza async.
suspend fun loadDashboard(): Dashboard {
return coroutineScope {
// Parallel launch
val userDeferred = async { userRepository.getUser() }
val statsDeferred = async { statsRepository.getStats() }
val notificationsDeferred = async { notificationRepository.getNotifications() }
// Await results
Dashboard(
user = userDeferred.await(),
stats = statsDeferred.await(),
notifications = notificationsDeferred.await()
)
}
}Con async, tutte e 3 le chiamate vengono eseguite in parallelo. Se ciascuna richiesta impiega 1 secondo, il tempo totale è circa 1 secondo anziché 3 secondi in sequenza.
Gestione degli Errori
try/catch Classico
viewModelScope.launch {
try {
val user = userRepository.getUser(userId)
_uiState.value = UiState.Success(user)
} catch (e: HttpException) {
_uiState.value = UiState.Error("Server error: ${e.code()}")
} catch (e: IOException) {
_uiState.value = UiState.Error("Network error")
} catch (e: Exception) {
_uiState.value = UiState.Error("Unexpected error")
}
}CoroutineExceptionHandler
class UserViewModel : ViewModel() {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
_uiState.value = UiState.Error(throwable.message)
Timber.e(throwable, "Error in coroutine")
}
fun loadUser(userId: String) {
viewModelScope.launch(exceptionHandler) {
val user = userRepository.getUser(userId)
_uiState.value = UiState.Success(user)
}
}
}Pattern Result Wrapper
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
suspend fun <T> safeApiCall(apiCall: suspend () -> T): Result<T> {
return try {
Result.Success(apiCall())
} catch (e: Exception) {
Result.Error(e)
}
}
// Usage
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): Result<User> = safeApiCall {
api.getUser(id)
}
}
// In 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)
}
}Pronto a superare i tuoi colloqui su Android?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Cancellazione: Ripulire Correttamente
Le coroutine supportano la cancellazione cooperativa. Si tratta di un aspetto essenziale per evitare sprechi di risorse.
Cancellazione Automatica con gli Scope
class SearchViewModel : ViewModel() {
private var searchJob: Job? = null
fun search(query: String) {
// Cancel previous search
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300) // Debounce
val results = searchRepository.search(query)
_searchResults.value = results
}
}
}Verificare la Cancellazione
suspend fun processLargeList(items: List<Item>) {
items.forEach { item ->
// Check if coroutine is cancelled
ensureActive()
processItem(item)
}
}
suspend fun downloadFiles(urls: List<String>) = coroutineScope {
urls.map { url ->
async {
try {
downloadFile(url)
} catch (e: CancellationException) {
cleanupPartialDownload(url)
throw e // Re-throw to propagate cancellation
}
}
}.awaitAll()
}Non intercettare mai CancellationException: se si cattura Exception, è necessario rilanciare CancellationException affinché la cancellazione si propaghi correttamente.
Flow: Programmazione Reattiva
Flow rappresenta l'equivalente di RxJava Observable nel mondo delle coroutine, ma con un'API più semplice e un'integrazione nativa.
Creare e Raccogliere un Flow
fun getUsers(): Flow<List<User>> = flow {
while (true) {
val users = userApi.getUsers()
emit(users)
delay(5000) // Poll every 5 seconds
}
}
// Flow from Room
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
}
// Collecting in ViewModel
viewModelScope.launch {
userDao.getAllUsers()
.catch { e -> _uiState.value = UiState.Error(e.message) }
.collect { users ->
_uiState.value = UiState.Success(users)
}
}StateFlow vs SharedFlow
class EventViewModel : ViewModel() {
// StateFlow: keeps last value, ideal for UI state
private val _uiState = MutableStateFlow(UiState.Initial)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// SharedFlow: for one-shot events (navigation, snackbar)
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun onButtonClick() {
viewModelScope.launch {
_events.emit(UiEvent.NavigateToDetail)
}
}
}
sealed class UiEvent {
object NavigateToDetail : UiEvent()
data class ShowSnackbar(val message: String) : UiEvent()
}Operatori Flow Essenziali
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()
)Pattern Avanzati
Retry con Exponential Backoff
suspend fun <T> 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
suspend fun fetchWithTimeout() {
try {
val result = withTimeout(5000L) {
api.fetchData()
}
processResult(result)
} catch (e: TimeoutCancellationException) {
showError("Request took too long")
}
}
// Or with a default value
val result = withTimeoutOrNull(5000L) {
api.fetchData()
} ?: defaultValueConclusione
Le coroutine Kotlin sono diventate uno strumento imprescindibile per lo sviluppo Android moderno. Offrono un approccio elegante e performante alla programmazione asincrona, perfettamente integrato con l'ecosistema Jetpack.
Checklist
- ✅ Utilizzare
viewModelScopeelifecycleScopeper evitare memory leak - ✅ Scegliere il Dispatcher corretto (Main, IO, Default)
- ✅ Gestire gli errori con try/catch o il pattern Result wrapper
- ✅ Implementare la cancellazione cooperativa
- ✅ Utilizzare Flow per flussi di dati reattivi
- ✅ Preferire StateFlow per lo stato UI e SharedFlow per gli eventi
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Padroneggiare le coroutine garantisce un vantaggio significativo nei progetti Android e nei colloqui tecnici. La pratica costante e l'esplorazione dei pattern avanzati sono la chiave per raggiungere la competenza completa.
Sapresti trovare il bug in Android?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 2 aprile 2026
Tag
Condividi
Articoli correlati

Kotlin Flow vs StateFlow vs SharedFlow: domande da colloquio Android nel 2026
Le domande su Kotlin Flow vs StateFlow vs SharedFlow che gli intervistatori Android pongono nel 2026, con risposte chiare, una tabella comparativa e codice pronto per la produzione.

Room Database in Android 2026: Migrazioni, Relazioni e Coroutines
Padroneggia Room database in Android con migrazioni, relazioni tra entità, Kotlin Coroutines e Flow. Tutorial completo con pattern pronti per la produzione per il 2026.

Kotlin 2.3 per Android: Destrutturazione Basata sui Nomi, KMP e Domande da Colloquio 2026
Domande da colloquio su Kotlin 2.3 riguardanti la destrutturazione basata sui nomi, Kotlin Multiplatform, parametri di contesto, coroutine e Flow. Preparazione ai colloqui per sviluppatori Android nel 2026 con esempi di codice reali.