Android Kotlin #9: ViewModel dan LiveData – Arsitektur Modern
Halo lagi, calon arsitek aplikasi Android!
Selama ini kita menulis semua kode di Activity: mulai dari memanggil API, mengelola data, sampai memperbarui UI. Ini seperti menaruh dapur, kamar tidur, dan ruang tamu dalam satu ruangan—berantakan dan susah dirawat. Apalagi kalau layar HP diputar (rotasi), Activity akan dibuat ulang dan data kita hilang! 😱
Nah, untuk mengatasi itu, Google merekomendasikan arsitektur modern dengan ViewModel dan LiveData. Anggap saja ViewModel itu asisten pribadi yang menyimpan data dan tidak mati meskipun Activity-nya berganti. LiveData itu seperti TV yang menayangkan data—setiap kali data berubah, TV (UI) langsung update.
Teori Singkat
- ViewModel: kelas yang menyimpan dan mengelola data terkait UI. Dia akan bertahan selama lifecycle View-nya (Activity/Fragment) ada. Jika Activity dibuat ulang (misal rotasi), ViewModel yang sama akan digunakan kembali. Data tidak hilang!
- LiveData: objek yang bisa diamati (observable). Dia menyimpan data dan memberitahu observer (biasanya Activity/Fragment) setiap kali data berubah. Observer hanya akan menerima update jika dalam keadaan aktif (STARTED/RESUMED).
Langkah 1: Menambahkan Dependency
Buka build.gradle (Module: app). Di bagian dependencies, tambahkan:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
Klik Sync Now.
Langkah 2: Membuat ViewModel untuk Wisata
Buat kelas Kotlin baru: WisataViewModel.kt (pilih Class). Isinya:
package com.example.wisataapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class WisataViewModel : ViewModel() {
// MutableLiveData (bisa diubah) vs LiveData (hanya dibaca)
private val _listWisata = MutableLiveData<List<DaftarWisata>>()
val listWisata: LiveData<List<DaftarWisata>> get() = _listWisata
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> get() = _isLoading
private val _errorMessage = MutableLiveData<String?>()
val errorMessage: LiveData<String?> get() = _errorMessage
fun fetchWisata() {
viewModelScope.launch {
_isLoading.value = true
try {
val response = RetrofitInstance.api.getWisata().execute()
if (response.isSuccessful) {
response.body()?.let {
_listWisata.value = it
}
} else {
_errorMessage.value = "Gagal mengambil data: ${response.code()}"
}
} catch (e: Exception) {
_errorMessage.value = "Error: ${e.message}"
} finally {
_isLoading.value = false
}
}
}
}
Penjelasan:
MutableLiveData: versi LiveData yang nilainya bisa diubah (dari dalam ViewModel).LiveData: versi read-only yang diekspos ke Activity.viewModelScope: Coroutine scope yang terkait dengan ViewModel. Akan otomatis dibatalkan saat ViewModel dihapus._isLoading: untuk menampilkan indikator loading._errorMessage: untuk menampilkan pesan error.
Langkah 3: Menggunakan ViewModel di MainActivity
Sekarang kita ubah MainActivity.kt untuk menggunakan ViewModel. Kita akan mengamati LiveData dan memperbarui UI saat data berubah.
Kode baru MainActivity.kt:
package com.example.wisataapp
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: WisataAdapter
// Delegasi viewModels() untuk mendapatkan instance ViewModel
private val wisataViewModel: WisataViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerViewKategori)
recyclerView.layoutManager = LinearLayoutManager(this)
// Observer LiveData
wisataViewModel.listWisata.observe(this, Observer { listWisata ->
// Update adapter saat data berubah
adapter = WisataAdapter(listWisata) { wisata ->
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("wisata", wisata)
startActivity(intent)
}
recyclerView.adapter = adapter
})
wisataViewModel.isLoading.observe(this, Observer { isLoading ->
// Bisa tampilkan progress bar di sini
if (isLoading) {
// Tampilkan loading
} else {
// Sembunyikan loading
}
})
wisataViewModel.errorMessage.observe(this, Observer { errorMsg ->
errorMsg?.let {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
// Panggil fetch data
wisataViewModel.fetchWisata()
}
}
Perhatikan:
by viewModels(): delegasi KTX untuk mendapatkan ViewModel yang sudah di-scope ke Activity.observe(this, Observer): mengamati LiveData. Parameter pertama adalah LifecycleOwner (Activity), sehingga observer hanya aktif saat Activity dalam keadaan start/resume.- Ketika data berubah (setelah fetch), observer akan dipanggil dan RecyclerView diperbarui.
Langkah 4: Jalankan dan Uji Rotasi
Jalankan aplikasi. Saat pertama kali, data akan dimuat. Coba putar layar HP emulator (Ctrl + F11 di emulator). Seharusnya data tetap ada, tidak perlu memuat ulang dari API. Itulah kehebatan ViewModel!
fetchWisata() dan lihat di Logcat. Seharusnya hanya dipanggil sekali saat pertama kali ViewModel dibuat.
Langkah 5: Menambahkan ProgressBar (Opsional)
Kita bisa menambahkan ProgressBar di layout untuk indikator loading. Tambahkan di activity_main.xml di dalam ConstraintLayout:
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Kemudian di MainActivity, panggil ProgressBar dan ubah visibilitasnya sesuai isLoading:
private lateinit var progressBar: ProgressBar
// di onCreate
progressBar = findViewById(R.id.progressBar)
wisataViewModel.isLoading.observe(this, Observer { isLoading ->
progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
})
Langkah 6: (Bonus) ViewModel untuk DetailActivity
Di DetailActivity, kita juga bisa pakai ViewModel jika ingin mengambil data lebih lengkap dari API berdasarkan id. Tapi untuk sekarang, kita kirim data via Intent saja sudah cukup.
Selamat! Aplikasi Kini Lebih Kokoh
Dengan ViewModel dan LiveData, aplikasi kamu sudah mengikuti arsitektur modern yang direkomendasikan Google. Data tidak hilang saat rotasi, kode lebih terstruktur, dan mudah di-test.
Rangkuman
- ViewModel: menyimpan data UI dan bertahan dari perubahan konfigurasi.
- LiveData: observable data holder yang sadar lifecycle.
- MutableLiveData: versi LiveData yang bisa diubah nilainya (di dalam ViewModel).
- viewModelScope: coroutine scope untuk operasi background di ViewModel.
- Observer: mengamati LiveData dan memperbarui UI saat data berubah.
Selanjutnya?
Di artikel berikutnya (Android Kotlin #10: Menambah, Mengubah, dan Menghapus Data (POST, PUT, DELETE)), kita akan belajar melakukan operasi tulis ke API. Kita akan menambahkan fitur tambah kategori/wisata, edit, dan hapus dari dalam aplikasi. Sampai jumpa!
Ditulis oleh Kakak programmer yang dulu juga sering kehilangan data karena rotasi. Kalau ada pertanyaan, tulis di komentar ya!