Android Kotlin #7: Menampilkan Gambar dengan Glide/Coil
Halo lagi, calon developer aplikasi wisata!
Di artikel sebelumnya kita sudah berhasil mengambil data kategori dari API. Sekarang kita akan naik level: menampilkan gambar untuk setiap tempat wisata. Di API Wisata kita, setiap tempat wisata punya properti imagePath yang berisi URL gambar (misal /uploads/gambar.jpg). Nah, tugas kita sekarang adalah menampilkan gambar-gambar itu di aplikasi Android.
Pilihan Library: Glide vs Coil
Ada dua library populer untuk menampilkan gambar dari internet:
- Glide: library mature (dewasa) yang sudah ada sejak lama, banyak digunakan, dan stabil [citation:2].
- Coil: library modern yang ditulis dengan Kotlin dan memanfaatkan Coroutine, lebih ringan [citation:1][citation:3].
Untuk project kita, kita akan pakai Glide karena lebih umum dan mudah dicari referensinya. Tapi nanti akan saya kasih catatan juga kalau mau pakai Coil.
Langkah 1: Tambahkan Dependency Glide
Buka file build.gradle (Module: app). Di bagian dependencies, tambahkan baris berikut [citation:2][citation:5]:
implementation 'com.github.bumptech.glide:glide:4.16.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
Klik Sync Now di pojok kanan atas.
Langkah 2: Update Model DaftarWisata
Kita perlu menambahkan field imagePath di model DaftarWisata (kalau belum ada). Buat file baru atau buka DaftarWisata.kt:
package com.example.wisataapp
data class DaftarWisata(
val id: Int,
val nama: String,
val deskripsi: String?,
val lokasi: String?,
val hargaTiket: Double?,
val kategoriId: Int,
val imagePath: String? // 👈 tambahkan ini
)
Langkah 3: Buat Layout untuk Item Wisata (item_wisata.xml)
Buat file layout baru: res/layout/item_wisata.xml. Isinya:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<!-- ImageView untuk foto -->
<ImageView
android:id="@+id/imageWisata"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder_image" />
<!-- Detail teks di sebelah kanan -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/textNama"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/textLokasi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginTop="4dp"
android:textColor="@android:color/darker_gray" />
<TextView
android:id="@+id/textHarga"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginTop="4dp"
android:textColor="@android:color/holo_green_dark" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Catatan: @drawable/placeholder_image adalah gambar sementara (placeholder) sebelum gambar asli dimuat. Kamu bisa buat file PNG kecil atau pakai vector asset dari Android Studio.
Cara buat placeholder: klik kanan res/drawable → New → Vector Asset, pilih ikon (misal ic_image). Atau download gambar polos dari internet.
Langkah 4: Buat Adapter untuk DaftarWisata (WisataAdapter.kt)
Buat class baru WisataAdapter.kt:
package com.example.wisataapp
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
class WisataAdapter(
private val listWisata: List<DaftarWisata>,
private val onItemClick: (DaftarWisata) -> Unit
) : RecyclerView.Adapter<WisataAdapter.WisataViewHolder>() {
class WisataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageWisata: ImageView = itemView.findViewById(R.id.imageWisata)
val textNama: TextView = itemView.findViewById(R.id.textNama)
val textLokasi: TextView = itemView.findViewById(R.id.textLokasi)
val textHarga: TextView = itemView.findViewById(R.id.textHarga)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WisataViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_wisata, parent, false)
return WisataViewHolder(view)
}
override fun onBindViewHolder(holder: WisataViewHolder, position: Int) {
val wisata = listWisata[position]
holder.textNama.text = wisata.nama
holder.textLokasi.text = wisata.lokasi ?: "Lokasi tidak diketahui"
holder.textHarga.text = if (wisata.hargaTiket != null)
"Rp ${wisata.hargaTiket}" else "Gratis"
// Load gambar dengan Glide
Glide.with(holder.itemView.context)
.load(wisata.imagePath) // URL gambar
.placeholder(R.drawable.placeholder_image) // tampil saat loading
.error(R.drawable.error_image) // tampil jika gagal
.transform(CenterCrop(), RoundedCorners(8)) // biar rapi
.into(holder.imageWisata)
// Klik listener
holder.itemView.setOnClickListener {
onItemClick(wisata)
}
}
override fun getItemCount(): Int = listWisata.size
}
Penjelasan method Glide [citation:5][citation:9]:
with(context): menyediakan context.load(url): URL gambar (bisa String atau URI).placeholder(): gambar sementara sebelum gambar asli dimuat [citation:2].error(): gambar alternatif kalau gagal load [citation:2].transform(): memanipulasi gambar (misal crop, rounded corners).into(imageView): menaruh hasil ke ImageView.
Langkah 5: Update MainActivity untuk Mengambil Daftar Wisata
Sekarang kita ubah MainActivity.kt untuk mengambil data wisata (bukan kategori). Kita asumsikan API punya endpoint /api/wisata yang mengembalikan daftar wisata lengkap dengan gambar.
Pertama, update ApiService.kt dengan endpoint baru:
interface ApiService {
@GET("api/kategori")
fun getKategori(): Call<List<KategoriWisata>>
@GET("api/wisata")
fun getWisata(): Call<List<DaftarWisata>>
}
Kemudian di MainActivity.kt, ganti dengan kode berikut:
package com.example.wisataapp
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: WisataAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerViewKategori) // kita pakai RecyclerView yang sama
recyclerView.layoutManager = LinearLayoutManager(this)
fetchWisata()
}
private fun fetchWisata() {
lifecycleScope.launch {
try {
val response = RetrofitInstance.api.getWisata().execute()
if (response.isSuccessful) {
val listWisata = response.body()
if (listWisata != null) {
adapter = WisataAdapter(listWisata) { wisata ->
Toast.makeText(this@MainActivity, "Memilih: ${wisata.nama}", Toast.LENGTH_SHORT).show()
// nanti bisa intent ke detail activity
}
recyclerView.adapter = adapter
}
} else {
Toast.makeText(this@MainActivity, "Gagal mengambil data: ${response.code()}", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this@MainActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
Langkah 6: Jalankan Aplikasi
Pastikan API Wisata berjalan (di Visual Studio) dan endpoint /api/wisata mengembalikan data dengan imagePath. Jalankan aplikasi Android.
Jika berhasil, RecyclerView akan menampilkan daftar wisata dengan gambar, nama, lokasi, dan harga. Gambar akan muncul setelah beberapa saat (tergantung koneksi).
- Pastikan URL gambar lengkap (misal
http://10.0.2.2:7000/uploads/namafile.jpg). - Pastikan file gambar benar-benar ada di folder
wwwroot/uploadsdi server. - Di Logcat, filter dengan "Glide" untuk melihat error detail.
- Coba akses URL gambar langsung dari browser emulator (buka browser di emulator, ketik URL-nya).
Bonus: Membuat Placeholder dan Error Image
Placeholder dan error image bisa dibuat dengan vector asset:
- Klik kanan res/drawable → New → Vector Asset.
- Pilih ikon image (untuk placeholder) dan broken image (untuk error).
- Beri nama
ic_placeholder.xmldanic_broken_image.xml.
Lalu di kode Glide, gunakan:
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_broken_image)
Alternatif dengan Coil (untuk yang penasaran)
Coil adalah library modern yang ditulis dengan Kotlin. Cara pakainya mirip, tapi dependency-nya berbeda [citation:1][citation:3]:
// build.gradle
implementation("io.coil-kt:coil:2.6.0")
// di adapter (dalam onBindViewHolder)
holder.imageWisata.load(wisata.imagePath) {
placeholder(R.drawable.placeholder_image)
error(R.drawable.error_image)
transformations(CenterCrop(), RoundedCorners(8f))
}
Coil punya extension function load yang bisa langsung dipanggil di ImageView. Lebih ringkas dan Kotlin-idiomatic.
Selamat! Sekarang Aplikasi Bisa Menampilkan Gambar
Dengan Glide (atau Coil), aplikasi Android-mu sekarang bisa menampilkan foto-foto tempat wisata dari server. Ini bikin aplikasi jauh lebih menarik dan informatif.
Rangkuman
- Glide/Coil: library untuk loading gambar dari internet dengan fitur caching, placeholder, dan error handling.
- Placeholder: gambar sementara saat loading.
- Error image: gambar alternatif jika gagal load.
- Transformations: mengubah tampilan gambar (crop, rounded corners, dll).
Selanjutnya?
Di artikel berikutnya (Android Kotlin #8: Detail Screen – Berpindah Halaman dan Mengirim Data), kita akan membuat halaman detail tempat wisata. Saat item diklik, akan membuka activity/fragment baru yang menampilkan informasi lengkap dan gambar besar. Sampai jumpa!
Ditulis oleh Kakak programmer yang dulu juga pusing ngurusin gambar. Kalau ada pertanyaan, tulis di komentar ya!