viewModelScope é um CoroutineScope integrado da biblioteca androidx.lifecycle que está vinculado ao ciclo de vida do ViewModel e é cancelado automaticamente quando ele é limpo. Segundo Google Android Developers, 2025, o viewModelScope é o mecanismo padrão para lançar corrotinas na arquitetura MVVM, garantindo operações assíncronas seguras sem risco de vazamento de memória. O ViewModelScope usa Dispatchers.Main por padrão, e todas as operações de IO dentro dele devem ser executadas via withContext.
Pontos principais
viewModelScope é uma propriedade de extensão na interface ViewModel, adicionada na biblioteca lifecycle-viewmodel-ktx (a partir da versão 2.1.0). Ela fornece um CoroutineScope pronto para uso vinculado ao ciclo de vida do ViewModel.
// Internal structure (simplified)
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope = this.getTag(JOB_KEY)
if (scope != null) return scope
return CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
.also { setTag(JOB_KEY, it) }
}
O escopo é criado de forma preguiçosa (lazy) no primeiro acesso e armazenado em cache via setTag. Ele usa SupervisorJob, o que significa que uma exceção em uma corrotina filha não cancela as outras. O dispatcher padrão é Dispatchers.Main.immediate, que executa código na thread principal sem despacho adicional se já estiver na Main.
Quando o ViewModel sai do ciclo de vida (a Activity é finalizada ou o Fragment é removido), o sistema chama clear(), que aciona onCleared(). Neste callback, o viewModelScope cancela seu Job, que encerra recursivamente todas as corrotinas ativas. O mecanismo é implementado através da interface Closeable, onde o Job do escopo é registrado como um recurso para fechamento automático.
O mecanismo de vinculação do viewModelScope ao ciclo de vida do ViewModel é baseado em taggeamento e no callback onCleared. Vamos analisar passo a passo.
Quando o ViewModel executa viewModelScope.launch { ... }, o getter verifica se já existe um escopo armazenado sob a tag JOB_KEY. Se não existir, uma nova instância de CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) é criada. O escopo é armazenado dentro do ViewModel através de um mapa interno de tags.
Todas as corrotinas lançadas via viewModelScope.launch ou viewModelScope.async tornam-se filhas do SupervisorJob do escopo. Elas executam na thread principal (a menos que um dispatcher diferente seja especificado via withContext). Enquanto o ViewModel estiver vivo, as corrotinas podem estar ativas, suspensas ou concluídas.
Quando o sistema destrói o ViewModel, ViewModel.clear() é chamado. Dentro de clear(), ocorre o seguinte:
Quando a tela é girada, a Activity é recriada, mas o ViewModel sobrevive (graças ao ViewModelStoreOwner). Isso significa que o viewModelScope permanece ativo e as corrotinas continuam executando sem interrupção. Após a recriação da Activity, o mesmo ViewModel (e o mesmo escopo) é reutilizado — o carregamento de dados não recomeça do zero.
MVVM (Model-View-ViewModel) é a arquitetura recomendada pelo Google para aplicativos Android. viewModelScope ocupa um lugar central nela como executor de operações assíncronas.
| Camada | Componente | Papel do viewModelScope |
|---|---|---|
| UI | Activity / Fragment | Observa StateFlow/LiveData do ViewModel |
| ViewModel | ViewModel | Lança corrotinas via viewModelScope, gerencia o estado da UI |
| Repository | Repository | Expõe funções suspend chamadas das corrotinas do viewModelScope |
| Data | DAO / Api | Executa as requisições reais (Room, Retrofit) |
O ViewModel lança corrotinas via viewModelScope, dentro das quais chama funções suspend do Repository. O resultado é transformado em StateFlow, que é observado pela camada de UI. Este design garante clara separação de responsabilidades e testabilidade independente de cada camada.
Se as corrotinas fossem lançadas de um Fragment, seriam canceladas ao girar a tela junto com a destruição do Fragment. O ViewModel sobrevive à rotação, portanto as corrotinas lançadas em seu escopo continuam em execução. Esta é a principal vantagem do viewModelScope sobre o lifecycleScope ao carregar dados.
Vamos analisar três cenários práticos de uso do viewModelScope em um aplicativo Android com Kotlin.
class ProfileViewModel(
private val repo: ProfileRepository
) : ViewModel() {
private val _profile = MutableStateFlow<Profile?>(null)
val profile: StateFlow<Profile?> = _profile
init {
loadProfile()
}
private fun loadProfile() {
viewModelScope.launch {
val result = repo.getProfile()
_profile.value = result
}
}
}
No bloco init, o carregamento do perfil começa imediatamente. A corrotina executa na thread principal (por padrão). O repositório usa withContext(Dispatchers.IO) para a requisição de rede dentro de sua função suspend, então o ViewModel não precisa se preocupar com a troca de threads.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
fun fetchItems() {
_state.value = UiState.Loading
viewModelScope.launch {
try {
val items = repo.getItems()
_state.value = UiState.Success(items)
} catch (e: Exception) {
_state.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
O estado da UI é descrito através de uma sealed class UiState. O ViewModel atualiza o estado a cada mudança. O Fragment se inscreve no StateFlow e reage apenas ao estado atual, ignorando chamadas obsoletas de rotações anteriores.
private var searchJob: Job? = null
fun search(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300)
val results = repo.search(query)
_searchResults.value = results
}
}
A cada nova consulta de pesquisa, a corrotina anterior é cancelada. delay(300) implementa debounce — a pesquisa é executada apenas após 300 ms de inatividade. Isso reduz a carga do servidor e evita resultados obsoletos.
Ambos os escopos são fornecidos pela biblioteca AndroidX Lifecycle, mas estão vinculados a ciclos de vida diferentes. A escolha depende do tipo de tarefa.
| Característica | viewModelScope | lifecycleScope |
|---|---|---|
| Proprietário | ViewModel | LifecycleOwner (Activity/Fragment) |
| Cancelado na rotação | Não (ViewModel sobrevive) | Sim (Activity é recriada) |
| Dispatcher padrão | Dispatchers.Main.immediate | Dispatchers.Main.immediate |
| Disponível em | ViewModel | Activity, Fragment, Service |
| Caso de uso típico | Carregamento de dados, lógica de negócios | Interações de UI, animações |
O Google recomenda usar viewModelScope para todas as tarefas de carregamento e processamento de dados. O lifecycleScope deve ser usado para operações vinculadas a um momento específico do ciclo de vida da UI — por exemplo, iniciar uma animação na primeira aparição da tela ou se inscrever em atualizações de localização que devem parar ao sair da tela.
Mesmo em uma API Android bem documentada, os desenvolvedores cometem erros típicos. Vamos analisar quatro dos problemas mais comuns.
O erro mais traiçoeiro é tentar atualizar StateFlow ou LiveData depois que o ViewModel foi limpo. Embora o viewModelScope seja cancelado no onCleared(), uma corrotina pode executar código antes que o cancelamento entre em vigor. Use isActive para verificar ou confie na conclusão do bloco catch.
O viewModelScope usa SupervisorJob internamente, o que isola erros entre corrotinas. No entanto, se você lançar uma corrotina com seu próprio Job() dentro de viewModelScope.launch, essa corrotina se torna filha do SupervisorJob, mas não estará protegida contra cancelamento causado por erros em outras corrotinas.
Embora o viewModelScope não tenha um limite rígido, milhares de corrotinas ativas podem lentificar o sistema. Para listas longas de dados, use Flow com collectLatest em vez de criar corrotinas separadas para cada item.
Se GlobalScope for importado acidentalmente em vez de viewModelScope, a corrotina não será cancelada quando o ViewModel for limpo. Isso leva a vazamentos de memória e possíveis falhas. Sempre garanta que as corrotinas sejam lançadas via viewModelScope, especialmente em subclasses de Fragment.
Perguntas frequentes
Você não pode alterar diretamente o dispatcher do viewModelScope — ele está fixado como Dispatchers.Main.immediate. No entanto, dentro de uma corrotina você pode mudar para outro dispatcher via withContext. Para alterar o dispatcher em testes, use TestDispatcher através de uma Rule.
Não passe o escopo para o Repository — isso quebra os princípios arquiteturais. O Repository deve expor funções suspend, e o próprio ViewModel gerencia as corrotinas através do viewModelScope. Se o Repository precisar de um escopo, repense a arquitetura em favor da Clean Architecture.
O SupervisorJob garante que uma exceção em uma corrotina (por exemplo, um erro de carregamento em uma de várias requisições independentes) não cancele as outras corrotinas. Isso corresponde ao cenário do ViewModel, onde diferentes telas carregam dados independentes.
Sim, o viewModelScope está disponível em qualquer ViewModel independentemente do tipo de UI (View System ou Jetpack Compose). No Compose, as corrotinas também são lançadas via viewModelScope, enquanto para efeitos de UI são usados LaunchedEffect e rememberCoroutineScope.
Chamar viewModelScope.cancel() cancela o escopo imediatamente — todas as corrotinas ativas terminam com CancellationException. Se viewModelScope.launch for chamado depois, um novo escopo é criado automaticamente no próximo acesso ao getter.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também