Melanjutkan pembahasan Design Pattern untuk pembuatan perangkat lunak, artikel ini akan membahas tentang apa saja tips penerapan design pattern MVVM untuk pembuatan aplikasi Android. MVVM merupakan salah satu design pattern yang dapat kita gunakan dalam pengembangan aplikasi Android.
Sebelumnya, kita sudah sudah banyak belajar mulai dari apa itu design pattern, manfaatnya seperti apa, dan berbagai macam design pattern apa yang bisa kita terapkan. Mungkin kamu bingung dengan Design Pattern MVVM yang sudah disebutkan di atas karena tidak dibahas di artikel Design Pattern sebelumnya. Tidak perlu khawatir! Kita akan membahasnya setelah ini. Jadi, baca sampai akhir ya!
Apa itu Design Pattern MVVM?
MVVM adalah salah satu arsitektur pembuatan aplikasi berbasis GUI yang berfokus pada pemisahan antara kode untuk logika bisnis dan tampilan aplikasi. Dalam penerapannya, MVVM terbagi atas beberapa layer, yaitu Model, View, dan ViewModel. Simak pembahasan ketiga layer tersebut di bawah ini.
💻 Mulai Belajar Pemrograman
Belajar pemrograman di Dicoding Academy dan mulai perjalanan Anda sebagai developer profesional.
Daftar Sekarang- Model
Layer ini adalah model atau entitas yang merepresentasikan data yang akan digunakan pada logika bisnis. Umumnya kelas-kelas yang ada di dalamnya berupa POJO atau Plain Old Java Object dan Data Classes jika kita menggunakan Kotlin.
- View
Tidak seperti layer sebelumnya, layer ini berisi UI dari aplikasi untuk mengatur bagaimana informasi akan ditampilkan. Layer ini akan berisi kelas-kelas, seperti Activity dan Fragment.
- ViewModel
Layer terakhir adalah ViewModel yang bertugas untuk berinteraksi dengan model di mana data yang ada akan diteruskan ke layer view.
Tips dalam penerapan MVVM
Design pattern MVVM dibuat dengan memperhatikan banyak hal. Google sebagai kiblat untuk menerapkan design pattern tersebut sudah banyak membuat API dan dokumentasi khusus untuk membantu developer ketika ingin menerapkannya.
Namun, pada praktik di lapangan, kita masih dapat melihat beberapa kesalahan yang akan membuat penerapannya tidak maksimal bahkan berpotensi menyebabkan alur aplikasi yang dikembangkan menjadi terganggu bahkan rusak.
Nah, apa saja kesalahan umum yang ditemukan dan langkah apa saja yang bisa kita ambil untuk mengatasinya? Mari kita bahas satu per satu.
Pemisahan event dan observer data
Saat membuat kelas ViewModel, kita pasti familier dengan yang namanya LiveData dan biasanya kita akan menuliskannya seperti berikut.
1 2 3 |
class UserViewModel(repository: UserRepository): ViewModel() { val users: LiveData<Users> = repository.getAllUsers() } |
Lalu, mengobservasinya dari layer View seperti berikut.
1 2 3 4 5 6 7 8 9 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) .... userViewModel.users.observe(this){ renderUsers(it) } } } |
Saat kode di atas dijalankan, tidak akan ada masalah berarti dan data akan berhasil ditampilkan dengan baik. Permasalahan akan muncul saat terjadi perubahan behaviour pada aplikasi seperti orientation changed di mana data yang sudah tampil akan hilang karena request data akan dilakukan kembali dan menunggu sampai request data selesai dijalankan.
Untuk mengatasi masalah di atas, cukup pisahkan fungsi untuk melakukan request data dan obyek yang akan di-observe seperti berikut.
1 2 3 4 5 6 7 8 9 10 |
class UserViewModel(repository: UserRepository): ViewModel() { val _users = MutableLiveData<Users>() val users : LiveData<Users> get() = _users fun getAllUsers() { val tempUsers = repository.getAllUsers() _users.postValue(tempUsers) } } |
Kemudian, ubahlah kode yang ada di kelas Activity menjadi seperti berikut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... if (savedInstanceState === null){ userViewModel.getAllUser() } userViewModel.users.observe(this){ renderUsers(it) } } } |
Dengan kode di atas, ketika terjadi orientation changed, data yang sudah ditampilkan tidak akan hilang lagi. Menarik, bukan?
Menghindari pemanggilan observe dari event listener
Tips berikutnya adalah bagaimana kita sebaiknya menempatkan pemanggilan fungsi observe. Pada umumnya, pemanggilan fungsi observer adalah seperti berikut.
1 2 3 4 5 6 7 8 9 10 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) userViewModel.users.observe(this){ renderUsers(it) } } } |
Kode di atas normal dan tidak ada masalah. Namun, bagaimana jika data hanya akan ditampilkan saat suatu event dibangkitkan? Contoh, saat sebuah tombol diklik. Beberapa kasus pemanggilan fungsi observe akan terlihat seperti di bawah ini.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... btn.setOnClickListener { userViewModel.users.observe(this){ renderUsers(it) } } } } |
Apakah kode di atas bisa dijalankan? Tentu bisa. Namun, akan ada masalah yang muncul, yaitu instance dari Observe akan dibuat berulang saat tombol diklik. Lantas, langkah apa yang bisa kita lakukan? Mudah, cukup ikuti tips yang pertama, yaitu memisahkan antara event dan data observe seperti berikut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btn.setOnClickListener { userViewModel.getAllUser() } userViewModel.users.observe(this){ renderUsers(it) } } } |
Dengan kode seperti di atas, kita dapat menghindari instance Observe yang dibuat berulang.
Memanfaatkan state untuk data
Tips yang terakhir adalah memanfaatkan state data. Eits, tapi sebelum kita memanfaatkan state data, kasus yang ingin diselesaikan seperti apa nih? Mari kita lihat contoh kelas ViewModel di bawah ini.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class UserViewModel(repository: UserRepository): ViewModel() { val _users = MutableLiveData<Users>() val users : LiveData<Users> get() = _users val _loading = MutableLiveData<Boolean>() val loading : LiveData<Boolean> get() = _loading val _error = MutableLiveData<String>() val error : LiveData<String> get() = _error fun getAllUsers() { _loading.postValue(true) try { val tempUsers = repository.getAllUsers() _users.postValue(tempUsers) } catch (e: Exception) { _error.postValue(e.message) } finally { _loading.postValue(false) } } } |
Lalu, ketika kelas ViewModel di atas digunakan di dalam kelas Activity, akan terlihat seperti di bawah ini.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btn.setOnClickListener { userViewModel.getAllUser() } userViewModel.users.observe(this){ renderUsers(it) } userViewModel.loading.observe(this){ renderLoading(it) } userViewModel.error.observe(this){ renderError(it) } } } |
Terlihat tidak ada masalah dengan kode di atas. Namun, coba bayangkan jika ada banyak event dan observer data di dalamnya. Tanpa melihat kodenya pasti sudah terbayangkan kodenya akan seperti apa.
Seiring berjalannya pengembangan aplikasi, hal ini akan menjadi sangat kompleks dan berpotensi menimbulkan kesalahan. Kita yang ikut dalam pengembangan akan merasa kesulitan untuk merawat kompleksitas kode yang ada di dalamnya.
Jika sudah membayangkan hal ini akan terjadi, kita perlu memanfaatkan data state. Data di sini tidak hanya merujuk pada informasi yang akan dicari dan dibaca oleh pengguna, tapi data di sini termasuk juga seperti status loading, pesan eror, dsb.
Memanfaatkan data state cukup mudah, kita hanya perlu membuat sebuah kelas yang bertugas sebagai container yang akan menampung seluruh informasi seperti berikut.
1 2 3 4 5 |
sealed class Resource<out T> { data class Loading<out T>(val state: Boolean) : Resource<T>() data class Success<out T>(val data: T) : Resource<T>() data class Failure<out T>(val throwable: Throwable) : Resource<T>() } |
Cara penggunaannya seperti berikut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class UserViewModel(repository: UserRepository): ViewModel() { val _users = MutableLiveData<Resource<Users>>() val users : LiveData<Resource<Users>> get() = _users fun getAllUsers() { _users.postValue(Loading(true)) try { val tempUsers = repository.getAllUsers() _users.postValue(Success(tempUsers)) } catch (e: Exception) { _users.postValue(Failure(e)) } finally { _users.postValue(Loading(false)) } } } |
Jika kelas ViewModel sudah seperti di atas, kita sudah tidak perlu lagi melakukan observe untuk masing-masing state. Hasilnya kode menjadi lebih simpel seperti berikut.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class UserActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btn.setOnClickListener { userViewModel.getAllUser() } userViewModel.users.observe(this){ resource -> when (resource) { is Loading -> renderLoading(resource.state) is Success -> renderData(resource.data) is Failure -> renderError(resource.throwable) } } } } |
Terlihat lebih bersih, bukan? Dengan begini, kita bisa lebih fokus ke alur dan bisnis aplikasi daripada harus mengatur ke-riwuehan yang ada di dalam proyek.
Kesimpulan
Design pattern dibuat untuk membantu pengembangan aplikasi, tetapi kita perlu memastikan apakah design pattern yang kita terapkan sudah benar dan sesuai untuk menghindari kesalahan-kesalahan yang justru akan menyulitkan pengembangan lebih lanjut.
Oke, sampai di situ dulu pembahasan terkait tips dalam penerapan design pattern MVVM. Jika kamu ingin kita membahas tips-tips pengembangan aplikasi lainnya, silakan tuliskan di kolom komentar di bawah! Sampai jumpa di artikel berikutnya, ya.