Cara mengambil data dari API dengan Android Architecture Component
Sama seperti jalan ke Roma, tentu ada banyak jalan untuk menghubungkan aplikasi Android dengan API. Tidak ada satu jalan / cara yang dapat masuk di semua kasus, semua tergantung dari situasi dan kondisi.
Tentunya, cara yang digunakan setiap developer selalu dengan tujuan agar aplikasi yang dikembangkannya dapat membantu mempermudah manusia dalam kehidupan sehari-harinya. Namun yang tak kalah penting untuk diingat, developer juga manusia loh, jadi tidak ada salahnya untuk seorang developer mempermudah dirinya. Bagaimana cara developer mempermudah dirinya? Ya, salah satu jawabannya adalah dengan mengembangkan aplikasi menggunakan best practice yang direkomendasikan.
Per bulan Mei tahun 2017 lalu, tepatnya saat Google I/O 2017 [1], Google selaku developer dari Android telah merekomendasikan kita selaku Android Developer untuk membuat aplikasi Android menggunakan Android Architecture Components. Bukan tanpa sebab, seperti yang ditulis pada dokumentasi arsitektur [2], terdapat banyak keuntungan yang dapat kita raih dengan menggunakan pola arsitektur ini, namun keuntungan yang paling ingin saya soroti adalah dengan menggunakan arsitektur ini maka akan terjadinya Separation of Concerns (SoC) dalam proses penulisan source code aplikasi.
Sederhananya, dengan arsitektur Android Architecture Components, kita akan memisahkan kode yang digunakan untuk tampilan UI dan kode yang digunakan untuk proses pada aplikasi (business logic). Memang di tahap awal, hal ini sepertinya seperti menambahkan pekerjaan. Namun, dalam jangka panjang, pengembangan aplikasi menggunakan arsitektur ini akan menghasilkan aplikasi yang kuat (robust), mudah diuji dan mudah dipelihara sehingga membuat aplikasi dapat lebih mudah untuk dikembangkan (scale).
Oke, sudah semakin penasaran dengan bagaimana mengambil data dari API dengan Android Architecture Components? Sebelum kita lihat praktiknya, mari kita terlebih dahulu melihat visualisasi dari pola arsitektur Android Architecture Components:
Bingung? Yap, tenang saja, hal ini wajar, saya juga dahulu bingung hehehe. Sebelum kita masuk ke penjelasan bagaimana arsitektur ini bekerja, mari saya perkenalkan dahulu masing-masing komponen yang berperan:
- View: Activity / Fragment sebagai UI Controller bertugas untuk menampilkan data kepada pengguna dan menangani aksi yang dilakukan oleh pengguna dalam aplikasi.
- ViewModel: Menjadi jembatan untuk View ketika proses meminta dan menerima data dari Repository dan mempertahankan data ketika terjadi configuration changes pada View.
- LiveData: Sebuah variabel yang menyimpan data dan terletak pada ViewModel. Variabel ini dapat di-observe dan akan secara langsung mengabari pengamatnya jika terjadi perubahan data / nilai pada variabel tersebut.
- Repository: Bertugas untuk menangani permintaan data yang diminta oleh View melalui ViewModel. View dan ViewModel tidak perlu tahu darimana kah data yang dikirim oleh Repository berasal, entah dari sebuah database lokal, REST API proyek aplikasi tersebut, atau sumber data lainnya. Intinya, Repository akan mengelola sumber data yang dimiliki aplikasi dan mengirimkan data sesuai dengan permintaan yang diminta.
- Room: Database lokal yang menjadi sebuah sumber data. Room Persistence Library adalah sebuah library yang disediakan oleh Android untuk membuat sebuah database lokal dan mengelola operasi data pada database tersebut.
- Remote Data Source: Sebuah sumber data yang berasal dari network, umumnya seperti dari REST API, Firebase, ataupun Web Service lainnya. Ada banyak library yang dapat digunakan untuk berkomunikasi dengan Remote Data Source, namun pada umumnya, library Retrofit digunakan untuk berkomunikasi dengan sebuah REST API.
Catatan: Agar pembahasan tidak melebar terlalu jauh, di artikel ini kita tidak akan membahas bagaimana implementasi penyimpanan data secara lokal menggunakan Room.
Dalam konteks mengambil data dari API dengan Android, agar lebih memahami Android Architecture Components, mari kita coba ambil sebuah kasus pengambilan data pengguna Github dari Github REST API.
Bayangkan kita mempunyai sebuah aplikasi yang memungkinkan pengguna mencari daftar pengguna Github. Setelah memasukkan username pengguna Github, aplikasi akan menampilkan daftar pengguna Github dengan username serupa menggunakan RecyclerView.
Sudah terbayang? Selanjutnya mari kita coba visualisasikan alur proses aplikasi menggunakan Android Architecture Components:
Secara sederhana, bagian View bertugas untuk menangani interaksi pengguna dalam aplikasi. Dalam contoh kasus di atas, View bertugas untuk mengirim perintah ke ViewModel untuk melakukan proses pencarian pengguna Github dengan username “X”. Lalu, ViewModel yang bertugas untuk menjembatani antara View dan Repository melakukan tugasnya dengan meneruskan perintah tersebut ke Repository.
Selanjutnya, menggunakan Retrofit, Repository meneruskan permintaan dari ViewModel dengan membuat request ke REST API Github untuk mencari daftar pengguna Github dengan username yang serupa dengan “X”. Setelah mendapatkan data response dari REST API Github, maka Retrofit akan menggunakan GSON untuk melakukan proses parsing pada response data JSON yang diterima sehingga menghasilkan data yang sesuai dengan yang diperlukan oleh Repository. Masih di Repository, data yang sudah dalam bentuk kelas model GSON diberikan ke ViewModel untuk disimpan ke dalam LiveData.
Penting untuk diingat, proses request data ke sebuah API adalah proses yang bersifat asinkron. Oleh karena itu, LiveData berperan penting dalam proses ini karena Ia akan memberi tahu View saat terjadinya perubahan data / nilai pada dirinya sehingga View sebagai pengamat dapat meresponsnya. Dalam contoh kasus di atas, View akan memperbaharui tampilan komponen RecyclerView untuk menampilkan daftar pengguna Github dengan username yang serupa dengan “X”.
Catatan: GSON digunakan agar data dengan format JSON yang diterima aplikasi dapat di-parsing secara otomatis ke sebuah kelas model.
Oke, secara teori, apakah anda sudah semakin paham bagaiama alur dari proses mengambil data dari API dengan Android Architecture Component? Jika sudah, izinkan saya mengutip dari J. Robert Oppenheimer yang mengatakan bahwa “Theory will take you only so far”. Jadi, yuk, mari kita langsung saja praktekkan bagaimana mengambil data dari API dengan Android Architecture Component.
Pertama-tama, yuk terlebih dahulu kita unduh starter source code untuk aplikasi di atas. Starter source code disini hanya berisi kode yang digunakan untuk tampilan UI dari aplikasinya saja sehingga pembahasan praktik kita kali ini tetap dapat fokus ke proses mengambil data dari API dengan Android Architecture Component. Untuk teman-teman yang ingin langsung melihat hasil akhirnya, saya juga sudah sediakan finish source code dari aplikasinya ya.
- Starter Source Code — Mengambil data dari API dengan Android Architecture Component
- Finish Source Code — Mengambil data dari API dengan Android Architecture Component
Oke, selanjutnya, setelah mengunduh starter source code dan membuka proyek di Android Studio, maka langkah pertama yang perlu kita lakukan adalah menambahkan library Retrofit, GSON, ViewModel, dan LiveData ke dalam berkas build.gradle(module:App):
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
Kemudian, karena aplikasi akan menggunakan internet untuk meminta data ke Github REST API, maka jangan lupa untuk tambahkan permission Internet ya di dalam berkas AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Selanjutnya, karena kita akan menggunakan Github REST API, maka kita perlu memilih data yang ingin kita dapatkan dari API. Terdapat banyak data yang bisa kita ambil dari beragam endpoint yang telah disediakan oleh Github REST API [3]. Lebih lanjut, dikarenakan aplikasi kita membutuhkan daftar pengguna Github dengan username yang dimasukkan pengguna, maka pada kesempatan kali ini, kita akan menggunakan endpoint berikut ini:
https://api.github.com/search/users?q={username}
Dengan memberikan username yang ingin kita cari, maka endpoint di atas akan menampilkan daftar pengguna Github yang mempunyai username yang serupa dengan yang kita minta. Menggunakan Postman, berikut adalah contoh response yang diberikan oleh Github REST API ketika kita melakukan request ke endpoint tersebut:
Seperti yang bisa dilihat pada gambar di atas, kita akan mendapatkan data JSON yang dimana atribut “items” menyimpan data daftar pengguna Github. Pada aplikasi, kita tentu tidak ingin menampilkan semua data yang diperoleh, melainkan, kita hanya akan mengambil data atribut “items” yang mewakili sebuah list daftar pengguna, atribut “login” yang mewakili username pengguna, “avatar_url” yang mewakili foto profil pengguna, dan “html_url” yang mewakili alamat url dari pengguna Github tersebut. Oleh karena itu, kita perlu membuat sebuah kelas model GSON yang dapat melakukan proses parsing dari data JSON tersebut menjadi sebuah kelas model yang hanya menyimpan data / atribut yang kita inginkan.
Lebih lanjut, kita dapat menggunakan plugin RoboPOJOGenerator yang dapat secara otomatis membuat kelas model GSON dari data JSON yang ada. Agar pembahasan tidak melebar, langkah-langkah untuk pemasangan plugin dapat dilihat pada dokumentasi plugin ya [5].
Seperti yang dapat dilihat pada gambar bergerak di atas, menggunakan data JSON yang ada, kita dapat menggunakan plugin RoboPOJOGenerator untuk membuat kelas model GSON. Selanjutnya, kita hanya perlu mengganti nama kelas model dan menyesuaikan data / atribut yang ada dengan yang kita inginkan. Berikut adalah hasil akhir dari kelas model yang kita inginkan:
Catatan: Pastikan penggunaan kelas model dan atributnya pada proyek ini sesuai dengan penamaan yang kamu berikan ya.
Selanjutnya, mari kita coba lihat kembali visualisasi alur proses aplikasi menggunakan Android Architecture Components. Agar kita dapat sambil memahami alur proses aplikasi kita, maka mari kita buat arsitektur aplikasi kita dari belakang. Lebih tepatnya, mari kita buat dari bagian objek Retrofit yang akan bertugas untuk berhubungan dengan Github REST API.
Seperti yang tadi sudah kita bahas, kita akan menggunakan endpoint https://api.github.com/search/users?q={username}, yang dimana endpoint ini mewajibkan kita untuk mengisi query dengan username yang ingin kita cari. Oleh karena itu hal pertama yang akan kita lakukan adalah membuat interface endpoint API yang akan digunakan oleh objek Retrofit:
Seperti yang dapat dilihat pada kode di atas, interface “ApiService” mempunyai sebuah metode abstrak bernama “getSearchData”. Pada metode ini, kita menambahkan anotasi “@GET” dengan endpoint yang kita tuju (“search/users”) dan parameter “id”, yang dimana parameter ini telah kita tambahkan anotasi “@Query” dengan key “q”. Kemudian, data keluaran (return) dari abstrak method ini adalah sebuah Response dengan tipe data kelas model yang telah kita buat sebelumnya, yaitu “SearchResponse”. Terakhir, penting untuk diingat, seperti yang dijelaskan sebelumnya, dikarenakan proses request data ke API adalah proses yang bersifat asinkron, maka kita menambahkan keyword “suspend” pada deklarasi metode abstrak “getSearchData”.
Selanjutnya, setelah membuat interface endpoint API, maka kita akan membuat objek Retrofitnya:
Dapat dilihat pada kode di atas, ada empat hal yang berperan dalam pembuatan objek Retrofit, yaitu base URL dari API yang kita gunakan, GSON yang digunakan untuk proses parsing data JSON yang akan diterima, OkHttpClient yang bertugas untuk menangani proses request HTTP, dan metode “create” yang akan meingimplementasikan endpoint API dari interface “ApiService”.
Oke, selanjutnya, setelah membuat objek Retrofit, maka langkah selanjutnya adalah membuat Repository. Namun sebelum membuat kelas Repository, mari kita buat sebuah sealed class bernama Result:
Agar pembahasan tidak melebar, saya tidak akan membahas detail konsep dari kelas ini, namun sederhananya, pada proyek ini, kelas Result akan bertugas untuk menangani proses Loading, Success, dan Error pada saat proses request ke API. Untuk yang ingin tahu terkait detail konsep dari sealed class, terdapat artikel Medium oleh Seanghay Yath yang membahas terkait hal ini [6].
Selanjutnya, berikut adalah kode yang kita gunakan untuk membuat Repository:
Dapat dilihat pada kode di atas, SearchRepository dapat dibuat dengan cara memanggil metode “getInstance”. Lebih lanjut, melalui SearchRepository, kita dapat mendapatkan daftar pengguna Github yang ingin kita cari dengan cara memanggil metode “findSearchUser” dan memberikan username pengguna Github yang ingin kita cari ke metode tersebut.
Seperti yang telah dijelaskan sebelumnya, penting untuk diingat, proses request data ke Github REST API pada metode “findSearchUser” adalah proses yang bersifat asinkron, oleh karena itu disinilah pentingnya peran dari LiveData dan kelas Result yang berperan sebagai data keluaran (return) dari metode “findSearchUser”. Mula-mula nilai dari LiveData adalah “Result.Loading”, lalu jikalau proses request berhasil dan sukses, maka nilai berubah menjadi “Result.Success(response.body)”, namun jikalau proses request gagal atau error maka nilai berubah menjadi “Result.Error(“pesan error”)”.
Selanjutnya, setelah membuat Repository, maka langkah selanjutnya adalah membuat bagian ViewModel. Sedikit kilas balik, ViewModel adalah penghubung antara View dan Repository. Tugas ViewModel pada kasus kali ini adalah meneruskan perintah dari View ke Repository lalu menyimpan data hasil keluaran metode “findSearchUser” milik kelas SearchRepository ke dalam LiveData. Berikut adalah kode yang kita gunakan untuk membuat ViewModel:
Seperti yang dapat kita lihat pada kode di atas, pemanggilan metode “findSearchUser” milik kelas SearchRepository dilakukan di dalam sebuah Coroutine melalui “ViewModelScope.launch(Dispatchers.IO)”. Hal ini kita lakukan karena melalui objek Retrofit, metode “findSearchUser” milik kelas SearchRepository akan memanggil implementasi dari metode abstrak “getSearchData” milik interface ApiService yang merupakan metode dengan keyword “suspend”.
Keyword “suspend” digunakan dalam proses asinkron yang menggunakan Coroutine. Sederhananya, “suspend” digunakan untuk menandakan bahwa sebuah fungsi dapat ditangguhkan. Terdapat artikel bagus oleh Baeldung yang menjelaskan tentang hal ini. Untuk yang tertarik, bisa lihat disini ya [7].
Kemudian, karena nilai LiveData pada keluaran “findSearchUser” milik kelas SearchRepository bersifat dinamis (terdapat 3 nilai, yaitu: Result.Loading, Result.Success, Result.Error), maka disini kita akan menggunakan “when” untuk menangani setiap nilai yang ada pada kelas Result.
Lebih lanjut, kita membuat masing-masing tiga variabel MutableLiveData dan LiveData. Anda mungkin bingung, apa bedanya MutableLiveData dan LiveData? Tenang saja, MutableLiveData adalah jenis LiveData yang memungkinkan nilainya (value) untuk dapat diubah, sementara LiveData adalah jenis LiveData yang nilainya tidak dapat diubah setelah diinisialisasi. Konsep ini mengikuti prinsip enkapsulasi dalam pemrograman, di mana MutableLiveData bertindak sebagai setter yang dalam contoh kali ini hanya dimungkinkan perubahan nilainya melalui Repository, sedangkan LiveData berperan sebagai getter yang memungkinkan View untuk mengamati perubahan nilai.
Lebih lanjut lagi, “_listOfUser” & “listOfUser” digunakan untuk menyimpan data daftar pengguna Github, “_isLoading” & “isLoading” digunakan untuk memberi tahu View (UI) kapan ditampilkan / disembunyikannya komponen ProgressBar, dan “_errorMessage” & “errorMessage” digunakan untuk menampilkan Toast yang berisi pesan error di View (UI).
Catatan: Metode “postValue” digunakan untuk mengubah nilai (value) dari sebuah MutableLiveData dari dalam Courotine dan memberi tahu (notify) main thread (UI Thread) terkait perubahan nilai ini.
Terakhir, pada bagian View, lebih tepatnya MainActivity, karena kita sudah mengimplementasikan ViewModel beserta dengan LiveData yang berperan, maka sekarang kita tinggal menyambungkan semuanya saja di dalam View. Untuk itu, mari kita awali dengan mendeklarasi dan menginisialisasi ViewModelnya pada metode “onCreate”:
private lateinit var binding: ActivityMainBinding
// Deklarasi ViewModel
private lateinit var viewModel: MainViewModel
override fun onCreateOptionsMenu(menu: Menu?): Boolean {}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Inisialisasi ViewModel
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val layoutManager = LinearLayoutManager(this)
binding.rvSearchList.layoutManager = layoutManager
val itemDecoration = DividerItemDecoration(this, layoutManager.orientation)
binding.rvSearchList.addItemDecoration(itemDecoration)
}
Selanjutnya, pada metode “onCreateOptionsMenu”, lebih tepatnya pada bagian override metode “onQueryTextSubmit”, mari kita panggil metode “findSearchUser” milik kelas MainViewModel lalu kita sertakan “query” yang berisi data masukkan yang dimasukkan oleh pengguna aplikasi:
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.option_menu, menu)
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(this@MainActivity.componentName))
searchView.queryHint = resources.getString(R.string.search_hint)
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String): Boolean {
// Memanggil metode findSearchUser milik ViewModel
viewModel.findSearchUser(query)
searchView.clearFocus()
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
return true
}
Terakhir, pada bagian metode “onCreate”, mari kita observe ketiga LiveData tadi, lalu mengimplementasikan aksi ketika terdapat perubahan nilai pada ketiga LiveData tersebut:
// Inisialisasi ViewModel
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val layoutManager = LinearLayoutManager(this)
binding.rvSearchList.layoutManager = layoutManager
val itemDecoration = DividerItemDecoration(this, layoutManager.orientation)
binding.rvSearchList.addItemDecoration(itemDecoration)
// Observe LiveData "isLoading"
viewModel.isLoading.observe(this){
showLoading(it)
}
// Observe LiveData "listOfUser"
viewModel.listOfUser.observe(this){
if (it != null){
setSearchResultData(it)
}
}
// Observe LiveData "errorMessage"
viewModel.errorMessage.observe(this){
if (it != null){
Toast.makeText(this@MainActivity, it, Toast.LENGTH_SHORT).show()
}
}
Secara keseluruhan, berikut adalah kode pada kelas MainActivity:
Sip, setiap komponen pada Android Architecture Components telah berhasil kita buat dan hubungkan. Sekarang mari kita coba jalankan aplikasi kita.
Yeay, aplikasi telah berhasil berjalan sesuai dengan yang kita harapkan di awal. Terlebih lagi, seperti yang dapat kita lihat pada gambar bergerak di atas, dikarenakan kita telah menerapkan ViewModel dan LiveData, maka data yang digunakan oleh View tetap dapat bertahan ketika terjadi configuration changes.
Menurut saya, selain dapatnya data bertahan pada saat configuration changes, keuntungan utama terletak pada terjadinya Separation of Concerns (SoC) yang dimana ini sangat berdampak bagi pengembangan aplikasi yang kita kembangkan.
Sebagai contoh, masih dalam kasus kita kali ini, jikalau kita ingin mengembangkan fitur yang dapat menampilkan data daftar followers dan following dari suatu akun Github, maka kita tinggal membuat metode abstrak pada “ApiService” dengan endpoint yang ingin kita tuju untuk mendapatkan data tersebut, metode pada “SearchRepository” yang digunakan untuk memanggil metode abstrak tersebut melalui objek Retrofit, metode dan variabel LiveData pada “DetailViewModel” untuk meneruskan permintaan View ke Repository dan menyimpan hasil keluaran dari Repository, dan komponen pada View untuk mengirim username akun Github yang ingin kita tuju dan melakukan observe pada LiveData yang terletak pada “DetailViewModel” untuk kemudian melakukan perubahan tampilan berdasarkan nilai (value) dari LiveData tersebut.
Akhir kata, selamat, kita telah berhasil membuat aplikasi yang memungkinkan pengguna mencari daftar pengguna Github. Lebih penting lagi, kita telah memahami bagaimana cara membuat aplikasi yang mengambil data dari API menggunakan Android Architecture Components. Tepuk tangan untuk kita semua 👏👏
Referensi
- Architecture components — introduction (Google I/O ’17) oleh Android Developers
- Panduan arsitektur aplikasi oleh Android Developers
- Beragam endpoint pada Github REST API
- Data JSON hasil pencarian daftar pengguna Github pada Github REST API
- Dokumentasi plugin RoboPOJOGenerator
- Konsep Sealed Class untuk penganganan Success dan Error oleh Seanghay Yath
- Introduction to Kotlin Coroutines oleh Baeldung