Usar corrotinas do Kotlin com componentes que reconhecem o ciclo de vida

As corrotinas do Kotlin fornecem uma API que permite escrever código assíncrono. Com as corrotinas do Kotlin, é possível definir um CoroutineScope, que ajuda a gerenciar quando as corrotinas precisam ser executadas. Cada operação assíncrona é executada em um escopo específico.

Os componentes com reconhecimento de ciclo de vida oferecem suporte de primeira classe para corrotinas em escopos lógicos no app. Este documento explica como usar corrotinas de maneira eficaz com componentes com reconhecimento de ciclo de vida.

Adicionar dependências

Os escopos de corrotina integrados descritos neste tópico estão contidos na API Lifecycle. Adicione as dependências adequadas ao usar esses escopos.

  • Para utilitários do ViewModel no Compose, use implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version").
  • Para utilitários do Lifecycle no Compose, use implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version").

Escopos de corrotina com reconhecimento de ciclo de vida

O Compose e as bibliotecas de ciclo de vida fornecem os seguintes escopos integrados que podem ser usados no seu app.

ViewModelScope

Um ViewModelScope é definido para cada ViewModel no app. Qualquer corrotina iniciada nesse escopo será cancelada automaticamente se o ViewModel for apagado. As corrotinas são úteis quando você tem um trabalho que precisa ser feito somente se o ViewModel estiver ativo. Por exemplo, se você estiver computando alguns dados de um layout, será necessário definir o escopo do trabalho como ViewModel. Dessa forma, o trabalho será cancelado automaticamente para evitar o consumo de recursos caso o ViewModel seja apagado.

É possível acessar o CoroutineScope de um ViewModel pela propriedade viewModelScope do ViewModel, conforme mostrado no exemplo a seguir:

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

Para casos de uso mais avançados, transmita um CoroutineScope personalizado diretamente para o construtor do ViewModel para substituir o viewModelScope padrão. Essa abordagem oferece mais controle e flexibilidade, principalmente para:

  • Teste: permite injetar um TestScope, facilitando o controle do tempo e a verificação do comportamento da corrotina em testes de unidade.

  • Configuração personalizada: é possível configurar o escopo com um CoroutineDispatcher específico (como Dispatchers.Default para computação pesada) ou um CoroutineExceptionHandler personalizado antes mesmo que o ViewModel comece o trabalho.

Escopos vinculados à composição

Efeitos colaterais, como animações, chamadas de rede ou timers, precisam ser limitados ao ciclo de vida do elemento que pode ser composto. Dessa forma, quando um elemento combinável sai da tela (sai da composição), todas as corrotinas em execução são canceladas automaticamente para evitar vazamentos de memória.

O Compose oferece a API LaunchedEffect para processar o escopo da composição de forma declarativa.

LaunchedEffect cria um CoroutineScope que permite executar funções de suspensão. O escopo está vinculado ao ciclo de vida da composição do elemento combinável, não ao ciclo de vida da atividade host.

  • Entrada: a corrotina é iniciada quando o elemento combinável entra na composição.
  • Saída: a corrotina é cancelada quando o elemento combinável sai da composição.
  • Reinicialização: se alguma chave transmitida para LaunchedEffect mudar, a corrotina atual será cancelada e uma nova será iniciada.

O exemplo a seguir demonstra como usar LaunchedEffect para criar uma animação pulsante. A corrotina está vinculada à presença do elemento combinável na composição e reage às mudanças de configuração:

// 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)
    }
}

Para mais informações sobre LaunchedEffect, consulte Efeitos colaterais no Compose.

Coleta de fluxo com reconhecimento de ciclo de vida

Para coletar fluxos com segurança no Jetpack Compose, use a API collectAsStateWithLifecycle. Essa função única converte um Flow em um objeto State do Compose e gerencia automaticamente a inscrição do ciclo de vida para você. Por padrão, a coleta começa quando o ciclo de vida é STARTED e para quando o ciclo de vida é STOPPED. Para modificar esse comportamento padrão, transmita o parâmetro minActiveState com o método de ciclo de vida que você quer, como Lifecycle.State.RESUMED.

O exemplo abaixo demonstra como coletar o StateFlow de um ViewModel em um combinável:

@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)
    /* ... */
}

Coleta paralela de vários fluxos

No Compose, é possível coletar vários fluxos em paralelo declarando várias variáveis de estado. Como collectAsStateWithLifecycle gerencia o próprio escopo, a coleta paralela é processada 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()

    // ...
}

Calcular valores de forma assíncrona usando fluxos

Quando você precisar calcular valores de forma assíncrona, use StateFlow com o operador stateIn.

O snippet a seguir usa um Flow padrão convertido em um StateFlow. O parâmetro WhileSubscribed(5000) mantém a inscrição ativa por cinco segundos após o desaparecimento da interface para processar mudanças de configuração.

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

Use collectAsStateWithLifecycle para converter os valores coletados em State do Compose, para que a IU possa ser atualizada de forma reativa sempre que os dados mudarem.

Para mais informações sobre o estado, consulte Estado e Jetpack Compose.

Outros recursos

Visualiza conteúdo

Amostras