viewModelScope: apa itu, keterkaitan dengan ViewModel dan kerja di Android

Penulis: IT Sectr Diterbitkan: 2026-06-23 Waktu membaca: 9 mnt

viewModelScope — adalah CoroutineScope bawaan dari pustaka androidx.lifecycle, yang terikat pada siklus hidup ViewModel dan secara otomatis dibatalkan saat pembersihannya. Menurut Google Android Developers, 2025, viewModelScope adalah mekanisme standar untuk menjalankan coroutine dalam arsitektur MVVM, menyediakan kerja aman dengan operasi asinkron tanpa risiko kebocoran memori. ViewModelScope secara default menggunakan Dispatchers.Main, dan semua operasi IO di dalamnya harus dilakukan melalui withContext.

Poin utama

  • viewModelScope — CoroutineScope dari lifecycle-viewmodel-ktx, dibatalkan saat onCleared() ViewModel
  • Dispatchers.Main — dispatcher default, sehingga pembaruan UI di dalam coroutine aman
  • onCleared — callback, saat dipanggil viewModelScope secara otomatis membatalkan semua coroutine aktif
  • clear() vs onCleared() — clear() dipanggil oleh framework sebelum onCleared, yang menjamin pembatalan scope
  • launch — cara utama menjalankan coroutine di viewModelScope untuk operasi fire-and-forget

Apa itu viewModelScope di Android?

viewModelScope — adalah properti ekstensi (extension property) pada antarmuka ViewModel, ditambahkan di pustaka lifecycle-viewmodel-ktx (sejak versi 2.1.0). Ini menyediakan CoroutineScope siap pakai yang terikat pada siklus hidup ViewModel.

kotlin
// Struktur internal (disederhanakan)
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 dibuat secara malas (lazy) saat akses pertama dan di-cache melalui setTag. SupervisorJob digunakan, yang berarti pengecualian di satu coroutine anak tidak membatalkan yang lain. Dispatcher default adalah Dispatchers.Main.immediate, yang mengeksekusi kode di thread utama tanpa dispatch tambahan jika panggilan sudah di Main.

Bagaimana viewModelScope menerima notifikasi pembersihan

Ketika ViewModel meninggalkan siklus hidup (Activity selesai atau Fragment dihapus), sistem memanggil clear(), yang memicu onCleared(). Dalam callback ini, viewModelScope membatalkan Job-nya, yang secara rekursif mengakhiri semua coroutine aktif. Mekanismenya diimplementasikan melalui antarmuka Closeable, di mana Job scope didaftarkan sebagai sumber daya untuk penutupan otomatis.

Cara kerja viewModelScope: keterkaitan dengan siklus hidup ViewModel

Mekanisme pengikatan viewModelScope ke siklus hidup ViewModel didasarkan pada penandaan dan callback onCleared. Mari kita lihat langkah demi langkah cara kerjanya.

Langkah 1: Pembuatan scope saat akses pertama

Ketika ViewModel menjalankan viewModelScope.launch { ... }, getter memeriksa apakah ada scope yang disimpan di bawah tag JOB_KEY. Jika scope belum dibuat — instance baru CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) dibuat. Scope disimpan di dalam ViewModel melalui peta tag internal.

Langkah 2: Siklus hidup coroutine

Semua coroutine yang dijalankan melalui viewModelScope.launch atau viewModelScope.async menjadi anak dari SupervisorJob scope. Mereka berjalan di thread utama (jika tidak ada dispatcher lain yang ditentukan melalui withContext). Selama ViewModel hidup — coroutine bisa aktif, ditangguhkan, atau selesai.

Langkah 3: Pembatalan saat onCleared

Ketika sistem menghancurkan ViewModel, ViewModel.clear() dipanggil. Di dalam clear() terjadi hal berikut:

  • Pemanggilan onCleared() untuk logika pengguna
  • Penutupan semua sumber daya Closeable yang didaftarkan melalui addCloseable
  • Job viewModelScope beralih ke status Cancelled
  • Semua coroutine anak dibatalkan secara rekursif
  • Pembebasan referensi ke scope untuk garbage collector

Ketahanan terhadap rotasi layar

Saat rotasi layar, Activity dibuat ulang, tetapi ViewModel dipertahankan (berkat ViewModelStoreOwner). Ini berarti viewModelScope tetap aktif, dan coroutine terus berjalan tanpa gangguan. Setelah pembuatan ulang Activity, ViewModel yang sama (dan scope yang sama) digunakan kembali — pemuatan data tidak dimulai dari awal.

viewModelScope dalam arsitektur MVVM

MVVM (Model-View-ViewModel) — adalah arsitektur yang direkomendasikan Google untuk aplikasi Android. viewModelScope menempati posisi sentral di dalamnya sebagai pelaksana operasi asinkron.

Peran viewModelScope dalam lapisan arsitektur

LapisanKomponenPeran viewModelScope
UIActivity / FragmentMengamati StateFlow/LiveData dari ViewModel
ViewModelViewModelMenjalankan coroutine melalui viewModelScope, mengelola status UI
RepositoryRepositoryMenerima fungsi suspend yang dipanggil dari coroutine viewModelScope
DataDAO / ApiMelakukan permintaan aktual (Room, Retrofit)

ViewModel melalui viewModelScope menjalankan coroutine, di dalamnya memanggil fungsi suspend Repository. Hasilnya diubah menjadi StateFlow, yang diamati oleh lapisan UI. Skema ini memastikan pemisahan tanggung jawab yang jelas dan kemampuan pengujian setiap lapisan secara independen.

Mengapa viewModelScope di ViewModel, bukan di Fragment

Jika coroutine dijalankan dari Fragment, saat rotasi layar mereka akan dibatalkan bersamaan dengan penghancuran Fragment. ViewModel bertahan dari rotasi, sehingga coroutine yang dijalankan dalam scope-nya terus berlangsung. Ini adalah keunggulan utama viewModelScope dibandingkan lifecycleScope saat memuat data.

Contoh penggunaan viewModelScope

Mari kita lihat tiga skenario praktis penggunaan viewModelScope dalam aplikasi Android dengan Kotlin.

Contoh 1: Memuat data saat pembuatan 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
        }
    }
}

Di blok init, pemuatan profil segera dimulai. Coroutine berjalan di thread utama (secara default). Repositori menggunakan withContext(Dispatchers.IO) untuk permintaan jaringan di dalam fungsi suspend-nya, sehingga ViewModel tidak perlu memikirkan perpindahan thread.

Contoh 2: Penanganan kesalahan melalui 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")
        }
    }
}

Status UI dijelaskan melalui sealed class UiState. ViewModel memperbarui state pada setiap perubahan. Fragment berlangganan ke StateFlow dan hanya bereaksi terhadap status saat ini, mengabaikan panggilan usang pada rotasi berulang.

Contoh 3: Pembatalan coroutine sebelumnya saat permintaan baru

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

Pada setiap permintaan pencarian baru, coroutine sebelumnya dibatalkan. delay(300) mengimplementasikan debounce — pencarian dilakukan hanya setelah jeda 300 ms dalam input. Ini mengurangi beban server dan mencegah hasil usang.

viewModelScope vs lifecycleScope: kapan memilih apa

Kedua scope disediakan oleh pustaka AndroidX Lifecycle, tetapi terikat pada siklus hidup yang berbeda. Pilihan di antara mereka tergantung pada jenis tugas.

Perbandingan scope

KarakteristikviewModelScopelifecycleScope
PemilikViewModelLifecycleOwner (Activity/Fragment)
Pembatalan saat rotasiTidak (ViewModel dipertahankan)Ya (Activity dibuat ulang)
Dispatcher defaultDispatchers.Main.immediateDispatchers.Main.immediate
Tersedia diViewModelActivity, Fragment, Service
Skenario tipikalMemuat data, logika bisnisInteraksi UI, animasi, snackbar

Rekomendasi Google

Google merekomendasikan penggunaan viewModelScope untuk semua tugas yang terkait dengan pemuatan dan pemrosesan data. lifecycleScope harus diterapkan untuk operasi yang terikat pada momen tertentu dari kehidupan UI — misalnya, memulai animasi saat pertama kali layar muncul atau berlangganan pembaruan Location yang harus berhenti saat meninggalkan layar.

Kesalahan umum saat bekerja dengan viewModelScope

Bahkan di API Android yang terdokumentasi dengan baik, pengembang membuat kesalahan karakteristik. Mari kita lihat empat masalah paling umum.

Kesalahan 1: Memperbarui UI setelah pembatalan scope

Kesalahan paling berbahaya — mencoba memperbarui StateFlow atau LiveData setelah ViewModel dibersihkan. Meskipun viewModelScope dibatalkan saat onCleared(), coroutine dapat mengeksekusi kode hingga saat pembatalan yang sebenarnya. Gunakan isActive untuk pemeriksaan atau andalkan penyelesaian blok catch.

Kesalahan 2: Menjalankan coroutine tanpa mempertimbangkan SupervisorJob

viewModelScope secara internal menggunakan SupervisorJob, yang mengisolasi kesalahan antar coroutine. Tetapi jika Anda menjalankan coroutine dengan Job() sendiri di dalam viewModelScope.launch, coroutine tersebut menjadi anak dari SupervisorJob, tetapi tidak akan dilindungi dari pembatalan saat terjadi kesalahan di coroutine lain.

Kesalahan 3: Terlalu banyak coroutine dalam satu scope

Meskipun viewModelScope tidak memiliki batasan ketat, ribuan coroutine aktif dapat memperlambat sistem. Untuk daftar data yang panjang, gunakan Flow dengan collectLatest daripada membuat coroutine terpisah untuk setiap elemen.

Kesalahan 4: Menggunakan GlobalScope alih-alih viewModelScope

Jika Anda secara tidak sengaja mengimpor GlobalScope alih-alih viewModelScope, coroutine tidak akan dibatalkan saat pembersihan ViewModel. Ini menyebabkan kebocoran memori dan potensi crash. Selalu periksa bahwa coroutine dijalankan melalui viewModelScope, terutama di fragment turunan.

Pertanyaan yang Sering Diajukan

Bisakah dispatcher default viewModelScope diubah?

Dispatcher viewModelScope tidak dapat diubah secara langsung — ia ditetapkan secara tetap sebagai Dispatchers.Main.immediate. Namun di dalam coroutine dapat beralih ke dispatcher lain melalui withContext. Untuk mengubah dispatcher dalam pengujian, gunakan TestDispatcher melalui Rule.

Bagaimana cara meneruskan viewModelScope ke Repository?

Jangan meneruskan scope ke Repository — ini melanggar prinsip arsitektur. Repository harus menyediakan fungsi suspend, dan ViewModel sendiri mengelola coroutine melalui viewModelScope. Jika Repository memerlukan scope — tinjau ulang arsitektur demi Clean Architecture.

Mengapa viewModelScope menggunakan SupervisorJob?

SupervisorJob menjamin bahwa pengecualian di satu coroutine (misalnya, kesalahan pemuatan salah satu dari beberapa permintaan independen) tidak membatalkan coroutine lainnya. Ini sesuai dengan skenario ViewModel, di mana layar yang berbeda memuat data independen.

Apakah viewModelScope tersedia di Jetpack Compose?

Ya, viewModelScope tersedia di ViewModel mana pun, terlepas dari jenis UI (View System atau Jetpack Compose). Di Compose, coroutine juga dijalankan melalui viewModelScope, dan untuk efek UI digunakan LaunchedEffect dan rememberCoroutineScope.

Apa yang terjadi pada coroutine saat viewModelScope.cancel() dipanggil?

Pemanggilan viewModelScope.cancel() membatalkan scope segera — semua coroutine aktif berakhir dengan CancellationException. Jika setelah itu Anda memanggil viewModelScope.launch, scope baru dibuat secara otomatis pada akses berikutnya ke getter.

Kesimpulan

  • viewModelScope — CoroutineScope terikat pada siklus hidup ViewModel, dibatalkan secara otomatis saat onCleared()
  • SupervisorJob + Dispatchers.Main — konfigurasi internal yang memastikan isolasi kesalahan dan akses aman ke UI
  • Rotasi layar — ViewModel dipertahankan, sehingga coroutine di viewModelScope terus berjalan tanpa restart
  • Arsitektur MVVM — viewModelScope adalah elemen pusat untuk operasi asinkron di lapisan ViewModel
  • lifecycleScope — alternatif untuk operasi yang terikat pada siklus hidup Activity/Fragment, bukan ViewModel
  • StateFlow — cara yang lebih disukai untuk mentransfer data dari coroutine viewModelScope ke UI melalui sealed class
  • GlobalScope berbahaya — mengganti viewModelScope dengan GlobalScope menyebabkan kebocoran memori dan crash aplikasi

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga