Sub CPMK :
- Mahasiswa dapat menjelaskan konsep Web API
- Mahasiswa dapat melakukan Parsing JSON menggunakan LoopJ
- Mahasiswa dapat membuat aplikasi android yang dapat mengambil data dari API
Alat dan Bahan :
- Laptop atau PC dengan Spesifikasi Prosesor minimal Corei5 dan RAM 8 GB
- Android Studio
- Android Device
Langkah Praktikum
- Buat Project baru di android studio, dengan kriteria sebagai berikut :
Nama Project | MyRandomQuotes |
Target & Minimal SDK | Phone and Tablet, API Level 21 |
Tipe Activity | Empty Activity |
Language | Kotlin |
- Pada latihan kali ini kita akan menampilkan random quote menggunakan data API dari https://quote-api.dicoding.dev (Alternatif). API yang akan kita gunakan yaitu :
- Random Quote : https://quote-api.dicoding.dev/random
- List Quotes : https://quote-api.dicoding.dev/list
- Cobalah buka URL tersebut di browser. Maka hasilnya seperti ini:
{"_id":"5a9b22c72bad9600044b700b","en":"Reliable computations are obtainable from buggy programs, which after all, are the only kind of programs there are.","author":"Daniel T. Barry","id":"5a9b22c72bad9600044b700b"}
Supaya terlihat rapi, kita bisa menambahkan extension JSON Formatter pada Chrome sehingga tampilannya akan mudah dibaca seperti ini:
{ "_id": "5a9ab0372bad9600044b6fbf", "en": "When I invented the web, I didn't have to ask anyone's permission.", "author": "Tim Berners-Lee", "id": "5a9ab0372bad9600044b6fbf" }
- Selanjutnya kita akan menggunakan library AsyncHttpClient (LoopJ) yang terdapat pada website loopj.com. Untuk menambahkannya, buka berkas build.gradle(Module: app). Kemudian tambahkan baris berikut:
dependencies { ... implementation 'com.loopj.android:android-async-http:1.4.9' }
Lalu klik pilihan Sync Now di pojok kanan atas Android Studio seperti ini:
- Okay, pertama kita mulai dengan membuat desain aplikasinya terlebih dahulu. Buka activity_main.xml dan buat layout menggunakan Constraint Layout seperti ini:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context=".MainActivity"> <TextView android:id="@+id/tvQuote" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" android:textAlignment="center" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="@string/quote" /> <TextView android:id="@+id/tvAuthor" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" android:textAlignment="center" android:textSize="14sp" android:textStyle="italic" app:layout_constraintTop_toBottomOf="@+id/tvQuote" tools:layout_editor_absoluteX="16dp" tools:text="@string/author" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" 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" /> <Button android:id="@+id/btnAllQuotes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:text="@string/show_list_quotes" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Tambahkan juga resource di strings.xml seperti berikut:
<resources> <string name="app_name">MyRandomQuotes</string> <string name="quote">No one can win every battle, but no man should fall without a struggle</string> <string name="author">- Peter Parker</string> <string name="show_list_quotes">Daftar Quote</string> </resources>
Sehingga desain dari aplikasi utamanya akan menjadi seperti ini:
- Selanjutnya buka MainActivity dan buat method dengan nama getRandomQuote().
class MainActivity : AppCompatActivity() { companion object { private val TAG = MainActivity::class.java.simpleName } private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) getRandomQuote() } private fun getRandomQuote() { } }
Jangan lupa untuk mengaktifkan ViewBinding terlebih dahulu pada build.gradle(module: app) dengan menambahkan kode berikut:
android { ... buildFeatures { viewBinding = true } }
- Selanjutnya kita mulai untuk mengimplementasikan LoopJ untuk mengambil data dari Web API seperti berikut:
private fun getRandomQuote() { binding.progressBar.visibility = View.VISIBLE val client = AsyncHttpClient() val url = "https://quote-api.dicoding.dev/random" client.get(url, object : AsyncHttpResponseHandler() { override fun onSuccess(statusCode: Int, headers: Array<Header>, responseBody: ByteArray) { // Jika koneksi berhasil } override fun onFailure(statusCode: Int, headers: Array<Header>, responseBody: ByteArray, error: Throwable) { // Jika koneksi gagal } }) }
Di sini terdapat ProgressBar yang ditampilkan untuk mengindikasikan proses loading data. Loading ini akan hilang ketika data sudah ditampilkan.
- Kemudian lihat kembali API untuk mengambil random quote.
Di sini terdapat 1 JSONObject yang berisi 4 data. Namun hanya 2 data yang diperlukan, yaitu data dengan key “en” dan “author”. Berikut ini adalah cara untuk mem-parsing data tersebut di dalam method onSuccess:
private fun getRandomQuote() { binding.progressBar.visibility = View.VISIBLE val client = AsyncHttpClient() val url = "https://quote-api.dicoding.dev/random" client.get(url, object : AsyncHttpResponseHandler() { override fun onSuccess(statusCode: Int, headers: Array<Header>, responseBody: ByteArray) { // Jika koneksi berhasil binding.progressBar.visibility = View.INVISIBLE val result = String(responseBody) Log.d(TAG, result) try { val responseObject = JSONObject(result) val quote = responseObject.getString("en") val author = responseObject.getString("author") binding.tvQuote.text = quote binding.tvAuthor.text = author } catch (e: Exception) { Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show() e.printStackTrace() } } override fun onFailure(statusCode: Int, headers: Array<Header>, responseBody: ByteArray, error: Throwable) { // Jika koneksi gagal binding.progressBar.visibility = View.INVISIBLE val errorMessage = when (statusCode) { 401 -> "$statusCode : Bad Request" 403 -> "$statusCode : Forbidden" 404 -> "$statusCode : Not Found" else -> "$statusCode : ${error.message}" } Toast.makeText(this@MainActivity, errorMessage, Toast.LENGTH_SHORT).show() } }) }
Data kemudian ditampilkan di TextView jika berhasil. Jika tidak, akan muncul Toast dan memberitahukan eror apa yang terjadi.
- Terakhir, karena menggunakan koneksi internet, kita perlu menambahkan permission di AndroidManifest seperti berikut:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="studio.afandi.myrandomquotes"> <uses-permission android:name="android.permission.INTERNET" /> <application ... </application> </manifest>
- Setelah semuanya selesai, jalankan aplikasi yang kita buat. Maka hasilnya akan menjadi seperti ini:
Selamat! kita sudah menerapkan LoopJ untuk mengakses Web API yang berbentuk satu JSONObject. Lalu, bagaimana jika datanya berbentuk JSONArray yang berisi banyak data? Kita lanjutkan di langkah berikutnya. Semangat!
Membuat List Quotes
- Buatlah Activity baru dengan cara klik kanan pada nama package → new → activity → empty activity. Kemudian beri nama ListQuotesActivity.
- Buka activit_list_quotes.xml, kemudian tambahkan RecyclerView dan ProgressBar di dalamnya seperti ini:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8p" tools:context=".ListQuotesActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/listQuotes" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" 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" /> </androidx.constraintlayout.widget.ConstraintLayout>
- Sebelum melangkah lebih jauh, buatlah terlebih dahulu layout untuk item RecyclerView dengan cara klik kanan pada folder layout → New → Layout resource file dan beri nama item_quote. Lalu buat desain seperti berikut:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/tvItem" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:minHeight="?android:attr/listPreferredItemHeightSmall" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:textAppearance="?android:attr/textAppearanceListItemSmall" tools:text="Quote" />
- Kemudian buat class baru untuk adapter RecyclerView dengan nama QuoteAdapter dan tuliskan kode berikut:
class QuoteAdapter(private val listReview: ArrayList<String>) : RecyclerView.Adapter<QuoteAdapter.ViewHolder>() { override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.item_quote, viewGroup, false) return ViewHolder(view) } override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { viewHolder.tvItem.text = listReview[position] } override fun getItemCount(): Int { return listReview.size } class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val tvItem: TextView = view.findViewById(R.id.tvItem) } }
- Kemudian buka ListQuotesActivity dan buat method getListQuotes untuk mengambil data list quotes.
class ListQuotesActivity : AppCompatActivity() { companion object { private val TAG = ListQuotesActivity::class.java.simpleName } private lateinit var binding: ActivityListQuotesBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityListQuotesBinding.inflate(layoutInflater) setContentView(binding.root) supportActionBar?.title = "List of Quotes" val layoutManager = LinearLayoutManager(this) binding.listQuotes.setLayoutManager(layoutManager) val itemDecoration = DividerItemDecoration(this, layoutManager.orientation) binding.listQuotes.addItemDecoration(itemDecoration) getListQuotes() } private fun getListQuotes() { binding.progressBar.visibility = View.VISIBLE val client = AsyncHttpClient() val url = "https://quote-api.dicoding.dev/list" client.get(url, object : AsyncHttpResponseHandler() { override fun onSuccess(statusCode: Int, headers: Array<Header>, responseBody: ByteArray) { // Jika koneksi berhasil binding.progressBar.visibility = View.INVISIBLE } override fun onFailure(statusCode: Int, headers: Array<Header>, responseBody: ByteArray, error: Throwable) { // Jika koneksi gagal binding.progressBar.visibility = View.INVISIBLE val errorMessage = when (statusCode) { 401 -> "$statusCode : Bad Request" 403 -> "$statusCode : Forbidden" 404 -> "$statusCode : Not Found" else -> "$statusCode : ${error.message}" } Toast.makeText(this@ListQuotesActivity, errorMessage, Toast.LENGTH_SHORT).show() } }) } }
- Tambahkan pula Button Navigation Up agar dapat kembali ke halaman beranda (home). Caranya tambahkan kode
supportActionBar?.setDisplayHomeAsUpEnabled(true)
di methodonCreate()
. Kemudian tambahkan fungsi baru yaituonSupportNavigateUp()
. Sehingga kode akhir dari file ListQoutesActivity adalah sebagai berikut :
class ListQuotesActivity : AppCompatActivity() { companion object { private val TAG = ListQuotesActivity::class.java.simpleName } private lateinit var binding: ActivityListQuotesBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_list_quotes) binding = ActivityListQuotesBinding.inflate(layoutInflater) setContentView(binding.root) supportActionBar?.title = "List of Quotes" supportActionBar?.setDisplayHomeAsUpEnabled(true) val layoutManager = LinearLayoutManager(this) binding.listQuotes.layoutManager = layoutManager val itemDecoration = DividerItemDecoration(this, layoutManager.orientation) binding.listQuotes.addItemDecoration(itemDecoration) getListQuotes() } private fun getListQuotes() { binding.progressBar.visibility = View.VISIBLE val client = AsyncHttpClient() val url = "https://quote-api.dicoding.dev/list" client.get(url, object : AsyncHttpResponseHandler(){ override fun onSuccess( statusCode: Int, headers: Array<out Header>, responseBody: ByteArray ) { binding.progressBar.visibility = View.INVISIBLE val listQuote = ArrayList<String>() val result = String(responseBody) Log.d(TAG, result) try { val jsonArray = JSONArray(result) for (i in 0 until jsonArray.length()) { val jsonObject = jsonArray.getJSONObject(i) val quote = jsonObject.getString("en") val author = jsonObject.getString("author") listQuote.add("\n$quote\n — $author\n") } val adapter = QuoteAdapter(listQuote) binding.listQuotes.adapter = adapter } catch (e: Exception) { Toast.makeText(this@ListQuotesActivity, e.message, Toast.LENGTH_SHORT).show() e.printStackTrace() } } override fun onFailure( statusCode: Int, headers: Array<out Header>, responseBody: ByteArray?, error: Throwable ) { binding.progressBar.visibility = View.INVISIBLE val errorMessage = when (statusCode) { 401 -> "$statusCode : Bad Request" 403 -> "$statusCode : Forbidden" 404 -> "$statusCode : Not Found" else -> "$statusCode : ${error.message}" } Toast.makeText(this@ListQuotesActivity, errorMessage, Toast.LENGTH_SHORT).show() } }) } override fun onSupportNavigateUp(): Boolean { onBackPressed() return true } }
- Sebelum menulis kode yang ada di onSuccess, lihat kembali API untuk mengambil list quotes.
Di sini kita lihat terdapat JSONArray yang berisi banyak JSON Object. Maka kita perlu melakukan perulangan untuk mendapatkan data tersebut dengan cara seperti ini:
override fun onSuccess(statusCode: Int, headers: Array<Header>, responseBody: ByteArray) { // Jika koneksi berhasil binding.progressBar.visibility = View.INVISIBLE val listQuote = ArrayList<String>() val result = String(responseBody) Log.d(TAG, result) try { val jsonArray = JSONArray(result) for (i in 0 until jsonArray.length()) { val jsonObject = jsonArray.getJSONObject(i) val quote = jsonObject.getString("en") val author = jsonObject.getString("author") listQuote.add("\n$quote\n — $author\n") } val adapter = QuoteAdapter(listQuote) binding.listQuotes.adapter = adapter } catch (e: Exception) { Toast.makeText(this@ListQuotesActivity, e.message, Toast.LENGTH_SHORT).show() e.printStackTrace() } }
- Kemudian pada MainActivity tambahkan aksi pada Button tersebut seperti ini:
class MainActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) getRandomQuote() binding.btnAllQuotes.setOnClickListener { startActivity(Intent(this@MainActivity, ListQuotesActivity::class.java)) } } ... }
- Setelah semuanya selesai, jalankan kembali aplikasi yang kita buat. Maka hasilnya akan menjadi seperti ini: