Adapter — adalah pola desain struktural yang mengubah antarmuka suatu kelas ke antarmuka lain yang diharapkan oleh klien. Dalam pengembangan mobile, Adapter paling sering digunakan di RecyclerView.Adapter untuk Android dan UITableViewDataSource untuk iOS. Menurut data Google I/O (2024), RecyclerView digunakan di 95% aplikasi Android untuk menampilkan daftar, dan masing-masing memerlukan implementasi Adapter sendiri.
Poin Utama
Adapter — pola struktural yang memecahkan masalah ketidakcocokan antarmuka. Ia membungkus satu objek (Adaptee) ke dalam kelas (Adapter) dengan antarmuka yang diharapkan klien (Target). Klien bekerja dengan Target, tanpa mengetahui keberadaan Adaptee. Ini adalah varian dari pola Wrapper.
// Kelas yang ada dengan antarmuka yang tidak kompatibel
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Antarmuka target (yang diharapkan klien)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — mengubah LegacyAuthApi menjadi AuthService
class LegacyAuthAdapter(
private val legacyApi: LegacyAuthApi
) : AuthService {
override suspend fun login(credentials: Credentials): Result {
val token = "${credentials.login}:${credentials.password}"
.encodeToByteArray().let { Base64.encodeToString(it) }
val response = legacyApi.loginWithToken(token)
return if (response["status"] == "ok") {
Result.success(User(response["user_id"] as Int))
} else {
Result.failure(AuthException("Login failed"))
}
}
}LegacyAuthAdapter mengubah panggilan login(credentials) menjadi loginWithToken(token) untuk API lama. Klien (ViewModel) bekerja melalui antarmuka AuthService dan tidak tahu apakah di bawahnya ada Firebase Auth terbaru atau API lama berusia sepuluh tahun. Ini memungkinkan penggantian implementasi tanpa mengubah kode klien.
RecyclerView.Adapter — implementasi paling umum dari pola Adapter di Android. Ia mengubah data (daftar objek) menjadi ViewHolder yang ditampilkan RecyclerView di layar. Dengan munculnya ListAdapter (Android Architecture Components), pola tersebut mendapat dukungan bawaan untuk perhitungan diff untuk animasi perubahan.
class UserAdapter(
private val onItemClick: (User) -> Unit
) : ListAdapter (DiffCallback()) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): UserViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return UserViewHolder(view)
}
override fun onBindViewHolder(
holder: UserViewHolder,
position: Int
) {
holder.bind(getItem(position), onItemClick)
}
class UserViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
private val tvName = itemView.findViewById(R.id.tvName)
fun bind(user: User, click: (User) -> Unit) {
tvName.text = user.name
itemView.setOnClickListener { click(user) }
}
}
private class DiffCallback : DiffUtil.ItemCallback () {
override fun areItemsTheSame(old: User, new: User) = old.id == new.id
override fun areContentsTheSame(old: User, new: User) = old == new
}
} UserAdapter menggunakan ListAdapter dengan DiffUtil untuk menggambar ulang daftar secara efisien. Saat data diperbarui, DiffCallback menghitung perbedaan antara daftar lama dan baru, dan RecyclerView hanya menganimasikan elemen yang berubah — tanpa menggambar ulang seluruh daftar. Ini adalah keunggulan utama dibandingkan ListView lama yang memerlukan notifyDataSetChanged() manual.
UITableViewDataSource — adalah pola Adapter dan DataSource sekaligus. Ia mengubah data model menjadi sel tabel. Sejak iOS 13, Apple memperkenalkan DiffableDataSource — pengganti modern yang, seperti ListAdapter di Android, secara otomatis menghitung perubahan dan menganimasikan pembaruan.
class UserTableViewAdapter: NSObject, UITableViewDataSource {
var items: [User] = []
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: "UserCell", for: indexPath)
let user = items[indexPath.row]
cell.textLabel?.text = user.name
return cell
}
}
// Versi modern: DiffableDataSource
enum Section { case main }
class ModernUserAdapter {
var dataSource: UITableViewDiffableDataSource<Section, User>!
func configure(for tableView: UITableView) {
dataSource = UITableViewDiffableDataSource(
tableView: tableView
) { tableView, indexPath, user in
let cell = tableView.dequeueReusableCell(
withIdentifier: "UserCell", for: indexPath)
cell.textLabel?.text = user.name
return cell
}
}
func update(with users: [User]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, User>()
snapshot.appendSections([.main])
snapshot.appendItems(users)
dataSource.apply(snapshot, animatingDifferences: true)
}
}ModernUserAdapter dengan DiffableDataSource secara otomatis menganimasikan perubahan saat data diperbarui: elemen muncul, menghilang, atau berpindah dengan animasi yang halus. DiffableDataSource iOS dan ListAdapter Android menyelesaikan tugas yang sama dengan API berbeda, tetapi dengan logika yang sama — perhitungan diff + animasi otomatis.
Object Adapter menggunakan komposisi: adapter berisi referensi ke objek Adaptee dan mendelegasikan panggilan kepadanya. Class Adapter menggunakan pewarisan: adapter mewarisi Adaptee dan secara bersamaan mengimplementasikan antarmuka target. Di Java, Swift, dan Kotlin, Class Adapter tidak mungkin karena tidak adanya pewarisan berganda — hanya Object Adapter yang tersisa.
| Karakteristik | Object Adapter | Class Adapter |
|---|---|---|
| Mekanisme | Komposisi (berisi Adaptee) | Pewarisan (extends Adaptee) |
| Fleksibilitas | Lebih tinggi — Adaptee dapat diubah saat runtime | Lebih rendah — Adaptee tetap saat kompilasi |
| Keterikatan | Lemah (melalui antarmuka) | Kuat (melalui pewarisan) |
| Adaptasi subkelas | Ya — Adaptee bisa berupa subkelas apa pun | Tidak — hanya superkelas konkret |
| Ketersediaan | Java, Kotlin, Swift, C++, C# | Hanya bahasa dengan pewarisan berganda (C++) |
Dalam pengembangan mobile, hanya Object Adapter yang digunakan. RecyclerView.Adapter berisi referensi ke data dan LayoutInflater, UITableViewDataSource berisi array elemen. Komposisi membuat kode lebih fleksibel dan mudah diuji dibandingkan dengan pewarisan.
Adapter tidak hanya diterapkan untuk daftar. Mari kita lihat tiga skenario dari praktik pengembangan mobile di mana pola tersebut memecahkan masalah integrasi komponen yang tidak kompatibel.
| Skenario | Adaptee | Target | Adapter |
|---|---|---|---|
| Integrasi pustaka auth lama | LegacyAuthLib (callback-based) | AuthService (suspend) | LegacyAuthAdapter |
| Adaptasi JSON ke model baru | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Antarmuka seragam untuk layanan push | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Setiap Adapter mengenkapsulasi logika transformasi: GsonAdapter menerjemahkan string JSON menjadi objek melalui @SerializedName, PushServiceAdapter mengabstraksi pendaftaran token untuk berbagai platform. Klien (logika bisnis) bekerja dengan antarmuka seragam dan tidak bergantung pada implementasi tertentu.
Kesalahan dalam implementasi Adapter menyebabkan daftar melambat, kebocoran memori, dan bug pembaruan data. Mari kita bahas tiga masalah umum.
onBindViewHolder dipanggil untuk setiap sel saat scrolling. Pembuatan objek, parsing JSON, atau query database di metode ini menyebabkan daftar tersendat (frame drops). Solusi: lakukan semua perhitungan berat sebelumnya, berikan data yang sudah siap ke adapter. Untuk gambar, gunakan pustaka dengan caching — Glide, Coil, Kingfisher.
Memanggil notifyDataSetChanged() pada setiap pembaruan menggambar ulang seluruh daftar, menyebabkan kedipan dan kehilangan fokus pada input. Solusi: gunakan ListAdapter dengan DiffUtil di Android atau DiffableDataSource di iOS. Perhitungan diff memakan waktu <1 ms untuk daftar hingga 1000 elemen dan memastikan animasi yang halus.
Adapter hidup selama RecyclerView/UITableView. Menyimpan Bitmap atau array besar di adapter menyebabkan kebocoran memori saat perubahan konfigurasi (rotasi layar, perubahan tema). Solusi: adapter harus menyimpan hanya model data ringan (data class / struct), dan memuat sumber daya berat melalui ViewHolder sesuai permintaan.
Pertanyaan yang Sering Diajukan
Adapter mengubah antarmuka Adaptee menjadi antarmuka Target — klien mendapatkan cara interaksi baru. Proxy menyediakan antarmuka yang sama dengan objek asli, tetapi menambahkan kontrol akses, caching, atau inisialisasi malas. Proxy tidak mengubah antarmuka, Adapter — mengubah.
RecyclerView.Adapter mengubah data (misalnya, daftar User) menjadi ViewHolder yang dapat ditampilkan RecyclerView. RecyclerView mengharapkan ViewHolder, data dalam format List<User> — Adapter menyesuaikan satu dengan yang lain. Selain itu, Adapter mengelola siklus hidup ViewHolder melalui recycle pool, meningkatkan kinerja daftar.
Object Adapter menggunakan komposisi: berisi referensi ke Adaptee dan mendelegasikan panggilan kepadanya. Class Adapter menggunakan pewarisan berganda: mewarisi Adaptee dan mengimplementasikan Target. Di Java, Kotlin, dan Swift, hanya Object Adapter yang tersedia karena tidak adanya pewarisan berganda kelas. Class Adapter hanya mungkin di C++.
Adapter digunakan ketika dua kelas yang ada perlu bekerja sama dengan mengubah antarmuka salah satunya. Facade — ketika interaksi dengan subsistem kompleks perlu disederhanakan dengan menyediakan antarmuka sederhana. Adapter — untuk kompatibilitas, Facade — untuk penyederhanaan.
Ya, menggunakan penutupan (closures). Alih-alih antarmuka dengan satu metode, fungsi dapat diteruskan. Misalnya, di Swift: let adapter = { (data: Data) -> [CellConfig] in /* transformasi */ }. Pendekatan ini disebut functional adapter. Namun untuk 2+ metode, protokol/antarmuka tetap lebih disukai — membuat kontrak menjadi eksplisit.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga