Adapter — egy strukturális tervezési minta, amely egy osztály interfészét egy másik, az ügyfél által várt interfésszé alakítja. Mobilfejlesztésben az Adaptert leggyakrabban az Android rendszerben a RecyclerView.Adapter, iOS rendszerben a UITableViewDataSource használja. A Google I/O (2024) adatai szerint a RecyclerView az Android alkalmazások 95%-ában használatos listák megjelenítésére, és mindegyik saját Adapter implementációt igényel.
Főbb pontok
Adapter — strukturális minta, amely megoldja az interfészek összeférhetetlenségének problémáját. Becsomagol egy objektumot (Adaptee) egy osztályba (Adapter) az ügyfél által várt interfésszel (Target). Az ügyfél a Target-tel dolgozik, nem tudva az Adaptee létezéséről. Ez a Wrapper minta egy változata.
// Meglévő osztály összeférhetetlen interfésszel
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Cél interfész (amit az ügyfél vár)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — a LegacyAuthApi-t AuthService-ré alakítja
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 a login(credentials) hívást loginWithToken(token)-ké alakítja a régi API-hoz. Az ügyfél (ViewModel) az AuthService interfészen keresztül dolgozik, és nem tudja, hogy alatta a legújabb Firebase Auth vagy egy tíz éves régi API van. Ez lehetővé teszi az implementációk cseréjét az ügyfélkód megváltoztatása nélkül.
RecyclerView.Adapter — az Adapter minta leggyakoribb implementációja Androidban. Az adatokat (objektumok listáját) ViewHolder-okké alakítja, amelyeket a RecyclerView megjelenít a képernyőn. A ListAdapter (Android Architecture Components) megjelenésével a minta beépített támogatást kapott a különbségszámításhoz a változások animálásához.
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 ListAdapter-t használ DiffUtil-lel a lista hatékony újrarajzolásához. Amikor az adatok frissülnek, a DiffCallback kiszámítja a különbséget a régi és az új lista között, és a RecyclerView csak a megváltozott elemeket animálja — a teljes lista újrarajzolása nélkül. Ez kulcsfontosságú előny a régebbi ListView-vel szemben, amely manuális notifyDataSetChanged()-et igényelt.
UITableViewDataSource — egyszerre az Adapter és a DataSource minta is. A modell adatait táblázatcellákká alakítja. Az iOS 13-tól kezdve az Apple bevezette a DiffableDataSource-ot — egy modern helyettesítőt, amely az Android ListAdapter-hez hasonlóan automatikusan kiszámítja a változásokat és animálja a frissítéseket.
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 verzió: 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-szal automatikusan animálja a változásokat az adatok frissítésekor: az elemek sima animációkkal jelennek meg, tűnnek el vagy mozognak. Az iOS DiffableDataSource és az Android ListAdapter ugyanazt a feladatot oldja meg különböző API-kkal, de azonos logikával — különbségszámítás + automatikus animáció.
Object Adapter kompozíciót használ: az adapter referenciát tartalmaz az Adaptee objektumra, és delegálja neki a hívásokat. Class Adapter öröklést használ: az adapter örökli az Adaptee-t, és egyidejűleg implementálja a cél interfészt. Java, Swift és Kotlin nyelven a Class Adapter lehetetlen a többszörös öröklés hiánya miatt — csak az Object Adapter marad.
| Jellemző | Object Adapter | Class Adapter |
|---|---|---|
| Mechanizmus | Kompozíció (tartalmazza az Adaptee-t) | Öröklés (extends Adaptee) |
| Rugalmasság | Magasabb — Adaptee futásidőben változtatható | Alacsonyabb — Adaptee fordításkor rögzített |
| Kapcsoltság | Gyenge (interfészen keresztül) | Erős (öröklésen keresztül) |
| Alosztályok adaptálása | Igen — Adaptee bármely alosztály lehet | Nem — csak konkrét szuperosztály |
| Elérhetőség | Java, Kotlin, Swift, C++, C# | Csak többszörös örökléssel rendelkező nyelvek (C++) |
A mobilfejlesztésben csak Object Adapter használatos. A RecyclerView.Adapter referenciákat tartalmaz az adatokra és a LayoutInflater-re, a UITableViewDataSource elemek tömbjét tartalmazza. A kompozíció rugalmasabbá és tesztelhetőbbé teszi a kódot az örökléshez képest.
Adapter nem csak listákra alkalmazható. Vizsgáljunk meg három forgatókönyvet a mobilfejlesztés gyakorlatából, ahol a minta megoldja az összeférhetetlen komponensek integrálásának problémáját.
| Forgatókönyv | Adaptee | Target | Adapter |
|---|---|---|---|
| Régi auth könyvtár integrálása | LegacyAuthLib (callback-based) | AuthService (suspend) | LegacyAuthAdapter |
| JSON adaptálása új modellhez | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Egységes interfész push szolgáltatásokhoz | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Minden Adapter beágyazza az átalakítási logikát: a GsonAdapter a JSON karakterláncot objektummá alakítja a @SerializedName segítségével, a PushServiceAdapter elvonatkoztatja a token regisztrációt különböző platformokhoz. Az ügyfél (üzleti logika) egységes interfésszel dolgozik, és nem függ egy konkrét implementációtól.
Hibák az Adapter implementálásakor a listák lelassulásához, memóriaszivárgáshoz és adatfrissítési hibákhoz vezetnek. Vizsgáljunk meg három gyakori problémát.
Az onBindViewHolder minden cellához meghívódik görgetéskor. Objektumok létrehozása, JSON elemzés vagy adatbázis lekérdezések ebben a metódusban a lista akadozásához (frame drops) vezetnek. Megoldás: végezze el az összes nehéz számítást előre, továbbítsa a már elkészített adatokat az adapternek. Képekhez használjon gyorsítótárazó könyvtárakat — Glide, Coil, Kingfisher.
A notifyDataSetChanged() minden frissítéskor történő meghívása újrarajzolja a teljes listát, villogást és fókuszvesztést okozva a beviteli mezőkön. Megoldás: használjon ListAdapter-t DiffUtil-lel Androidban vagy DiffableDataSource-t iOS-ben. A különbségszámítás <1 ms-ig tart 1000 elemig terjedő listák esetén, és sima animációkat biztosít.
Az adapter addig él, amíg a RecyclerView/UITableView. Bitmap vagy nagy tömbök tárolása az adapterben memóriaszivárgáshoz vezet konfigurációváltáskor (képernyő forgatása, téma váltása). Megoldás: az adapter csak könnyű adatmodelleket (data class / struct) tároljon, a nehéz erőforrásokat pedig a ViewHolder-en keresztül igény szerint töltse be.
Gyakran Ismételt Kérdések
Adapter átalakítja az Adaptee interfészét a Target interfészévé — az ügyfél új interakciós módot kap. Proxy ugyanazt az interfészt nyújtja, mint az eredeti objektum, de hozzáférés-vezérlést, gyorsítótárazást vagy lusta inicializálást ad hozzá. A Proxy nem változtatja meg az interfészt, az Adapter — igen.
RecyclerView.Adapter az adatokat (pl. User lista) ViewHolder-ré alakítja, amelyet a RecyclerView megjeleníthet. A RecyclerView ViewHolder-t vár, az adatok List<User> formátumban vannak — az Adapter az egyiket a másikhoz igazítja. Ezenkívül az Adapter kezeli a ViewHolder életciklusát az újrahasznosítási poolon keresztül, javítva a lista teljesítményét.
Object Adapter kompozíciót használ: referenciát tartalmaz az Adaptee-ra, és delegálja neki a hívásokat. Class Adapter többszörös öröklést használ: örökli az Adaptee-t és implementálja a Target-et. Java, Kotlin és Swift nyelveken csak az Object Adapter érhető el az osztályok többszörös öröklésének hiánya miatt. A Class Adapter csak C++-ban lehetséges.
Adapter akkor használatos, amikor két meglévő osztálynak kell együttműködnie az egyik interfészének átalakításával. Facade — amikor egy összetett alrendszerrel való interakciót kell leegyszerűsíteni egy egyszerű interfész biztosításával. Adapter — kompatibilitásért, Facade — egyszerűsítésért.
Igen, lezárások (closures) segítségével. Egy metódussal rendelkező interfész helyett átadható egy függvény. Például Swift-ben: let adapter = { (data: Data) -> [CellConfig] in /* átalakítás */ }. Ezt a megközelítést funkcionális adapternek nevezik. 2+ metódus esetén azonban a protokoll/interfész továbbra is előnyösebb — egyértelművé teszi a szerződést.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is