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

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

Os componentes que reconhecem o ciclo de vida oferecem suporte de primeira classe para corrotinas em escopos lógicos no seu app. Este documento explica como usar corrotinas de maneira eficaz com componentes que reconhecem o 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 Lifecycle fornecem os seguintes escopos integrados que você pode usar 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 aqui quando você tem um trabalho que precisa ser executado 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, você pode transmitir um CoroutineScope personalizado diretamente para o construtor do ViewModel para substituir o viewModelScope padrão. Essa abordagem oferece mais controle e flexibilidade, principalmente para:

  • Testes: 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 a funcionar.

Escopos vinculados à composição

Efeitos colaterais, como animações, chamadas de rede ou timers, precisam ser definidos para o ciclo de vida do elemento combinável. 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 fornece a 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 do host.

  • Entrar: a corrotina começa quando o elemento combinável entra na composição.
  • Sair: a corrotina é cancelada quando o elemento combinável sai da composição.
  • Reiniciar: 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 a 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 collectAsStateWithLifecycle API. Essa função única converte um Flow em um objeto State do Compose e gerencia automaticamente a assinatura do ciclo de vida. Por padrão, a coleta começa quando o ciclo de vida é STARTED e para quando o ciclo de vida é STOPPED. Para substituir esse comportamento padrão, transmita o parâmetro minActiveState com o método de ciclo de vida desejado, como Lifecycle.State.RESUMED.

O exemplo a seguir demonstra como coletar um StateFlow do ViewModel em um elemento 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 subjacente, 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 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 WhileSubscribed(5000) parâmetro mantém a assinatura 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 interface 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

Conteúdo de visualizações

Amostras