Android MVVM — Part 3 ViewModel Si Pintar
Membangun Aplikasi MVVM Dengan Kotlin, Coroutines dan Architecture Component
ViewModel
Setelah membangun Repository, sekarang waktunya membangun ViewModel. Sebelumnya udah dibahas kalau ViewModel berkomunikasi dengan Repository untuk mendapatkan data atau perubahan data dan melakukan update terhadap data yang dimiliki. Data yang didapat dari Repository itu bakal disimpan dalam bentuk LiveData dan nantinya bakal diobservasi oleh View.
Yuk Bikin!
Ada 3 tampilan dari aplikasi yang akan kita buat. Biasanya 1 tampilan akan berubah menjadi 1 activity. Setiap 1 activity akan memiliki 1 ViewModel. Tapi tidak menutup kemungkinan kalau ada aplikasi yang menggunakan Single Activity Architecture di mana setiap tampilannya ditampilkan dengan Fragment. Di kasus tersebut, kita bisa saja menggunakan 1 ViewModel untuk 1 Fragment atau bisa juga menggunakan 1 ViewModel untuk semua Fragment.
Sekarang kita akan menggunakan 1 ViewModel untuk 1 tampilan. Karena kita memiliki 3 tampilan, maka kita akan membangun 3 ViewModel serta unit testnya.
ViewModel yang pertama akan kita bangun adalah yang akan menyimpan data dari set pokemon yang tersedia.
Dilihat dari tampilannya, kita tau kalau tampilan ini akan membutuhkan data dari Repository yang sudah kita bangun sebelumnya. ViewModel kita bakal butuh PokemonSetRepository.
Dari tampilan ini juga, kita tau kalau ViewModel kita hanya akan melakukan komunikasi dengan PokemonSetRepository untuk mendapatkan list set pokemon yang tersedia.
Ada sesuatu yang terlihat asing? Ya MutableLiveData. Kita sudah sering bahas kalo ViewModel bakal simpan datanya dalam bentuk LiveData. Kenapa tiba-tiba ada MutableLiveData?
Perbedaan utama dari LiveData dan MutableLiveData adalah kita bisa ubah nilai dari MutableLiveData tapi kita gak bisa ubah nilai dari LiveData. Makannya kita pake MutableLiveData, karena kita butuh ubah nilainya.
Terus kenapa kita bikin property accessor viewState dengan LiveData tapi yang kita kasih malah MutableLiveData. Emang bisa? Ini merupakan tahap enkapsulasi. Tujuan enkapsulasi ini biar ViewModel kita itu satu-satunya yang bisa ubah datanya. Karena data tersebut nantinya bakal diobservasi sama View, kita harus pastiin kalau data yang diobservasi ga bisa diubah sama View.
Berikutnya buat jawab kenapa kita bisa kasih MutableLiveData padahal property accessor yang kita definisiin adalah LiveData. Kalau kita lihat dari gambar ini.
Kita bisa tau kalau LiveData mewarisi semua yang dia punya ke MutableLiveData. Ini merupakan konsep inheritance dalam pemrograman berbasis objek. Konsep ini memungkinkan kita buat bilang kalau semua MutableLiveData adalah LiveData tapi ga semua LiveData itu MutableLiveData. Makannya kita bisa tuker LiveData dengan MutableLiveData, tapi kita ga akan bisa tuker MutableLiveData dengan LiveData.
Hal berikutnya yang asing adalah MainViewState. MainViewState hanya sebuah class POJO yang menyimpan status dari tampilan tersebut.
Ini adalah status sederhana yang biasanya ada. Status yang disimpan tidak harus selalu seperti ini. Bisa jadi status error yang disimpan dibuat menjadi lebih spesifik misal dengan menyimpan variabel pesan error untuk sebuah EditText.
Hal lain yang terasa asing adalah viewModelScope.launch. Sebelumnya, kita banyak sekali membuat suspend function. Sederhananya dengan menggunakan suspend function kita gak perlu menghalangi thread yang sedang berjalan. Tapi kita gak bisa manggil suspend function kayak fungsi biasa. Suspend function harus dipanggil dalam konteks coroutine. Dan viewModelScope merupakan konteks coroutine yang bisa kita manfaatkan untuk memanggil suspend function di ViewModel. Karena viewModelScope akan otomatis dibatalkan saat ViewModel dibersihkan. Hal ini mengurangi kemungkinan terjadinya memory leak.
Setelah membangun ViewModel ini, yang kita lakukan adalah menguji apakah ViewModel yang dibangun sudah sesuai dengan kebutuhan kita atau belum.
Test yang pertama untuk mengecek kalau status pertama kali dibuat akan melakukan loading. Kedua adalah untuk mengecek kalau data akan sesuai dengan data yang didapatkan dari Repository. Terakhir adalah untuk mengecek kalo status akan error jika Repository melempar error.
ViewModel yang kita buat sesuai dengan keinginan kita yay! Selanjutnya kita akan buat ViewModel untuk tampilan list kartu pokemon.
ViewModel ini akan bergantung dengan PokemonCardRepository yang kita buat sebelumnya.
ViewModel ini tidak berbeda jauh dengan ViewModel sebelumnya.
Untuk status yang disimpan pun tidak berbeda dengan sebelumnya. Kita akan uji ViewModel ini dengan test yang sama juga seperti sebelumnya.
Yay, berhasil! ViewModel yang bakal kita buat terakhir adalah buat nampilin tampilan ini.
Berbeda dengan sebelumnya, ViewModel yang ini gak butuh Repository apa-apa.
Selanjutnya untuk menguji apakah ViewModel ini sudah sesuai dengan yang kita inginkan.
Pada test kali ini, kita hanya menguji kalau data kartu pokemon akan null saat ViewModel pertama kali dibuat dan tidak akan null setelah fungsi setData dipanggil.
Finally!
Kita sudah mengimplementasikan 3 ViewModel serta unit testnya. Seperti pada artikel sebelumnya, bagian ViewModel ini, kita tidak menggunakan framework Android kecuali library architecture component. Ini hal yang bagus. Ini memudahkan kita untuk membuat unit test dari komponen tersebut. Kalau emang terpaksa harus menggunakan komponen framework Android, lakukan Dependency Inversion Principle seperti yang kita lakukan pada PokemonSetDataStore dan PokemonCardDataStore di artikel sebelumnya.
Parts
Artikel ini akan dibagi ke dalam beberapa bagian. Karena saya rasa memahami tugas dari setiap komponen jauh lebih penting daripada hanya sekedar tau bagaimana MVVM diimplementasikan. Bagian-bagian tersebut adalah sebagai berikut:
- Part 1 Pengenalan MVVM
- Part 2 Repository Sumber Data Kita
- Part 3 ViewModel Si Pintar
- Part 4 View Yang Sangat Reaktif
- Part 5 RoomDataStore