Koneksi Activity — ViewModel dengan kontrak
Architecture Component — Activity — ViewModel — LiveData
Pada tulisan kali ini akan membahas kontrak antara Activity / Fragment dengan ViewModel. Kontrak disini merupakan aturan penulisan hubungan antara Activity dan ViewModel. Data disimpan memakai LiveData.
Mengapa perlu kontrak / aturan penulisan?
- Supaya kode lebih mudah dibaca karena ada keseragaman.
- Lebih memudahkan (sebagai pengingat) supaya data selalu disimpan dan diproses di ViewModel.
Mengapa kode yang mudah dibaca (readability) itu penting?
- Kode lebih mudah dimengerti, jadi memudahkan untuk debug, maintain ataupun melakukan perubahan.
- Kita atau rekan kita akan sering baca ulang kode yang kita tulis ketika akan melakukan perubahan. Ketika lebih mudah dimengerti maka akan menghemat waktu.
Sebelum masuk ke dalam kontrak penulisan, ada baiknya kita mengingat kembali mengenai Activity, ViewModel dan LiveData
Activity
- Activity bertugas mengelola UI dan interaksi dengan user.
- Activity mempunyai lifecycle. Activity akan di-destroy dan di recreate jika terjadi perubahan konfigurasi.
- Jika data disimpan di Activity data ini akan hilang bila terjadi perubahan konfigurasi. (harus disimpan di savedInstanceState — tetapi merepotkan dan ukurannya terbatas)
- Jadi data sebaiknya disimpan di ViewModel.
- Data disimpan mengunakan LiveData.
- Activity akan observe perubahan LiveData dan jika terjadi perubahan lakukan sesuatu di UI.
- Minimum logic di Activity, logic dilakukan di ViewModel. Juga karena Unit Test dilakukan di ViewModel.
ViewModel
- ViewModel digunakan untuk menyimpan dan mengelola data.
- ViewModel dapat disebut helper class dari Activity untuk menyiapkan data kepada UI.
- Data yang disimpan di ViewModel bersifat persistent. Data ini akan tetap ada jika terjadi perubahan konfigurasi, seperti terjadi rotasi screen, atau activity di-destroy, dll. ViewModel tidak punya android.* import. ViewModel tidak tergantung app lifecycle.
- ViewModel tidak boleh punya view object, activity, fragment ataupun context atau apapun yang tidak perlu punya scope viewModel. Dengan melakukan ini akan mencegah memory leak.
- Jika dipakai di Activity, setup ViewModel dilakukan di onCreate(). Jika dipakai di fragment, setup ViewModel dilakukan setelah fragment ter-attach dengan Activity.
LiveData
- LiveData adalah observable data.
- LiveData sadar akan lifecycle Activity, jadi LiveData hanya akan mengupdate observer-nya(Activity) bila dalam kondisi active lifecycle.
- LiveData dapat berisi data(object) apapun maupun collection.
- LiveData di observe oleh Activity pada onCreate()
- Update value LiveData dilakukan di ViewModel.
Mari masuk ke pembahasan kontrak antara Activity dan ViewModel. Kontrak menggunakan interface dan ViewModel akan implement interface ini.
Kontrak yang dipakai menggunakan konsep Input — Output.
- Input adalah terjadi interaksi user dengan Activity.
- Process dilakukan di ViewModel.
- Output adalah setelah selesai diproses di ViewModel, Activity akan melakukan apa. Untuk memberitahu Activity bahwa process telah selesai adalah menggunakan observer pattern. ViewModel punya LiveData yang akan di observe oleh Activity.
Contoh case:
- Activity punya recycleView untuk menunjukkan list data pemain sepakbola.
- Pull to refresh untuk refresh data.
- Load more untuk load data di halaman berikutnya.
- Update recyclerView setelah mendapatkan data dari API.
- Show error snackbar ketika gagal mendapatkan data
- Click item recyclerView
Maka contoh kontrak interface-nya:
interface MyViewModelContract {
// Input sections
fun onViewLoaded(countryId: String) fun onRefresh() fun onLoadMore(page: Int) fun onItemClicked(index: Int)
// Output sections
fun doUpdatePlayerList(): LiveData<MutableList<Player>> fun doShowErrorSnackBar(): SingleLiveEvent<Boolean>}
Untuk bagian input untuk membedakan diberi awalan “on” —kata kerja suatu interaksi di UI.
Untuk bagian output diberi awalan “do” — kata kerja melakukan sesuatu di UI.
Pada ViewModel:
class MyViewModel: MyViewModelContract { var countryId = ""
var page = 1 private val updatePlayerList: MutableLiveData<List<Player>> = MutableLiveData() private val showErrorSnackBar: SingleLiveEvent<Boolean> = SingleLiveEvent() // Input Sections
override fun onViewLoaded(countryId: String) {
this.countryId = countryId
page = 1 getPlayerFromApi(countryId, page)
} override fun onRefresh() {
page = 1
getPlayerFromApi(countryId, page)
} override onLoadMore(page: Int) {
this.page = page
getPlayerFromApi(countryId, page)
} // Output Sections
override doUpdatePlayerList = updatePlayerList
override doShowErrorSnackBar = showErrorSnackBar private fun getPlayerFromApi(countryId: String, page: Int) {
// get data from api
// ex: success result is List<Player>
// this just for example if (Result.Success) {
updatePlayerList.value = result
} else {
showErrorSnackBar.value = true
}
}
}
Pada Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) //...
// prepare viewModel initRecyclerView()
setUiListener()
subscribeToLiveData() // ex: countryId from intent when start this activity
viewModel.onViewLoaded(countryId)
}
private fun initRecyclerView() {
// For simplicity I'm just using adapter for explain myAdapter = MyAdapter(object: MyAdapter.Listener { override onItemClicked(index: Int) {
viewModel.onItemClicked(index)
} override onRefresh() {
viewModel.onRefresh()
} override onLoadMore(page: Int) {
viewModel.onLoadMore(page)
}
)}
}
private fun setUiListener() {
// for handle other UI input (if any)}
// Observer to ViewModel LiveData
// Output section, do something to response LiveData changes
private fun subscribeToLiveData() {
viewModel.run {
doUpdatePlayerList().reObserve(this@Activity, Observer) {
updateRecyclerView(it)
}) doShowErrorBottomSheet().reObserve(this@Activity, Observer) {
showErrorBottomSheet()
})
}
}
Dengan adanya keseragaman penamaan kontrak ini akan mempermudah kita jika suatu saat akan melakukan perubahan, atau untuk tracing bug.
Jadi bisa mulai dengan cek ada interaksi apa di UI kemudian cek proses di ViewModel-nya.
Saat membuat unit test juga membantu karena method di ViewModel fungsinya jelas.
Kesimpulan
Pada tulisan ini kita belajar:
- Kontrak antara Activity dan ViewModel untuk readability.
- Beberapa poin pengingat mengenai Activity, ViewModel, LiveData
- Menggunakan konsep Input — Output
- Penamaaan Input : dengan awalan “on” — kata kerja interaksi UI
- Penamaan Output : dengan awalan “do” — kata kerja melakukan sesuatu di UI
- Semua data ada di ViewModel
- Contoh kode
Sumber: