viewModelScope — är en inbyggd CoroutineScope från androidx.lifecycle-biblioteket, som är kopplat till ViewModels livscykel och automatiskt avbryts vid dess rensning. Enligt Google Android Developers, 2025 är viewModelScope den standardmekanism för att starta koroutiner i MVVM-arkitektur som ger säker asynkron hantering utan risk för minnesläckor. ViewModelScope använder som standard Dispatchers.Main och alla IO-operationer inuti måste utföras via withContext.
Huvudpunkter
viewModelScope — är en extension-egenskap på ViewModel-gränssnittet, tillagd i lifecycle-viewmodel-ktx-biblioteket (från version 2.1.0). Det ger en färdig CoroutineScope kopplad till ViewModels livscykel.
// Intern struktur (förenklad)
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) }
}
Scope skapas lat (lazy) vid första åtkomst och cachas via setTag. SupervisorJob används, vilket innebär att ett undantag i en underordnad koroutin inte avbryter de andra. Standarddispatcher är Dispatchers.Main.immediate, som exekverar kod på huvudtråden utan ytterligare dispatchering om anropet redan är på Main.
När ViewModel lämnar livscykeln (Activity är avslutad eller Fragment är borttaget) anropar systemet clear(), som utlöser onCleared(). I denna callback avbryter viewModelScope sin Job, vilket rekursivt avslutar alla aktiva koroutiner. Mekanismen är implementerad via Closeable-gränssnittet, där scopes Job registreras som en resurs för automatisk stängning.
Mekanismen för att koppla viewModelScope till ViewModels livscykel baseras på taggning och callbacken onCleared. Låt oss steg för steg titta på hur det fungerar.
När ViewModel utför viewModelScope.launch { ... } kontrollerar gettern om det finns ett sparat scope under taggen JOB_KEY. Om scope inte har skapats än — skapas en ny instans av CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate). Scope lagras inuti ViewModel via en intern taggkarta.
Alla koroutiner som startas via viewModelScope.launch eller viewModelScope.async blir underordnade scopes SupervisorJob. De arbetar på huvudtråden (om ingen annan dispatcher anges via withContext). Så länge ViewModel lever — kan koroutiner vara aktiva, suspenderade eller slutförda.
När systemet förstör ViewModel anropas ViewModel.clear(). Inuti clear() händer följande:
Vid skärmrotation återskapas Activity, men ViewModel behålls (tack vare ViewModelStoreOwner). Detta innebär att viewModelScope förblir aktivt och koroutiner fortsätter utan avbrott. Efter återskapning av Activity används samma ViewModel (och samma scope) igen — dataladdning startar inte om.
MVVM (Model-View-ViewModel) — är den av Google rekommenderade arkitekturen för Android-applikationer. viewModelScope intar en central plats i den som utförare av asynkrona operationer.
| Lager | Komponent | viewModelScopes roll |
|---|---|---|
| UI | Activity / Fragment | Observerar StateFlow/LiveData från ViewModel |
| ViewModel | ViewModel | Startar koroutiner via viewModelScope, hanterar UI-status |
| Repository | Repository | Tar emot suspend-funktioner anropade från viewModelScopes koroutiner |
| Data | DAO / Api | Utför faktiska förfrågningar (Room, Retrofit) |
ViewModel startar via viewModelScope koroutiner, inuti vilka den anropar Repositorys suspend-funktioner. Resultatet omvandlas till StateFlow, som observeras av UI-lagret. Detta schema säkerställer tydlig ansvarsfördelning och testbarhet för varje lager oberoende.
Om koroutiner startades från Fragment skulle de vid skärmrotation avbrytas tillsammans med Fragmentets förstöring. ViewModel överlever rotation, så koroutiner startade i dess scope fortsätter. Detta är den viktigaste fördelen med viewModelScope jämfört med lifecycleScope vid dataladdning.
Låt oss titta på tre praktiska scenarier för användning av viewModelScope i en Android-applikation i 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
}
}
}
I init-blocket startas omedelbart profilens laddning. Koroutinen körs på huvudtråden (standard). Repositoryt använder withContext(Dispatchers.IO) för nätverksförfrågan inuti sin suspend-funktion, så ViewModel behöver inte bry sig om trådväxling.
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")
}
}
}
UI-status beskrivs via sealed class UiState. ViewModel uppdaterar state vid varje ändring. Fragment prenumererar på StateFlow och reagerar endast på aktuell status och ignorerar föråldrade anrop vid upprepade rotationer.
private var searchJob: Job? = null
fun search(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300)
val results = repo.search(query)
_searchResults.value = results
}
}
Vid varje ny sökbegäran avbryts föregående koroutin. delay(300) implementerar debounce — sökning utförs först efter 300 ms paus i inmatningen. Detta minskar serverbelastningen och förhindrar föråldrade resultat.
Båda scopen tillhandahålls av AndroidX Lifecycle-biblioteket men är kopplade till olika livscykler. Valet dem emellan beror på typen av uppgift.
| Egenskap | viewModelScope | lifecycleScope |
|---|---|---|
| Ägare | ViewModel | LifecycleOwner (Activity/Fragment) |
| Avbrytning vid rotation | Nej (ViewModel behålls) | Ja (Activity återskapas) |
| Standarddispatcher | Dispatchers.Main.immediate | Dispatchers.Main.immediate |
| Tillgänglig i | ViewModel | Activity, Fragment, Service |
| Typiskt scenario | Dataladdning, affärslogik | UI-interaktion, animationer, snackbars |
Google rekommenderar att använda viewModelScope för alla uppgifter relaterade till dataladdning och bearbetning. lifecycleScope bör tillämpas för operationer kopplade till ett specifikt ögonblick i UI:ts liv — till exempel att starta en animation vid första skärmvisning eller prenumerera på Location-uppdateringar som ska stoppas när skärmen lämnas.
Även i väldokumenterade Android API:er gör utvecklare karakteristiska misstag. Låt oss titta på fyra vanliga problem.
Det mest lömska misstaget — att försöka uppdatera StateFlow eller LiveData efter att ViewModel har rensats. Även om viewModelScope avbryts vid onCleared() kan koroutinen exekvera kod fram till den faktiska avbrytningen. Använd isActive för kontroll eller lita på att catch-blocket slutförs.
viewModelScope använder internt SupervisorJob, vilket isolerar fel mellan koroutiner. Men om du startar en koroutin med egen Job() inuti viewModelScope.launch blir den koroutinen underordnad SupervisorJob men skyddas inte från avbrytning vid fel i andra koroutiner.
Även om viewModelScope inte har någon strikt gräns kan tusentals aktiva koroutiner sakta ner systemet. För långa datalistor, använd Flow med collectLatest istället för att skapa separata koroutiner för varje element.
Om du av misstag importerar GlobalScope istället för viewModelScope kommer koroutinen inte att avbrytas vid rensning av ViewModel. Detta leder till minnesläckor och potentiell krasch. Kontrollera alltid att koroutiner startas via viewModelScope, särskilt i ärvda fragment.
Vanliga frågor
ViewModelScopes dispatcher kan inte ändras direkt — den är hårt inställd som Dispatchers.Main.immediate. Men inuti en koroutin kan man via withContext växla till en annan dispatcher. För att ändra dispatcher i tester, använd TestDispatcher via Rule.
Skicka inte scope till Repository — det bryter mot arkitekturprinciperna. Repository bör tillhandahålla suspend-funktioner och ViewModel hanterar själv koroutiner via viewModelScope. Om Repository kräver scope — omvärdera arkitekturen till förmån för Clean Architecture.
SupervisorJob garanterar att ett undantag i en koroutin (till exempel ett laddningsfel i en av flera oberoende förfrågningar) inte avbryter de andra koroutinerna. Detta överensstämmer med ViewModel-scenariot där olika skärmar laddar oberoende data.
Ja, viewModelScope är tillgängligt i alla ViewModels oavsett UI-typ (View System eller Jetpack Compose). I Compose startas koroutiner också via viewModelScope och för UI-effekter används LaunchedEffect och rememberCoroutineScope.
Anrop av viewModelScope.cancel() avbryter scope omedelbart — alla aktiva koroutiner avslutas med CancellationException. Om du därefter anropar viewModelScope.launch skapas ett nytt scope automatiskt vid nästa åtkomst till gettern.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också