viewModelScope, androidx.lifecycle kütüphanesinden gelen, ViewModel yaşam döngüsüne bağlı ve temizlendiğinde otomatik olarak iptal edilen yerleşik bir CoroutineScope'tur. Google Android Developers, 2025'e göre viewModelScope, MVVM mimarisinde coroutine başlatmak için standart mekanizmadır ve bellek sızıntısı riski olmadan güvenli asenkron işlemler sağlar. ViewModelScope varsayılan olarak Dispatchers.Main kullanır ve içindeki tüm IO işlemleri withContext aracılığıyla yürütülmelidir.
Önemli Noktalar
viewModelScope, ViewModel arayüzünde bir extension özelliğidir ve lifecycle-viewmodel-ktx kütüphanesine (sürüm 2.1.0'dan itibaren) eklenmiştir. ViewModel yaşam döngüsüne bağlı, kullanıma hazır bir CoroutineScope sağlar.
// 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) }
}
Scope, ilk erişimde tembel (lazy) olarak oluşturulur ve setTag aracılığıyla önbelleğe alınır. SupervisorJob kullanır, bu da bir alt coroutine'deki istisnanın diğerlerini iptal etmediği anlamına gelir. Varsayılan dağıtıcı Dispatchers.Main.immediate'dir ve zaten Main üzerindeyse ek dağıtım olmadan ana iş parçacığında kod yürütür.
ViewModel yaşam döngüsünden çıktığında (Activity sonlandırılır veya Fragment kaldırılır), sistem clear() çağırır ve bu da onCleared()'ı tetikler. Bu geri çağrıda viewModelScope, Job'ını iptal eder ve tüm aktif coroutine'leri yinelemeli olarak sonlandırır. Mekanizma Closeable arayüzü aracılığıyla uygulanır; scope'un Job'ı otomatik kapatma için bir kaynak olarak kaydedilir.
viewModelScope'un ViewModel yaşam döngüsüne bağlanma mekanizması, etiketleme ve onCleared geri çağrısına dayanır. Adım adım inceleyelim.
ViewModel viewModelScope.launch { ... } yürüttüğünde, getter JOB_KEY etiketi altında zaten bir scope depolanmış olup olmadığını kontrol eder. Scope yoksa yeni bir CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) örneği oluşturulur. Scope, dahili bir etiket haritası aracılığıyla ViewModel içinde depolanır.
viewModelScope.launch veya viewModelScope.async aracılığıyla başlatılan tüm coroutine'ler, scope'un SupervisorJob'unun alt öğeleri haline gelir. Ana iş parçacığında çalışırlar (withContext aracılığıyla farklı bir dağıtıcı belirtilmedikçe). ViewModel canlı olduğu sürece coroutine'ler aktif, askıda veya tamamlanmış olabilir.
Sistem ViewModel'i yok ettiğinde, ViewModel.clear() çağrılır. clear() içinde aşağıdakiler olur:
Ekran döndürüldüğünde Activity yeniden oluşturulur, ancak ViewModel hayatta kalır (ViewModelStoreOwner sayesinde). Bu, viewModelScope'un aktif kaldığı ve coroutine'lerin kesintisiz olarak yürütülmeye devam ettiği anlamına gelir. Activity yeniden oluşturulduktan sonra, aynı ViewModel (ve aynı scope) yeniden kullanılır — veri yükleme sıfırdan başlamaz.
MVVM (Model-View-ViewModel), Google tarafından Android uygulamaları için önerilen mimaridir. viewModelScope, asenkron işlemlerin yürütücüsü olarak bu mimaride merkezi bir rol oynar.
| Katman | Bileşen | viewModelScope'un rolü |
|---|---|---|
| UI | Activity / Fragment | ViewModel'den StateFlow/LiveData'yı gözlemler |
| ViewModel | ViewModel | viewModelScope aracılığıyla coroutine başlatır, UI durumunu yönetir |
| Repository | Repository | viewModelScope coroutine'lerinden çağrılan suspend işlevleri sağlar |
| Data | DAO / Api | Gerçek istekleri yürütür (Room, Retrofit) |
ViewModel, viewModelScope aracılığıyla coroutine başlatır ve bunların içinde Repository'nin suspend işlevlerini çağırır. Sonuç StateFlow'a dönüştürülür ve UI katmanı tarafından gözlemlenir. Bu tasarım, net sorumluluk ayrımı ve her katmanın bağımsız test edilebilirliğini sağlar.
Coroutine'ler Fragment'ten başlatılsaydı, ekran döndürüldüğünde Fragment'in yok edilmesiyle iptal edilirlerdi. ViewModel döndürmeden sonra hayatta kalır, bu nedenle kendi scope'unda başlatılan coroutine'ler yürütülmeye devam eder. Veri yüklerken lifecycleScope'a kıyasla viewModelScope'un temel avantajı budur.
Kotlin ile bir Android uygulamasında viewModelScope kullanımının üç pratik senaryosunu inceleyelim.
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
}
}
}
init bloğunda, profil yükleme hemen başlar. Coroutine varsayılan olarak ana iş parçacığında çalışır. Depo, suspend işlevi içindeki ağ isteği için withContext(Dispatchers.IO) kullanır, bu nedenle ViewModel iş parçacığı değiştirmeyle ilgilenmek zorunda kalmaz.
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 durumu bir sealed class UiState aracılığıyla tanımlanır. ViewModel her değişiklikte durumu günceller. Fragment, StateFlow'a abone olur ve yalnızca mevcut duruma tepki verir, önceki döndürmelerden gelen eski çağrıları yok sayar.
private var searchJob: Job? = null
fun search(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300)
val results = repo.search(query)
_searchResults.value = results
}
}
Her yeni arama sorgusunda, önceki coroutine iptal edilir. delay(300) debounce uygular — arama yalnızca 300 ms hareketsizlikten sonra yürütülür. Bu, sunucu yükünü azaltır ve güncel olmayan sonuçları önler.
Her iki scope da AndroidX Lifecycle kütüphanesi tarafından sağlanır, ancak farklı yaşam döngülerine bağlıdır. Seçim, görevin türüne bağlıdır.
| Özellik | viewModelScope | lifecycleScope |
|---|---|---|
| Sahip | ViewModel | LifecycleOwner (Activity/Fragment) |
| Döndürmede iptal | Hayır (ViewModel hayatta kalır) | Evet (Activity yeniden oluşturulur) |
| Varsayılan dağıtıcı | Dispatchers.Main.immediate | Dispatchers.Main.immediate |
| Kullanılabilir | ViewModel | Activity, Fragment, Service |
| Tipik kullanım | Veri yükleme, iş mantığı | UI etkileşimleri, animasyonlar |
Google, tüm veri yükleme ve işleme görevleri için viewModelScope kullanılmasını önerir. lifecycleScope, UI yaşam döngüsünün belirli bir anına bağlı işlemler için kullanılmalıdır — örneğin, ekranın ilk görünümünde bir animasyon başlatmak veya ekrandan çıkıldığında durması gereken konum güncellemelerine abone olmak.
İyi belgelenmiş bir Android API'sinde bile geliştiriciler tipik hatalar yapar. En yaygın dört sorunu inceleyelim.
En sinsi hata, ViewModel temizlendikten sonra StateFlow veya LiveData'yı güncellemeye çalışmaktır. viewModelScope onCleared()'da iptal edilmesine rağmen, bir coroutine iptal yürürlüğe girmeden önce kod çalıştırabilir. Kontrol için isActive kullanın veya catch bloğunun tamamlanmasına güvenin.
viewModelScope dahili olarak SupervisorJob kullanır ve bu, coroutine'ler arasındaki hataları yalıtır. Ancak, viewModelScope.launch içinde kendi Job()'u ile bir coroutine başlatırsanız, bu coroutine SupervisorJob'un alt öğesi olur ancak diğer coroutine'lerdeki hatalardan kaynaklanan iptale karşı korunmaz.
viewModelScope'un katı bir sınırı olmamasına rağmen, binlerce aktif coroutine sistemi yavaşlatabilir. Uzun veri listeleri için, her öğe için ayrı coroutine oluşturmak yerine Flow'u collectLatest ile kullanın.
viewModelScope yerine yanlışlıkla GlobalScope içe aktarılırsa, ViewModel temizlendiğinde coroutine iptal edilmez. Bu, bellek sızıntılarına ve potansiyel çökmelere yol açar. Özellikle Fragment alt sınıflarında, coroutine'lerin her zaman viewModelScope aracılığıyla başlatıldığından emin olun.
Sıkça Sorulan Sorular
viewModelScope'un dağıtıcısını doğrudan değiştiremezsiniz — Dispatchers.Main.immediate olarak sabit kodlanmıştır. Ancak, bir coroutine içinde withContext aracılığıyla başka bir dağıtıcıya geçebilirsiniz. Testlerde dağıtıcıyı değiştirmek için bir Rule aracılığıyla TestDispatcher kullanın.
Scope'u Repository'ye aktarmayın — bu mimari ilkeleri ihlal eder. Repository suspend işlevleri sağlamalıdır ve ViewModel'in kendisi viewModelScope aracılığıyla coroutine'leri yönetir. Repository bir scope gerektiriyorsa, mimariyi Clean Architecture lehine yeniden değerlendirin.
SupervisorJob, bir coroutine'deki istisnanın (örneğin, birden çok bağımsız istekten birindeki yükleme hatası) diğer coroutine'leri iptal etmemesini sağlar. Bu, farklı ekranların bağımsız veri yüklediği ViewModel senaryosuyla uyumludur.
Evet, viewModelScope UI türünden (View System veya Jetpack Compose) bağımsız olarak herhangi bir ViewModel'de kullanılabilir. Compose'da da coroutine'ler viewModelScope aracılığıyla başlatılırken, UI efektleri için LaunchedEffect ve rememberCoroutineScope kullanılır.
viewModelScope.cancel() çağırmak scope'u hemen iptal eder — tüm aktif coroutine'ler CancellationException ile sonlanır. Daha sonra viewModelScope.launch çağrılırsa, getter'a bir sonraki erişimde otomatik olarak yeni bir scope oluşturulur.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun