Adapter, bir sınıfın arayüzünü istemcinin beklediği başka bir arayüze dönüştüren yapısal bir tasarım desenidir. Mobil geliştirmede Adapter, en sık Android'de RecyclerView.Adapter ve iOS'te UITableViewDataSource olarak kullanılır. Google I/O (2024)'ya göre, RecyclerView Android uygulamalarının %95'inde listeleri görüntülemek için kullanılır ve her biri kendi Adapter uygulamasını gerektirir.
Önemli Noktalar
Adapter, arayüz uyumsuzluğu sorununu çözen yapısal bir desendir. Bir nesneyi (Adaptee), istemcinin beklediği arayüze (Target) sahip bir sınıfa (Adapter) sarar. İstemci, Target ile çalışır ve Adaptee'nin varlığından habersizdir. Bu, Wrapper deseninin bir çeşididir.
// Uyumsuz arayüze sahip mevcut sınıf
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Hedef arayüz (istemcinin beklediği)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — LegacyAuthApi'yi AuthService'e dönüştürür
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, eski API için login(credentials) çağrısını loginWithToken(token)'a dönüştürür. İstemci (ViewModel), AuthService arayüzü üzerinden çalışır ve altında en yeni Firebase Auth mu yoksa on yıllık eski bir API mi olduğunu bilmez. Bu, istemci kodunu değiştirmeden uygulamaların değiştirilmesine olanak tanır.
RecyclerView.Adapter, Android'de Adapter deseninin en yaygın uygulamasıdır. Verileri (nesne listesi), RecyclerView'in ekranda görüntülediği ViewHolder'lara dönüştürür. ListAdapter'ın (Android Architecture Components) tanıtılmasıyla, desen değişiklikleri canlandırmak için yerleşik fark hesaplama desteği kazandı.
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, verimli liste yeniden çizimi için DiffUtil ile ListAdapter kullanır. Veriler güncellendiğinde, DiffCallback eski ve yeni liste arasındaki farkı hesaplar ve RecyclerView yalnızca değişen öğeleri canlandırır — tüm listeyi yeniden çizmeden. Bu, manuel notifyDataSetChanged() gerektiren eski ListView'a göre önemli bir avantajdır.
UITableViewDataSource, aynı anda hem Adapter deseni hem de DataSource desenidir. Model verilerini tablo hücrelerine dönüştürür. iOS 13'ten itibaren Apple, DiffableDataSource'ı tanıttı — Android'deki ListAdapter gibi, değişiklikleri otomatik olarak hesaplayan ve güncellemeleri canlandıran modern bir alternatif.
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
}
}
// Modern sürüm: 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, DiffableDataSource ile veriler güncellendiğinde değişiklikleri otomatik olarak canlandırır: öğeler akıcı animasyonlarla görünür, kaybolur veya hareket eder. iOS'in DiffableDataSource'u ve Android'in ListAdapter'i farklı API'lerle aynı sorunu çözer ancak aynı mantıkla — fark hesaplama + otomatik animasyon.
Object Adapter kompozisyon kullanır: adaptör, Adaptee nesnesine bir referans içerir ve çağrıları ona devreder. Class Adapter kalıtım kullanır: adaptör, Adaptee'den miras alır ve aynı anda hedef arayüzü uygular. Java, Swift ve Kotlin'de, çoklu kalıtım eksikliği nedeniyle Class Adapter imkansızdır — yalnızca Object Adapter kalır.
| Özellik | Object Adapter | Class Adapter |
|---|---|---|
| Mekanizma | Kompozisyon (Adaptee'yi içerir) | Kalıtım (Adaptee'yi genişletir) |
| Esneklik | Daha yüksek — Adaptee çalışma zamanında değiştirilebilir | Daha düşük — Adaptee derleme zamanında sabittir |
| Bağlılık | Zayıf (arayüz aracılığıyla) | Güçlü (kalıtım aracılığıyla) |
| Alt sınıf uyarlaması | Evet — Adaptee herhangi bir alt sınıf olabilir | Hayır — yalnızca belirli bir üst sınıf |
| Kullanılabilirlik | Java, Kotlin, Swift, C++, C# | Yalnızca çoklu kalıtımlı diller (C++) |
Mobil geliştirmede yalnızca Object Adapter kullanılır. RecyclerView.Adapter, verilere ve LayoutInflater'a referanslar içerir, UITableViewDataSource bir öğe dizisi içerir. Kompozisyon, kalıtıma kıyasla kodu daha esnek ve test edilebilir hale getirir.
Adapter yalnızca listeler için kullanılmaz. Desenin uyumsuz bileşenleri entegre etme sorununu çözdüğü mobil geliştirme pratiğinden üç senaryoyu inceleyelim.
| Senaryo | Adaptee | Target | Adapter |
|---|---|---|---|
| Eski auth kütüphanesinin entegrasyonu | LegacyAuthLib (callback tabanlı) | AuthService (suspend) | LegacyAuthAdapter |
| JSON'ın yeni modele uyarlanması | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Push hizmetleri için birleşik arayüz | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Her Adaptör dönüştürme mantığını kapsüller: GsonAdapter, @SerializedName aracılığıyla JSON dizesini nesneye çevirir, PushServiceAdapter farklı platformlar için token kaydını soyutlar. İstemci (iş mantığı) birleşik bir arayüzle çalışır ve belirli uygulamaya bağlı değildir.
Adapter uygulamasındaki hatalar, liste yavaşlamalarına, bellek sızıntılarına ve veri güncelleme hatalarına yol açar. Üç yaygın sorunu inceleyelim.
onBindViewHolder, kaydırma sırasında her hücre için çağrılır. Bu yöntemde nesne oluşturma, JSON ayrıştırma veya veritabanı çağrısı kare düşüşlerine (frame drops) neden olur. Çözüm: tüm ağır hesaplamaları önceden yapın, adaptöre hazır verileri iletin. Görüntüler için önbellekleme kütüphaneleri kullanın — Glide, Coil, Kingfisher.
Her güncellemede notifyDataSetChanged() çağırmak tüm listeyi yeniden çizer, titremeye ve girişlerde odak kaybına neden olur. Çözüm: Android'de DiffUtil ile ListAdapter veya iOS'te DiffableDataSource kullanın. Fark hesaplaması, 1000 öğeye kadar listeler için <1 ms sürer ve akıcı animasyonlar sağlar.
Adaptör, RecyclerView/UITableView kadar yaşar. Adaptörde Bitmap veya büyük diziler depolamak, yapılandırma değişikliklerinde (ekran dönüşü, tema değişikliği) bellek sızıntılarına yol açar. Çözüm: adaptör yalnızca hafif veri modellerini (data class / struct) depolamalı ve ağır kaynakları ihtiyaç halinde ViewHolder aracılığıyla yüklemelidir.
Sıkça Sorulan Sorular
Adapter, Adaptee arayüzünü Target arayüzüne dönüştürür — istemci yeni bir etkileşim yöntemi elde eder. Proxy, orijinal nesneyle aynı arayüzü sağlar ancak erişim kontrolü, önbellekleme veya geç başlatma ekler. Proxy arayüzü değiştirmez, Adapter değiştirir.
RecyclerView.Adapter, verileri (örneğin, User listesi) RecyclerView'in görüntüleyebileceği bir ViewHolder'a dönüştürür. RecyclerView bir ViewHolder bekler, veriler List<User> biçimindedir — Adapter birini diğerine uyarlar. Ek olarak, Adapter, geri dönüşüm havuzu aracılığıyla ViewHolder yaşam döngüsünü yöneterek liste performansını artırır.
Object Adapter kompozisyon kullanır: Adaptee'ye bir referans içerir ve çağrıları ona devreder. Class Adapter çoklu kalıtım kullanır: Adaptee'den miras alır ve Target'ı uygular. Java, Kotlin ve Swift'te, çoklu sınıf kalıtımı eksikliği nedeniyle yalnızca Object Adapter kullanılabilir. Class Adapter yalnızca C++'da mümkündür.
Adapter, iki mevcut sınıfın birlikte çalışmasını sağlamak için bunlardan birinin arayüzünü dönüştürerek kullanılır. Facade, basit bir arayüz sağlayarak karmaşık bir alt sistemle etkileşimi basitleştirmek gerektiğinde kullanılır. Adapter uyumluluk için, Facade basitleştirme içindir.
Evet, closure'lar kullanarak. Tek yöntemli bir arayüz yerine bir işlev iletilebilir. Örneğin, Swift'te: let adapter = { (data: Data) -> [CellConfig] in /* dönüştürme */ }. Bu yaklaşıma işlevsel adaptör denir. Ancak, 2+ yöntem için bir protokol/arayüz tercih edilir — sözleşmeyi açık hale getirir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun