viewModelScope — ay isang built-in na CoroutineScope mula sa androidx.lifecycle library, na nakatali sa lifecycle ng ViewModel at awtomatikong kinakansela kapag ito ay nilinis. Ayon sa Google Android Developers, 2025, ang viewModelScope ay ang karaniwang mekanismo para sa pagpapatakbo ng mga coroutine sa MVVM architecture, na nagbibigay ng ligtas na trabaho sa mga asynchronous na operasyon nang walang panganib ng memory leak. Ang ViewModelScope ay gumagamit ng Dispatchers.Main bilang default, at lahat ng IO operations sa loob nito ay dapat isagawa sa pamamagitan ng withContext.
Mga pangunahing punto
viewModelScope — ay isang extension property sa ViewModel interface, idinagdag sa lifecycle-viewmodel-ktx library (mula bersyon 2.1.0). Nagbibigay ito ng handa nang CoroutineScope na nakatali sa lifecycle ng ViewModel.
// Panloob na istraktura (pinasimple)
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) }
}
Ang scope ay ginagawa nang tamad (lazy) sa unang pag-access at naka-cache sa pamamagitan ng setTag. Ginagamit ang SupervisorJob, na nangangahulugang ang exception sa isang child coroutine ay hindi kinakansela ang iba. Ang default na dispatcher ay Dispatchers.Main.immediate, na nagpapatupad ng code sa main thread nang walang karagdagang dispatch kung ang tawag ay nasa Main na.
Kapag ang ViewModel ay umalis sa lifecycle (Activity natapos o Fragment naalis), ang sistema ay tumatawag ng clear(), na nagti-trigger ng onCleared(). Sa callback na ito, kinakansela ng viewModelScope ang Job nito, na rekursibong nagtatapos sa lahat ng aktibong coroutine. Ang mekanismo ay ipinatupad sa pamamagitan ng Closeable interface, kung saan ang Job ng scope ay nirerehistro bilang isang resource para sa awtomatikong pagsasara.
Ang mekanismo ng pagtali ng viewModelScope sa lifecycle ng ViewModel ay batay sa pag-tag at callback na onCleared. Suriin natin hakbang-hakbang kung paano ito gumagana.
Kapag ang ViewModel ay nag-execute ng viewModelScope.launch { ... }, sinusuri ng getter kung may naka-save na scope sa ilalim ng tag na JOB_KEY. Kung ang scope ay hindi pa nagagawa — isang bagong instance ng CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) ang gagawin. Ang scope ay iniimbak sa loob ng ViewModel sa pamamagitan ng internal na tag map.
Lahat ng coroutine na pinatakbo sa pamamagitan ng viewModelScope.launch o viewModelScope.async ay nagiging anak ng SupervisorJob ng scope. Gumagana ang mga ito sa main thread (kung walang ibang dispatcher na tinukoy sa pamamagitan ng withContext). Habang buhay ang ViewModel — ang mga coroutine ay maaaring aktibo, naka-suspend, o tapos na.
Kapag sinira ng sistema ang ViewModel, ViewModel.clear() ang tinatawag. Sa loob ng clear() ay nangyayari ang sumusunod:
Sa pag-ikot ng screen, ang Activity ay muling ginagawa, ngunit ang ViewModel ay pinapanatili (salamat sa ViewModelStoreOwner). Nangangahulugan ito na ang viewModelScope ay nananatiling aktibo, at ang mga coroutine ay patuloy na tumatakbo nang walang pagkaantala. Pagkatapos ng muling paggawa ng Activity, ang parehong ViewModel (at ang parehong scope) ay ginagamit muli — ang pag-load ng data ay hindi nagsisimula muli.
MVVM (Model-View-ViewModel) — ay ang inirerekomendang arkitektura ng Google para sa Android applications. Ang viewModelScope ay sumasakop sa sentral na lugar dito bilang tagapagpatupad ng mga asynchronous na operasyon.
| Layer | Component | Papel ng viewModelScope |
|---|---|---|
| UI | Activity / Fragment | Nagmamasid ng StateFlow/LiveData mula sa ViewModel |
| ViewModel | ViewModel | Nagpapatakbo ng coroutine sa pamamagitan ng viewModelScope, namamahala ng UI state |
| Repository | Repository | Tumatanggap ng suspend functions na tinatawag mula sa viewModelScope coroutine |
| Data | DAO / Api | Nagsasagawa ng aktwal na mga request (Room, Retrofit) |
Ang ViewModel sa pamamagitan ng viewModelScope ay nagpapatakbo ng coroutine, sa loob nito ay tumatawag ng suspend functions ng Repository. Ang resulta ay ginagawang StateFlow, na minamasdan ng UI layer. Ang scheme na ito ay nagsisiguro ng malinaw na paghihiwalay ng mga responsibilidad at testability ng bawat layer nang independyente.
Kung ang mga coroutine ay pinatakbo mula sa Fragment, sa pag-ikot ng screen sila ay kakanselahin kasama ng pagkawasak ng Fragment. Ang ViewModel ay nakaliligtas sa rotation, kaya ang mga coroutine na pinatakbo sa scope nito ay patuloy na tumatakbo. Ito ang pangunahing bentahe ng viewModelScope kumpara sa lifecycleScope sa pag-load ng data.
Tingnan natin ang tatlong praktikal na senaryo ng paggamit ng viewModelScope sa Android application sa 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
}
}
}
Sa init block, agad na sinisimulan ang pag-load ng profile. Ang coroutine ay tumatakbo sa main thread (bilang default). Ang repository ay gumagamit ng withContext(Dispatchers.IO) para sa network request sa loob ng suspend function nito, kaya ang ViewModel ay hindi nag-aalala tungkol sa paglipat ng thread.
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")
}
}
}
Ang UI state ay inilalarawan sa pamamagitan ng sealed class UiState. Ina-update ng ViewModel ang state sa bawat pagbabago. Ang Fragment ay naka-subscribe sa StateFlow at tumutugon lamang sa kasalukuyang estado, hindi pinapansin ang mga lumang tawag sa paulit-ulit na rotation.
private var searchJob: Job? = null
fun search(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300)
val results = repo.search(query)
_searchResults.value = results
}
}
Sa bawat bagong search request, ang nakaraang coroutine ay kinakansela. Ang delay(300) ay nagpapatupad ng debounce — ang paghahanap ay isinasagawa lamang pagkatapos ng 300 ms na pause sa input. Binabawasan nito ang server load at pinipigilan ang mga lumang resulta.
Ang parehong scope ay ibinibigay ng AndroidX Lifecycle library, ngunit nakatali sa magkaibang lifecycle. Ang pagpili sa pagitan nila ay depende sa uri ng gawain.
| Katangian | viewModelScope | lifecycleScope |
|---|---|---|
| May-ari | ViewModel | LifecycleOwner (Activity/Fragment) |
| Pagkansela sa rotation | Hindi (ViewModel pinapanatili) | Oo (Activity muling ginagawa) |
| Default na dispatcher | Dispatchers.Main.immediate | Dispatchers.Main.immediate |
| Available sa | ViewModel | Activity, Fragment, Service |
| Karaniwang senaryo | Pag-load ng data, business logic | UI interaction, animation, snackbar |
Inirerekomenda ng Google ang paggamit ng viewModelScope para sa lahat ng gawain na may kaugnayan sa pag-load at pagproseso ng data. Ang lifecycleScope ay dapat ilapat para sa mga operasyon na nakatali sa isang tiyak na sandali ng UI life — halimbawa, pagsisimula ng animation sa unang paglitaw ng screen o pag-subscribe sa Location updates na dapat huminto sa pag-alis ng screen.
Kahit sa mahusay na dokumentadong API ng Android, ang mga developer ay gumagawa ng mga katangiang pagkakamali. Tingnan natin ang apat na pinakakaraniwang problema.
Ang pinaka-mapanganib na pagkakamali — pagsubok na i-update ang StateFlow o LiveData pagkatapos malinis ang ViewModel. Kahit na ang viewModelScope ay kinakansela sa onCleared(), ang coroutine ay maaaring mag-execute ng code hanggang sa aktwal na pagkansela. Gamitin ang isActive para sa pagsusuri o umasa sa pagkumpleto ng catch block.
Ang viewModelScope ay panloob na gumagamit ng SupervisorJob, na naghihiwalay ng mga error sa pagitan ng coroutine. Ngunit kung magpapatakbo ka ng coroutine na may sariling Job() sa loob ng viewModelScope.launch, ang coroutine na iyon ay magiging anak ng SupervisorJob, ngunit hindi mapoprotektahan mula sa pagkansela sa mga error sa ibang coroutine.
Kahit na ang viewModelScope ay walang mahigpit na limitasyon, libu-libong aktibong coroutine ang maaaring makapagpabagal ng system. Para sa mahabang listahan ng data, gamitin ang Flow na may collectLatest sa halip na gumawa ng hiwalay na coroutine para sa bawat elemento.
Kung hindi sinasadyang mag-import ng GlobalScope sa halip na viewModelScope, ang coroutine ay hindi makakansela sa paglilinis ng ViewModel. Ito ay humahantong sa memory leak at potensyal na crash. Palaging suriin na ang mga coroutine ay pinapatakbo sa pamamagitan ng viewModelScope, lalo na sa mga inherited fragment.
Mga Madalas Itanong
Hindi maaaring direktang baguhin ang dispatcher ng viewModelScope — ito ay nakatakda bilang Dispatchers.Main.immediate. Ngunit sa loob ng coroutine ay maaaring lumipat sa ibang dispatcher sa pamamagitan ng withContext. Para sa pagpapalit ng dispatcher sa mga test, gamitin ang TestDispatcher sa pamamagitan ng Rule.
Huwag ipasa ang scope sa Repository — ito ay lumalabag sa mga prinsipyo ng arkitektura. Ang Repository ay dapat magbigay ng suspend functions, at ang ViewModel mismo ang namamahala ng coroutine sa pamamagitan ng viewModelScope. Kung ang Repository ay nangangailangan ng scope — suriin muli ang arkitektura pabor sa Clean Architecture.
Ginagarantiya ng SupervisorJob na ang exception sa isang coroutine (halimbawa, error sa pag-load ng isa sa maraming independyenteng request) ay hindi kinakansela ang iba pang coroutine. Ito ay tumutugma sa senaryo ng ViewModel, kung saan ang iba't ibang screen ay naglo-load ng independyenteng data.
Oo, available ang viewModelScope sa anumang ViewModel, anuman ang uri ng UI (View System o Jetpack Compose). Sa Compose, ang mga coroutine ay pinapatakbo din sa pamamagitan ng viewModelScope, at para sa UI effects ay ginagamit ang LaunchedEffect at rememberCoroutineScope.
Ang pagtawag ng viewModelScope.cancel() ay kinakansela ang scope agad — lahat ng aktibong coroutine ay nagtatapos sa CancellationException. Kung pagkatapos nito ay tumawag ka ng viewModelScope.launch, isang bagong scope ang awtomatikong gagawin sa susunod na pag-access sa getter.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din