Ringkasan ViewModel Bagian dari Android Jetpack.

Class ViewModel adalah pemegang logika bisnis atau holder status tingkat layar. Class ini mengekspos status ke UI dan mengenkapsulasi logika bisnis terkait. Keuntungan utamanya adalah melakukan cache status dan mempertahankannya melalui perubahan konfigurasi. Artinya, UI Anda tidak perlu mengambil data lagi saat bernavigasi di antara aktivitas, atau mengikuti perubahan konfigurasi, seperti saat memutar layar.

Untuk informasi selengkapnya tentang holder status, lihat panduan holder status. Demikian pula, untuk mengetahui informasi selengkapnya tentang lapisan UI secara umum, lihat panduan lapisan UI.

Manfaat ViewModel

Alternatif untuk ViewModel adalah class biasa yang menyimpan data yang ditampilkan di UI. Hal ini dapat menjadi masalah saat bernavigasi di antara aktivitas atau tujuan Navigation. Tindakan ini akan menghapus data tersebut jika Anda tidak menyimpannya menggunakan mekanisme status instance penyimpanan. ViewModel menyediakan API yang praktis untuk persistensi data yang menyelesaikan masalah ini.

Manfaat utama class ViewModel pada dasarnya ada dua:

  • Memungkinkan Anda mempertahankan status UI.
  • Menyediakan akses ke logika bisnis.

Persistensi

ViewModel memungkinkan persistensi melalui status yang dimiliki ViewModel, dan operasi yang dipicu ViewModel. Cache ini berarti Anda tidak perlu mengambil data lagi melalui perubahan konfigurasi umum, seperti rotasi layar.

Cakupan

Saat membuat instance ViewModel, Anda akan meneruskan objek yang mengimplementasikan antarmuka ViewModelStoreOwner. Ini dapat berupa tujuan Navigation , grafik Navigation, aktivitas, fragmen, atau jenis lain yang mengimplementasikan antarmuka. ViewModel Anda kemudian tercakup ke Siklus proses ViewModelStoreOwner. ViewModel tetap ada dalam memori hingga ViewModelStoreOwner-nya hilang secara permanen.

Rentang class adalah subclass langsung atau tidak langsung dari antarmuka ViewModelStoreOwner. Subclass langsung adalah ComponentActivity, Fragment, dan NavBackStackEntry. Untuk mengetahui daftar lengkap subclass tidak langsung, lihat referensi ViewModelStoreOwner.

Saat fragmen atau aktivitas yang dicakup dalam ViewModel dihancurkan, pekerjaan asinkron akan berlanjut di ViewModel yang dicakup. Ini adalah kunci untuk persistensi.

Untuk informasi selengkapnya, lihat bagian di bawah tentang siklus proses ViewModel.

SavedStateHandle

SavedStateHandle memungkinkan Anda mempertahankan data tidak hanya melalui perubahan konfigurasi, tetapi juga seluruh pembuatan ulang proses. Artinya, hal ini memungkinkan Anda untuk menjaga status UI tetap utuh bahkan ketika pengguna menutup aplikasi dan membukanya di lain waktu.

Akses ke logika bisnis

Meskipun sebagian besar logika bisnis ada di lapisan data, lapisan UI juga dapat berisi logika bisnis. Hal ini dapat terjadi saat menggabungkan data dari beberapa repositori untuk membuat status UI layar, atau saat jenis data tertentu tidak memerlukan lapisan data.

ViewModel adalah tempat yang tepat untuk menangani logika bisnis di lapisan UI. ViewModel juga bertanggung jawab menangani peristiwa dan mendelegasikannya ke lapisan hierarki lain saat logika bisnis perlu diterapkan untuk mengubah data aplikasi.

Jetpack Compose

Saat menggunakan Jetpack Compose, ViewModel adalah cara utama untuk mengekspos status UI layar ke composable Anda. Dalam aplikasi campuran, aktivitas dan fragmen hanya menghosting fungsi composable Anda. Hal ini merupakan pergeseran dari pendekatan sebelumnya yang tidak sederhana dan intuitif untuk membuat UI yang dapat digunakan kembali dengan aktivitas dan fragmen, yang menyebabkannya menjadi lebih aktif sebagai pengontrol UI.

Hal terpenting yang perlu diingat saat menggunakan ViewModel dengan Compose adalah Anda tidak dapat menentukan cakupan ViewModel ke composable. Hal ini disebabkan karena composable bukan ViewModelStoreOwner. Dua instance composable yang sama dalam Komposisi, atau dua composable yang berbeda mengakses jenis ViewModel yang sama dalam ViewModelStoreOwner yang sama akan menerima instance yang sama dari ViewModel, yang sering kali bukanlah perilaku yang diharapkan.

Untuk mendapatkan manfaat ViewModel di Compose, hosting setiap layar di Fragment atau Activity, atau gunakan Compose Navigation dan gunakan ViewModel dalam fungsi composable sedekat mungkin dengan tujuan Navigation. Hal ini karena Anda dapat mencakup ViewModel ke tujuan Navigation, grafik Navigation, Activity, dan Fragment.

Untuk informasi selengkapnya, lihat panduan tentang pengangkatan status untuk Jetpack Compose.

Mengimplementasikan ViewModel

Berikut adalah contoh implementasi ViewModel untuk layar yang memungkinkan pengguna melempar dadu.

Kotlin

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

Anda kemudian dapat mengakses ViewModel dari aktivitas dengan cara sebagai berikut:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

Menggunakan coroutine dengan ViewModel

ViewModel mencakup dukungan untuk coroutine Kotlin. API ini dapat mempertahankan pekerjaan asinkron dengan cara yang sama seperti mempertahankan status UI.

Untuk informasi selengkapnya, lihat Menggunakan coroutine Kotlin dengan Komponen Arsitektur Android.

Siklus proses ViewModel

Siklus proses ViewModel terikat langsung dengan cakupannya. ViewModel tetap berada dalam memori sampai ViewModelStoreOwner yang dicakupnya menghilang. Hal ini dapat terjadi dalam konteks berikut:

  • Dalam kasus aktivitas, setelah aktivitas selesai.
  • Dalam kasus fragmen, saat fragmen terlepas.
  • Dalam kasus entri Navigation, saat entri tersebut dihapus dari data sebelumnya.

Hal ini membuat ViewModel menjadi solusi yang bagus untuk menyimpan data yang bertahan dari perubahan konfigurasi.

Gambar 1 menunjukkan berbagai status siklus proses suatu aktivitas saat aktivitas tersebut mengalami rotasi dan kemudian selesai. Gambar ini juga menunjukkan masa aktif ViewModel di sebelah siklus proses aktivitas yang terkait. Diagram khusus ini menggambarkan status suatu aktivitas. Status dasar yang sama juga diterapkan untuk siklus proses suatu fragmen.

Menggambarkan siklus proses ViewModel saat suatu aktivitas berganti status.

Anda biasanya meminta ViewModel saat sistem pertama kali memanggil metode onCreate() milik objek aktivitas. Sistem mungkin memanggil onCreate() beberapa kali selama masa aktif aktivitas, seperti saat layar perangkat diputar. ViewModel tersedia sejak saat Anda pertama kali meminta ViewModel sampai aktivitas selesai dan dihancurkan.

Menghapus dependensi ViewModel

ViewModel memanggil metode onCleared saat ViewModelStoreOwner menghancurkannya selama siklus prosesnya. Hal ini memungkinkan Anda untuk membersihkan pekerjaan atau dependensi yang mengikuti siklus proses ViewModel.

Contoh berikut menunjukkan alternatif untuk viewModelScope. viewModelScope adalah CoroutineScope bawaan yang otomatis mengikuti siklus proses ViewModel. ViewModel menggunakannya untuk memicu operasi terkait bisnis. Jika Anda ingin menggunakan cakupan kustom, bukan viewModelScope untuk pengujian yang lebih mudah, ViewModel dapat menerima CoroutineScope sebagai dependensi dalam konstruktornya. Saat ViewModelStoreOwner menghapus ViewModel di akhir siklus prosesnya, ViewModel juga akan membatalkan CoroutineScope.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

Dari lifecycle versi 2.5 dan yang lebih baru, Anda dapat meneruskan satu atau beberapa objek Closeable ke konstruktor ViewModel yang otomatis ditutup saat instance ViewModel dihapus.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

Praktik terbaik

Berikut adalah beberapa praktik terbaik utama yang sebaiknya Anda ikuti saat mengimplementasikan ViewModel:

  • Karena cakupan, gunakan ViewModel sebagai detail implementasi pemegang status tingkat layar. Jangan menggunakannya sebagai pemegang status komponen UI yang dapat digunakan kembali seperti grup chip atau formulir. Jika tidak, Anda akan mendapatkan instance ViewModel yang sama dalam penggunaan komponen UI yang sama dengan cara berbeda dalam ViewModelStoreOwner yang sama.
  • ViewModel tidak boleh mengetahui detail implementasi UI. Pastikan nama metode yang ditampilkan oleh ViewModel API dan kolom status UI seumum mungkin. Dengan cara ini, ViewModel Anda dapat mengakomodasi jenis UI apa pun: ponsel, perangkat foldable, tablet, atau bahkan Chromebook.
  • Karena berpotensi dapat aktif lebih lama dari ViewModelStoreOwner, ViewModel tidak boleh menyimpan referensi apa pun yang terkait dengan API terkait siklus proses seperti Context atau Resources untuk mencegah kebocoran memori.
  • Jangan meneruskan ViewModel ke class, fungsi, atau komponen UI lainnya. Karena platform mengelolanya, Anda harus menyimpannya sedekat mungkin dengan Anda. Dekat dengan Activity, fragmen, atau fungsi composable tingkat layar. Hal ini mencegah komponen dengan tingkat lebih rendah mengakses lebih banyak data dan logika daripada yang dibutuhkan.

Informasi lebih lanjut

Ketika data Anda bertambah menjadi lebih kompleks, Anda mungkin memilih untuk memiliki class terpisah yang tugasnya hanya untuk memuat data. Tujuan dari ViewModel adalah untuk mengenkapsulasi data bagi pengontrol UI agar data dapat bertahan saat terjadi perubahan konfigurasi. Untuk informasi tentang cara memuat, mempertahankan, dan mengelola data di seluruh perubahan konfigurasi, lihat Status UI Tersimpan.

Panduan Arsitektur Aplikasi Android menyarankan pembuatan class repositori untuk penanganan fungsi-fungsi ini.

Referensi lainnya

Untuk informasi lebih lanjut tentang class ViewModel, lihat referensi berikut.

Dokumentasi

Contoh