Adapter — bir sinfin interfeysini müştərinin gözlədiyi başqa interfeysə çevirən struktur layihə nümunəsidir. Mobil inkişafda Adapter ən çox Android üçün RecyclerView.Adapter və iOS üçün UITableViewDataSource-da istifadə olunur. Google I/O (2024) məlumatlarına görə, RecyclerView Android tətbiqlərinin 95%-də siyahıları göstərmək üçün istifadə olunur və hər biri öz Adapter tətbiqini tələb edir.
Əsas məqamlar
Adapter — interfeys uyğunsuzluğu problemini həll edən struktur nümunədir. Bir obyekti (Adaptee) müştərinin gözlədiyi interfeysə (Target) malik sinfə (Adapter) bükür. Müştəri Adaptee-nin mövcudluğundan xəbərsiz Target ilə işləyir. Bu Wrapper nümunəsinin bir növüdür.
// Uyğun olmayan interfeysə malik mövcud sinif
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Hədəf interfeys (müştərinin gözlədiyi)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — LegacyAuthApi-ni AuthService-ə çevirir
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 köhnə API üçün login(credentials) çağırışını loginWithToken(token)-a çevirir. Müştəri (ViewModel) AuthService interfeysi vasitəsilə işləyir və onun altında ən yeni Firebase Auth və ya on illik köhnə API olduğunu bilmir. Bu, müştəri kodunu dəyişmədən tətbiqləri əvəz etməyə imkan verir.
RecyclerView.Adapter — Android-də Adapter nümunəsinin ən geniş yayılmış tətbiqidir. Məlumatları (obyekt siyahısını) RecyclerView-in ekranda göstərdiyi ViewHolder-lərə çevirir. ListAdapter-in (Android Architecture Components) meydana gəlməsi ilə nümunə dəyişiklik animasiyası üçün daxili diff hesablama dəstəyi aldı.
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 səmərəli siyahı yenidən çəkilməsi üçün DiffUtil ilə ListAdapter-dən istifadə edir. Məlumatlar yeniləndikdə, DiffCallback köhnə və yeni siyahı arasındakı fərqi hesablayır və RecyclerView yalnız dəyişdirilmiş elementləri animasiya edir — bütün siyahını yenidən çəkmədən. Bu, əl ilə notifyDataSetChanged() tələb edən köhnə ListView-dən əsas üstünlükdür.
UITableViewDataSource — eyni zamanda həm Adapter, həm də DataSource nümunəsidir. Model məlumatlarını cədvəl hüceyrələrinə çevirir. iOS 13-dən etibarən Apple DiffableDataSource-u təqdim etdi — Android-də ListAdapter kimi dəyişiklikləri avtomatik hesablayan və yeniləmələri animasiya edən müasir əvəz.
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
}
}
// Müasir versiya: 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 ilə məlumatlar yeniləndikdə dəyişiklikləri avtomatik animasiya edir: elementlər hamar animasiyalarla görünür, yox olur və ya hərəkət edir. iOS DiffableDataSource və Android ListAdapter eyni tapşırığı müxtəlif API-lərlə, lakin eyni məntiqlə — diff hesablama + avtomatik animasiya ilə həll edir.
Object Adapter kompozisiyadan istifadə edir: adapter Adaptee obyektinə istinad saxlayır və ona çağırışları həvalə edir. Class Adapter mirasdan istifadə edir: adapter Adaptee-dən miras alır və eyni zamanda hədəf interfeysi tətbiq edir. Java, Swift və Kotlin-də Class Adapter çoxlu mirasın olmaması səbəbindən mümkün deyil — yalnız Object Adapter qalır.
| Xüsusiyyət | Object Adapter | Class Adapter |
|---|---|---|
| Mexanizm | Kompozisiya (Adaptee-ni ehtiva edir) | Miras (extends Adaptee) |
| Çeviklik | Daha yüksək — Adaptee runtime-da dəyişdirilə bilər | Daha aşağı — Adaptee kompilyasiya mərhələsində sabitdir |
| Bağlılıq | Zəif (interfeys vasitəsilə) | Güclü (miras vasitəsilə) |
| Alt siniflərin adaptasiyası | Bəli — Adaptee istənilən alt sinif ola bilər | Xeyr — yalnız konkret super sinif |
| Mövcudluq | Java, Kotlin, Swift, C++, C# | Yalnız çoxlu miraslı dillər (C++) |
Mobil inkişafda yalnız Object Adapter istifadə olunur. RecyclerView.Adapter məlumatlara və LayoutInflater-ə istinadlar saxlayır, UITableViewDataSource elementlər massivi saxlayır. Kompozisiya kodu mirasla müqayisədə daha çevik və test edilə bilən edir.
Adapter təkcə siyahılar üçün tətbiq edilmir. Mobil inkişaf təcrübəsindən üç ssenarini nəzərdən keçirək, burada nümunə uyğun olmayan komponentlərin inteqrasiyası problemini həll edir.
| Ssenari | Adaptee | Target | Adapter |
|---|---|---|---|
| Köhnə auth kitabxanasının inteqrasiyası | LegacyAuthLib (callback-based) | AuthService (suspend) | LegacyAuthAdapter |
| JSON-un yeni modelə uyğunlaşdırılması | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Push xidmətləri üçün vahid interfeys | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Hər bir Adapter çevirmə məntiqini kapsullaşdırır: GsonAdapter JSON sətrini @SerializedName vasitəsilə obyektə çevirir, PushServiceAdapter müxtəlif platformalar üçün token qeydiyyatını abstraksiya edir. Müştəri (biznes məntiqi) vahid interfeyslə işləyir və konkret tətbiqdən asılı deyil.
Adapter tətbiqində səhvlər siyahıların ləngiməsinə, yaddaş sızmalarına və məlumat yeniləmə xətalarına səbəb olur. Üç ümumi problemi nəzərdən keçirək.
onBindViewHolder sürüşdürmə zamanı hər bir hüceyrə üçün çağırılır. Bu metoddakı obyekt yaratma, JSON pars etmə və ya verilənlər bazası sorğuları siyahının sıçramasına (frame drops) səbəb olur. Həll yolu: bütün ağır hesablamaları əvvəlcədən aparmaq, adapterə artıq hazır məlumatları ötürmək. Şəkillər üçün keşləmə kitabxanalarından istifadə edin — Glide, Coil, Kingfisher.
Hər yeniləmədə notifyDataSetChanged() çağırmaq bütün siyahını yenidən çəkir, yanıb-sönməyə və giriş sahələrində fokus itkisinə səbəb olur. Həll yolu: Android-də DiffUtil ilə ListAdapter və ya iOS-da DiffableDataSource istifadə edin. Diff hesablama 1000 elementə qədər siyahılar üçün <1 ms çəkir və hamar animasiyalar təmin edir.
Adapter RecyclerView/UITableView qədər yaşayır. Adapterdə Bitmap və ya böyük massivlərin saxlanması konfiqurasiya dəyişikliyi (ekranın döndərilməsi, mövzu dəyişikliyi) zamanı yaddaş sızmalarına səbəb olur. Həll yolu: adapter yalnız yüngül məlumat modellərini (data class / struct) saxlamalı, ağır resursları isə ViewHolder vasitəsilə tələb əsasında yükləməlidir.
Tez-tez verilən suallar
Adapter Adaptee interfeysini Target interfeysinə çevirir — müştəri yeni qarşılıqlı əlaqə üsulu alır. Proxy orijinal obyektlə eyni interfeysi təqdim edir, lakin giriş nəzarəti, keşləmə və ya tənbəl inisializasiya əlavə edir. Proxy interfeysi dəyişmir, Adapter — dəyişir.
RecyclerView.Adapter məlumatları (məsələn, User siyahısını) RecyclerView-in göstərə biləcəyi ViewHolder-a çevirir. RecyclerView ViewHolder gözləyir, məlumatlar List<User> formatındadır — Adapter birini digərinə uyğunlaşdırır. Əlavə olaraq, Adapter ViewHolder-ın həyat dövrünü təkrar istifadə hovuzu vasitəsilə idarə edərək siyahının performansını artırır.
Object Adapter kompozisiyadan istifadə edir: Adaptee-yə istinad saxlayır və ona çağırışları həvalə edir. Class Adapter çoxlu mirasdan istifadə edir: Adaptee-dən miras alır və Target-i tətbiq edir. Java, Kotlin və Swift-də siniflərin çoxlu mirasının olmaması səbəbindən yalnız Object Adapter mövcuddur. Class Adapter yalnız C++-da mümkündür.
Adapter iki mövcud sinfin birlikdə işləməsini təmin etmək üçün onlardan birinin interfeysini çevirərkən istifadə olunur. Facade — mürəkkəb alt sistemlə qarşılıqlı əlaqəni sadələşdirmək, sadə interfeys təmin etmək üçün istifadə olunur. Adapter — uyğunluq üçün, Facade — sadələşdirmə üçün.
Bəli, bağlanmalar (closures) vasitəsilə. Bir metodlu interfeys əvəzinə funksiya ötürmək olar. Məsələn, Swift-də: let adapter = { (data: Data) -> [CellConfig] in /* çevirmə */ }. Belə yanaşma functional adapter adlanır. Lakin 2+ metod üçün protokol/interfeys üstünlük təşkil edir — müqaviləni aşkar edir.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun