Android MVVM — Part 5 RoomDataStore
Membangun Aplikasi MVVM Dengan Kotlin, Coroutines dan Architecture Component
Protes Pengguna
Udah berbulan-bulan sejak aplikasi kartu pokemon kita masuk ke playstore. Ternyata kenyataan yang terjadi, ga sesuai dengan ekspektasi kita. Kita yang awalnya semangat banget dapet pujian dari pengguna, eh malah dapet banyak kritik dan protes dari pengguna.
Banyak banget pengguna yang ngerasa aplikasi kita boros paket data cuma buat sekedar nampilin informasi kartu pokemon. Dari banyaknya protes ini, project manager menyuruh tim programmer untuk mengurangi protes kayak gini. Setelah lama dicek dan ricek kenapa banyak yang ngerasa aplikasi kita boros, ketemu deh akar permasalahannya. Aplikasi kita bakal terus akses ke internet setiap kali aplikasi dibuka. Karena selama ini, kita simpan cache pada variable biasa yang bakal dihapus sama garbage collector waktu aplikasinya ditutup.
Buat nyelesein masalah ini, kita butuh tempat penyimpanan yang lebih persisten yang ga akan dihapus sama garbage collector waktu aplikasinya ditutup. Salah satunya adalah dengan memanfaatkan database SQLite.
Room
Pada bahasan sebelumnya, kita pernah membahas kalau Google mengenalkan Architecture Component pada Google I/O 2017. Salah satu library yang terdapat dalam Architecture Component adalah Room. Room adalah layar abstraksi untuk SQLite. Google merekomendasikan penggunaan Room. Selain mengurangi boilerplate pada penggunaan SQLite, Room juga melakukan verifikasi query SQL saat proses kompilasi.
Yuk Bikin!
Hal pertama yang bakal kita lakuin adalah menambahkan dependency dari Room ke file build.gradle. Ubah file build.gradle menjadi seperti ini.
Tambahkan baris:
apply plugin: "kotlin-kapt"
Kapt pada kotlin berperan sebagai pengganti annotationProcessor. AnnotationProcessor ini bekerja saat proses kompilasi untuk memroses semua anotasi yang ada dalam project. Salah satu tugas annotationProcessor merupakan membuat class secara otomatis.
Selanjutnya adalah kita tambahkan dependency untuk menggunakan Room dalam project.
implementation "androidx.room:room-runtime:2.2.4" implementation 'androidx.room:room-ktx:2.2.4' kapt "androidx.room:room-compiler:2.2.4"
Setelah itu kita harus mengubah model yang ada menjadi sesuai ketentuan dari Room. Berikut merupakan hasil perubahan dari class PokemonCard.
Pertama kita menambahkan anotasi @Entity untuk menandakan kalau ini class PokemonCard merupakan table yang ada di dalam database. Selain itu ada variabel tambahan yaitu idCard. Kita menggunakan anotas @PrimaryKey untuk menandakan kalau variabel ini adalah primary key dari table PokemonCard.
Untuk kelas PokemonSet diubah menjadi seperti ini.
Tidak banyak perbedaan dari class PokemonSet dengan PokemonCard. Setelah selesai dengan model, kita akan membuat class AppDatabase.
Class ini adalah kelas yang mengatur database SQLite yang akan digunakan. Kita harus menambahkan parameter entities untuk menunjukkan table apa saja yang terdapat dalam database, lalu version atau versi dari databasenya.
Pastikan kelas ini merupakan class abstrak yang di dalamnya terdapat beberapa fungsi abstrak untuk mendapatkan object Dao (Data Access Object). Fungsi abstrak ini akan diproses oleh kapt sehingga kita tidak perlu memikirkan tentang implementasi dari fungsi ini.
Selanjutnya kita membuat class ini menjadi Singleton sehingga hanya ada satu instance dari class ini yang ada selama aplikasi berjalan. Pada poin ini, IDE akan menunjukkan error karena IDE tidak mengenali class PokemonCardDao dan juga class PokemonSetDao. Oleh karena itu tugas kita selanjutnya adalah membuat class tersebut.
Untuk class dao, kita harus menandainya dengan anotasi @Dao. Class dao ini digunakan untuk menjalankan query tertentu pada database. Seperti sebelumnya, kita tidak perlu memikirkan implementasi dari setiap fungsi pada class ini karena kapt akan mengimplementasikannya secara otomatis. Keyword suspend pada setiap fungsi digunakan untuk memastikan kalau setiap fungsi akan berjalan pada CoroutineScope dan bisa dijalankan selain di thread utama.
Tidak ada banyak perbedaan pada class PokemonSetDao dan PokemonCardDao.
Pada part 2 kita sudah membuat repository untuk setiap model dan mengimplementasikan prinsip Open-Closed Principle (OCP). Hal ini memudahkan kita untuk mengubah cara kita menyimpan cache dari aplikasi. Sebelumnya kita mengatur cache dari aplikasi menggunakan objek dari class ModelLocalDataStore. Sesuai dengan prinsip OCP, kita tidak boleh mengubah isi dari ModelLocalDataStore, yang harus kita lakukan adalah membuat sebuah DataStore baru dengan logika yang baru. Oleh karena itu langkah selanjutnya adalah membuat RoomDataStore untuk setiap model.
Tidak terlalu banyak perubahan antara PokemonCardLocalDataStore dengan PokemonCardRoomDataStore, sebelumnya kita menyimpan setiap kartu dalam variable caches. Sekarang semua data kita simpan dalam database dan dapat diakses melalui PokemonCardDao.
Langkah terakhir yang harus dilakukan adalah memastikan kalau setiap Repository menggunakan RoomDataStore dan tidak menggunakan LocalDataStore. Pada Part 4, kita melakukan konfigurasi untuk setiap Repository pada BaseApplication. Maka kita akan ubah konfigurasi sebelumnya dengan konfigurasi yang baru.
Jadi kita mengubah yang sebelumnya kita memanggil init dengan memberikan hasil instantiasi ModelLocalDataStore, sekarang kita memberikan hasil instantiasi dari ModelRoomDataStore. Sekarang ayo kita jalanin buat liat hasilnya.
Berhasil! Yeay! Kita nggak nyentuh Fragment atau logika tampilan sama sekali. Kita hanya menambahkan sebuah DataStore baru dan mengubah konfigurasi dari Repository dan semuanya berjalan lancar. Sekarang coba tutup aplikasinya lalu buka lagi, ketika kita membuka aplikasi untuk kedua kalinya, loading akan terasa lebih cepat karena data yang dibutuhkan sudah ada di database dan tidak perlu mengakses internet.
Finally!
Oke aplikasi versi baru kita siap rilis lagi ke playstore, yay! Tapi tunggu dulu, kayaknya masih ada kelemahan lagi. Gimana kalau ada perubahan di server dan data sebelumnya udah tersimpan di database, berarti pengguna ga bisa dapet data barunya dong? Ya, benar. Untuk mengakalinya, kita bisa menambahkan satu tombol untuk melakukan sinkronisasi dengan server. Selain itu kita juga bisa mengubah logika pada Repository untuk melakukan pengecekan untuk setiap data yang ada di database, apakah data tersebut sudah kadaluarsa atau belum? Selain itu masih banyak hal yang bisa dipelajari di Room yang tidak dibahas dalam artikel ini, seperti relasi antar table, transaksi dll.
Poin yang ingin ditunjukkan di sini adalah betapa mudahnya kita untuk mengubah sesuatu ketika kita mengimplementasikan prinsip dari SOLID. Dengan cara yang sama kita bisa menambahkan DataStore lain seperti FirestoreDataStore atau FileDataStore. Prinsip SOLID yang ditekankan pada artikel ini adalah prinsip Open-Closed Principle yang mengatakan kalau setiap class tidak boleh ada perubahan, hanya boleh ada penambahan.
Link project github akan dishare di akhir bagian ini. Masih banyak sekali hal yang harus kita pelajari. Jadi apa lagi yang kita tunggu buat pelajari itu? Tapi pada akhirnya, semua ilmu yang kita punya tidak akan pernah bermanfaat kalau kita cuma pendam sendiri aja. Ayo kita mulai budaya untuk membagikan dan mengimplementasikan ilmu kita punya.
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