Utilizzare le cooutine Kotlin con componenti sensibili al ciclo di vita

Le coroutine Kotlin forniscono un'API che consente di scrivere codice asincrono Con le coroutine Kotlin, puoi definire un CoroutineScope, che ti aiuta a gestire quando devono essere eseguite le coroutine. Ogni operazione asincrona viene eseguita in un ambito specifico.

I componenti che tengono conto del ciclo di vita forniscono un supporto di prima classe per le coroutine per gli ambiti logici nella tua app. Questo documento spiega come utilizzare le coroutine in modo efficace con i componenti che tengono conto del ciclo di vita.

Aggiungi dipendenze

Gli ambiti delle coroutine integrate descritti in questo argomento sono contenuti nell'API Lifecycle. Assicurati di aggiungere le dipendenze appropriate quando utilizzi questi ambiti.

  • Per le utilità di ViewModel in Compose, utilizza implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version").
  • Per le utilità del ciclo di vita in Compose, utilizza implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version").

Ambiti delle coroutine che tengono conto del ciclo di vita

Compose e le librerie del ciclo di vita forniscono i seguenti ambiti integrati che puoi utilizzare nella tua app.

ViewModelScope

Un ViewModelScope è definito per ogni ViewModel nella tua app. Qualsiasi coroutine avviata in questo ambito viene annullata automaticamente se il ViewModel viene cancellato. Le coroutine sono utili in questo caso quando hai un lavoro che deve essere svolto solo se ViewModel è attivo. Ad esempio, se stai calcolando alcuni dati per un layout, devi limitare il lavoro a ViewModel in modo che, se ViewModel viene cancellato, il lavoro venga annullato automaticamente per evitare di consumare risorse.

Puoi accedere a CoroutineScope di un ViewModel tramite la proprietà viewModelScope di ViewModel, come mostrato nell'esempio seguente:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

Per casi d'uso più avanzati, puoi passare un CoroutineScope personalizzato direttamente nel costruttore di ViewModel per sostituire viewModelScope predefinito. Questo approccio offre maggiore controllo e flessibilità, in particolare per:

  • Test: ti consente di inserire un TestScope, semplificando il controllo del tempo e la verifica del comportamento delle coroutine nei test unitari.

  • Configurazione personalizzata: puoi configurare l'ambito con un CoroutineDispatcher specifico (ad esempio Dispatchers.Default per calcoli complessi) o un CoroutineExceptionHandler personalizzato prima ancora che ViewModel inizi il suo lavoro.

Ambiti associati alla composizione

Gli effetti collaterali come animazioni, chiamate di rete o timer devono essere limitati al ciclo di vita del composable. In questo modo, quando un composable esce dalla schermata (esce dalla composizione), tutte le coroutine in esecuzione vengono annullate automaticamente per evitare perdite di memoria.

Compose fornisce l'API LaunchedEffect per gestire la definizione dell'ambito della composizione in modo dichiarativo.

LaunchedEffect crea un CoroutineScope che ti consente di eseguire funzioni di sospensione. L'ambito è legato al ciclo di vita della composizione del composable, non al ciclo di vita dell'attività host.

  • Ingresso: la coroutine inizia quando il composable entra nella composizione.
  • Uscita: la coroutine viene annullata quando il composable esce dalla composizione.
  • Riavvio: se una delle chiavi passate a LaunchedEffect cambia, la coroutine esistente viene annullata e ne viene avviata una nuova.

L'esempio seguente mostra come utilizzare LaunchedEffect per creare un'animazione a impulsi. La coroutine è legata alla presenza del composable nella composizione e reagisce alle modifiche di configurazione:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

Per ulteriori informazioni su LaunchedEffect, consulta Effetti collaterali in Compose.

Raccolta di flussi che tengono conto del ciclo di vita

Per raccogliere in modo sicuro i flussi in Jetpack Compose, utilizza l' collectAsStateWithLifecycle API. Questa singola funzione converte un Flow in un oggetto State di Compose e gestisce automaticamente l'abbonamento al ciclo di vita. Per impostazione predefinita, la raccolta inizia quando il ciclo di vita è STARTED e si interrompe quando il ciclo di vita è STOPPED. Per sostituire questo comportamento predefinito, passa il parametro minActiveState con il metodo del ciclo di vita che preferisci, ad esempio Lifecycle.State.RESUMED.

L'esempio seguente mostra come raccogliere un StateFlow di ViewModel in un composable:

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

Raccolta parallela di più flussi

In Compose, puoi raccogliere più flussi in parallelo dichiarando più variabili di stato. Poiché collectAsStateWithLifecycle gestisce il proprio ambito sottostante, la raccolta parallela viene gestita automaticamente:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

Calcola i valori in modo asincrono utilizzando i flussi

Quando devi calcolare i valori in modo asincrono, utilizza StateFlow con l'operatore stateIn.

Il seguente snippet utilizza un Flow standard convertito in un StateFlow. Il WhileSubscribed(5000) parametro mantiene attivo l'abbonamento per cinque secondi dopo la scomparsa dell'UI per gestire le modifiche di configurazione.

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

Utilizza collectAsStateWithLifecycle per convertire i valori raccolti in Compose State, in modo che l'UI possa aggiornarsi in modo reattivo ogni volta che i dati cambiano.

Per ulteriori informazioni sullo stato, consulta Stato e Jetpack Compose.

Risorse aggiuntive

Visualizza contenuti

Esempi