viewModelScope: co to je, vazba na ViewModel a fungování v Androidu

Autor: IT Sectr Publikováno: 2026-06-23 Doba čtení: 9 min

viewModelScope — je vestavěný CoroutineScope z knihovny androidx.lifecycle, který je svázán s životním cyklem ViewModel a automaticky se ruší při jeho čištění. Podle Google Android Developers, 2025 je viewModelScope standardním mechanismem spouštění korutin v architektuře MVVM, který poskytuje bezpečnou práci s asynchronními operacemi bez rizika úniku paměti. ViewModelScope standardně používá Dispatchers.Main a všechny IO operace uvnitř něj musí být prováděny přes withContext.

Hlavní body

  • viewModelScope — CoroutineScope z lifecycle-viewmodel-ktx, ruší se při onCleared() ViewModel
  • Dispatchers.Main — výchozí dispatcher, proto jsou UI aktualizace uvnitř korutin bezpečné
  • onCleared — callback, při jehož volání viewModelScope automaticky ruší všechny aktivní korutiny
  • clear() vs onCleared() — clear() je volán frameworkem před onCleared, což zaručuje zrušení scope
  • launch — hlavní způsob spouštění korutin v viewModelScope pro fire-and-forget operace

Co je viewModelScope v Androidu?

viewModelScope — je rozšiřující vlastnost (extension property) na rozhraní ViewModel, přidaná v knihovně lifecycle-viewmodel-ktx (od verze 2.1.0). Poskytuje hotový CoroutineScope svázaný s životním cyklem ViewModel.

kotlin
// Vnitřní struktura (zjednodušená)
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 je vytvářen líně (lazy) při prvním přístupu a je ukládán do mezipaměti přes setTag. Používá se SupervisorJob, což znamená, že výjimka v jedné podřízené korutině neruší ostatní. Výchozí dispatcher je Dispatchers.Main.immediate, který spouští kód na hlavním vlákně bez dalšího dispatchování, pokud je volání již na Main.

Jak viewModelScope obdrží oznámení o čištění

Když ViewModel opustí životní cyklus (Activity je dokončena nebo Fragment je odstraněn), systém zavolá clear(), který spustí onCleared(). V tomto callbacku viewModelScope ruší svůj Job, což rekurzivně ukončí všechny aktivní korutiny. Mechanismus je implementován přes rozhraní Closeable, kde je Job scope registrován jako prostředek pro automatické uzavření.

Jak funguje viewModelScope: vazba na životní cyklus ViewModel

Mechanismus vazby viewModelScope na životní cyklus ViewModel je založen na tagování a callbacku onCleared. Podívejme se krok za krokem, jak to funguje.

Krok 1: Vytvoření scope při prvním přístupu

Když ViewModel provádí viewModelScope.launch { ... }, getter zkontroluje, zda existuje uložený scope pod tagem JOB_KEY. Pokud scope ještě není vytvořen — vytvoří se nová instance CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate). Scope je uložen uvnitř ViewModel přes vnitřní mapu tagů.

Krok 2: Životní cyklus korutin

Všechny korutiny spuštěné přes viewModelScope.launch nebo viewModelScope.async se stávají podřízenými vůči SupervisorJob scope. Pracují na hlavním vlákně (pokud není uveden jiný dispatcher přes withContext). Dokud je ViewModel živý — korutiny mohou být aktivní, pozastavené nebo dokončené.

Krok 3: Zrušení při onCleared

Když systém ničí ViewModel, je voláno ViewModel.clear(). Uvnitř clear() dochází k následujícímu:

  • Volání onCleared() pro uživatelskou logiku
  • Uzavření všech Closeable prostředků registrovaných přes addCloseable
  • Job viewModelScope přechází do stavu Cancelled
  • Všechny podřízené korutiny jsou rekurzivně rušeny
  • Uvolnění referencí na scope pro garbage collector

Odolnost vůči rotaci obrazovky

Při rotaci obrazovky je Activity znovu vytvořeno, ale ViewModel je zachován (díky ViewModelStoreOwner). To znamená, že viewModelScope zůstává aktivní a korutiny pokračují v běhu bez přerušení. Po znovuvytvoření Activity je stejný ViewModel (a stejný scope) použit znovu — načítání dat nezačíná od začátku.

viewModelScope v architektuře MVVM

MVVM (Model-View-ViewModel) — je architektura doporučená Googlem pro Android aplikace. viewModelScope v ní zaujímá ústřední místo jako vykonavatel asynchronních operací.

Role viewModelScope ve vrstvách architektury

VrstvaKomponentRole viewModelScope
UIActivity / FragmentSleduje StateFlow/LiveData z ViewModel
ViewModelViewModelSpouští korutiny přes viewModelScope, spravuje stav UI
RepositoryRepositoryPřijímá suspend-funkce volané z korutin viewModelScope
DataDAO / ApiProvádí skutečné dotazy (Room, Retrofit)

ViewModel přes viewModelScope spouští korutiny, uvnitř kterých volá suspend-funkce Repository. Výsledek je převeden na StateFlow, který je sledován UI vrstvou. Toto schéma zajišťuje jasné oddělení odpovědností a testovatelnost každé vrstvy nezávisle.

Proč viewModelScope ve ViewModel, ne ve Fragment

Pokud by korutiny byly spouštěny z Fragmentu, při rotaci obrazovky by byly zrušeny spolu se zničením Fragmentu. ViewModel přežije rotaci, takže korutiny spuštěné v jeho scope pokračují v běhu. To je klíčová výhoda viewModelScope oproti lifecycleScope při načítání dat.

Příklady použití viewModelScope

Podívejme se na tři praktické scénáře použití viewModelScope v Android aplikaci v Kotlin.

Příklad 1: Načítání dat při vytvoření ViewModel

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

V bloku init je okamžitě spuštěno načítání profilu. Korutina běží na hlavním vlákně (standardně). Repository používá withContext(Dispatchers.IO) pro síťový požadavek uvnitř své suspend-funkce, takže ViewModel se nemusí starat o přepínání vláken.

Příklad 2: Zpracování chyb přes sealed class

kotlin
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}
kotlin
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")
        }
    }
}

Stav UI je popsán přes sealed class UiState. ViewModel aktualizuje state při každé změně. Fragment je přihlášen k odběru StateFlow a reaguje pouze na aktuální stav, ignoruje zastaralá volání při opakovaných rotacích.

Příklad 3: Zrušení předchozí korutiny při novém požadavku

kotlin
private var searchJob: Job? = null

fun search(query: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        delay(300)
        val results = repo.search(query)
        _searchResults.value = results
    }
}

Při každém novém vyhledávacím požadavku je předchozí korutina zrušena. delay(300) implementuje debounce — vyhledávání se provede až po 300 ms pauze ve vstupu. To snižuje zatížení serveru a zabraňuje zastaralým výsledkům.

viewModelScope vs lifecycleScope: kdy co zvolit

Oba scope jsou poskytovány knihovnou AndroidX Lifecycle, ale jsou svázány s různými životními cykly. Volba mezi nimi závisí na typu úkolu.

Srovnání scope

VlastnostviewModelScopelifecycleScope
VlastníkViewModelLifecycleOwner (Activity/Fragment)
Zrušení při rotaciNe (ViewModel je zachován)Ano (Activity je znovu vytvořeno)
Výchozí dispatcherDispatchers.Main.immediateDispatchers.Main.immediate
Dostupný vViewModelActivity, Fragment, Service
Typický scénářNačítání dat, obchodní logikaInterakce UI, animace, snackbary

Doporučení Google

Google doporučuje používat viewModelScope pro všechny úkoly související s načítáním a zpracováním dat. lifecycleScope by měl být aplikován pro operace vázané na konkrétní okamžik života UI — například spuštění animace při prvním zobrazení obrazovky nebo přihlášení k odběru Location aktualizací, které by měly skončit při opuštění obrazovky.

Typické chyby při práci s viewModelScope

I v dobře zdokumentovaném API Androidu dělají vývojáři charakteristické chyby. Podívejme se na čtyři nejčastější problémy.

Chyba 1: Aktualizace UI po zrušení scope

Nejzákeřnější chyba — pokus o aktualizaci StateFlow nebo LiveData po vyčištění ViewModel. Přestože je viewModelScope zrušen při onCleared(), korutina může provést kód až do okamžiku skutečného zrušení. Použijte isActive pro kontrolu nebo se spolehněte na dokončení catch bloku.

Chyba 2: Spouštění korutin bez ohledu na SupervisorJob

viewModelScope interně používá SupervisorJob, což izoluje chyby mezi korutinami. Ale pokud spustíte korutinu s vlastním Job() uvnitř viewModelScope.launch, tato korutina se stane podřízenou SupervisorJob, ale nebude chráněna před zrušením při chybách v jiných korutinách.

Chyba 3: Příliš mnoho korutin v jednom scope

Ačkoli viewModelScope nemá přísný limit, tisíce aktivních korutin mohou zpomalit systém. Pro dlouhé seznamy dat použijte Flow s collectLatest místo vytváření samostatných korutin pro každý prvek.

Chyba 4: Použití GlobalScope místo viewModelScope

Pokud omylem importujete GlobalScope místo viewModelScope, korutina nebude zrušena při čištění ViewModel. To vede k úniku paměti a potenciálnímu pádu. Vždy kontrolujte, že korutiny jsou spouštěny přes viewModelScope, zejména v zděděných fragmentech.

Často kladené otázky

Lze změnit výchozí dispatcher viewModelScope?

Dispatcher viewModelScope nelze přímo změnit — je pevně nastaven jako Dispatchers.Main.immediate. Ale uvnitř korutiny lze přes withContext přepnout na jiný dispatcher. Pro změnu dispatcheru v testech použijte TestDispatcher přes Rule.

Jak předat viewModelScope do Repository?

Nepředávejte scope do Repository — to porušuje architektonické principy. Repository by mělo poskytovat suspend-funkce a ViewModel si samo spravuje korutiny přes viewModelScope. Pokud Repository vyžaduje scope — přehodnoťte architekturu ve prospěch Clean Architecture.

Proč viewModelScope používá SupervisorJob?

SupervisorJob zaručuje, že výjimka v jedné korutině (například chyba načítání jednoho z několika nezávislých požadavků) neruší ostatní korutiny. To odpovídá scénáři ViewModel, kde různé obrazovky načítají nezávislá data.

Je viewModelScope dostupný v Jetpack Compose?

Ano, viewModelScope je dostupný v jakémkoli ViewModel, bez ohledu na typ UI (View System nebo Jetpack Compose). V Composable jsou korutiny také spouštěny přes viewModelScope a pro UI efekty se používají LaunchedEffect a rememberCoroutineScope.

Co se stane s korutinou při volání viewModelScope.cancel()?

Volání viewModelScope.cancel() ruší scope okamžitě — všechny aktivní korutiny končí s CancellationException. Pokud poté zavoláte viewModelScope.launch, nový scope je automaticky vytvořen při příštím přístupu ke getteru.

Shrnutí

  • viewModelScope — CoroutineScope svázaný s životním cyklem ViewModel, automaticky rušený při onCleared()
  • SupervisorJob + Dispatchers.Main — vnitřní konfigurace zajišťující izolaci chyb a bezpečný přístup k UI
  • Rotace obrazovky — ViewModel je zachován, takže korutiny ve viewModelScope pokračují bez restartu
  • Architektura MVVM — viewModelScope je ústředním prvkem pro asynchronní operace ve vrstvě ViewModel
  • lifecycleScope — alternativa pro operace vázané na životní cyklus Activity/Fragment, ne ViewModel
  • StateFlow — preferovaný způsob přenosu dat z korutin viewModelScope do UI přes sealed class
  • GlobalScope je nebezpečný — nahrazení viewModelScope GlobalScope vede k únikům paměti a pádům aplikace

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také