Adapter ist ein strukturelles Entwurfsmuster, das die Schnittstelle einer Klasse in eine andere, vom Client erwartete Schnittstelle umwandelt. In der mobilen Entwicklung wird Adapter am häufigsten in RecyclerView.Adapter für Android und UITableViewDataSource für iOS verwendet. Laut Google I/O (2024) wird RecyclerView in 95% der Android-Apps zur Anzeige von Listen verwendet, und jede benötigt eine eigene Adapter-Implementierung.
Wichtige Punkte
Adapter ist ein strukturelles Muster, das das Problem der Schnittstelleninkompatibilität löst. Es kapselt ein Objekt (Adaptee) in einer Klasse (Adapter) mit der vom Client erwarteten Schnittstelle (Target). Der Client arbeitet mit Target, ohne von der Existenz des Adaptee zu wissen. Dies ist eine Variante des Wrapper-Patterns.
// Vorhandene Klasse mit inkompatibler Schnittstelle
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Zielschnittstelle (was der Client erwartet)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — wandelt LegacyAuthApi in AuthService um
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 wandelt den login(credentials)-Aufruf in loginWithToken(token) für die alte API um. Der Client (ViewModel) arbeitet über die AuthService-Schnittstelle und weiß nicht, ob darunter das neueste Firebase Auth oder eine jahrzehntealte Legacy-API liegt. Dies ermöglicht den Austausch von Implementierungen ohne Änderung des Client-Codes.
RecyclerView.Adapter ist die häufigste Implementierung des Adapter-Patterns in Android. Es wandelt Daten (eine Liste von Objekten) in ViewHolder um, die RecyclerView auf dem Bildschirm anzeigt. Mit der Einführung von ListAdapter (Android Architecture Components) erhielt das Pattern eine integrierte Diff-Berechnungsunterstützung zum Animieren von Änderungen.
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 verwendet ListAdapter mit DiffUtil für effizientes Neuzeichnen der Liste. Wenn die Daten aktualisiert werden, berechnet DiffCallback den Unterschied zwischen der alten und neuen Liste, und RecyclerView animiert nur die geänderten Elemente — ohne die gesamte Liste neu zu zeichnen. Dies ist ein entscheidender Vorteil gegenüber der älteren ListView, die ein manuelles notifyDataSetChanged() erforderte.
UITableViewDataSource ist gleichzeitig das Adapter-Pattern und das DataSource-Pattern. Es wandelt Modelldaten in Tabellenzellen um. Seit iOS 13 hat Apple DiffableDataSource eingeführt — einen modernen Ersatz, der wie ListAdapter in Android automatisch Änderungen berechnet und Aktualisierungen animiert.
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
}
}
// Moderne Version: 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 mit DiffableDataSource animiert Änderungen automatisch, wenn Daten aktualisiert werden: Elemente erscheinen, verschwinden oder bewegen sich mit flüssigen Animationen. DiffableDataSource in iOS und ListAdapter in Android lösen dasselbe Problem mit unterschiedlichen APIs, aber derselben Logik — Diff-Berechnung + automatische Animation.
Object Adapter verwendet Komposition: der Adapter enthält eine Referenz auf das Adaptee-Objekt und delegiert Aufrufe an es. Class Adapter verwendet Vererbung: der Adapter erbt von Adaptee und implementiert gleichzeitig die Zielschnittstelle. In Java, Swift und Kotlin ist Class Adapter aufgrund fehlender Mehrfachvererbung unmöglich — es bleibt nur Object Adapter.
| Eigenschaft | Object Adapter | Class Adapter |
|---|---|---|
| Mechanismus | Komposition (enthält Adaptee) | Vererbung (erweitert Adaptee) |
| Flexibilität | Höher — Adaptee kann zur Laufzeit geändert werden | Niedriger — Adaptee ist zur Kompilierzeit festgelegt |
| Kopplung | Schwach (über Schnittstelle) | Stark (über Vererbung) |
| Subklassen-Anpassung | Ja — Adaptee kann eine beliebige Subklasse sein | Nein — nur eine bestimmte Superklasse |
| Verfügbarkeit | Java, Kotlin, Swift, C++, C# | Nur Sprachen mit Mehrfachvererbung (C++) |
In der mobilen Entwicklung wird nur Object Adapter verwendet. RecyclerView.Adapter enthält Referenzen auf Daten und LayoutInflater, UITableViewDataSource enthält ein Array von Elementen. Komposition macht den Code flexibler und testbarer im Vergleich zur Vererbung.
Adapter wird nicht nur für Listen verwendet. Betrachten wir drei Szenarien aus der mobilen Entwicklungspraxis, in denen das Pattern das Problem der Integration inkompatibler Komponenten löst.
| Szenario | Adaptee | Target | Adapter |
|---|---|---|---|
| Integration einer alten Auth-Bibliothek | LegacyAuthLib (callback-basiert) | AuthService (suspend) | LegacyAuthAdapter |
| JSON-Anpassung an ein neues Modell | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Einheitliche Schnittstelle für Push-Dienste | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Jeder Adapter kapselt die Konvertierungslogik: GsonAdapter übersetzt einen JSON-String über @SerializedName in ein Objekt, PushServiceAdapter abstrahiert die Token-Registrierung für verschiedene Plattformen. Der Client (Geschäftslogik) arbeitet mit einer einheitlichen Schnittstelle und ist nicht von der spezifischen Implementierung abhängig.
Fehler bei der Adapter-Implementierung führen zu langsamen Listen, Speicherlecks und Fehlern bei Datenaktualisierungen. Betrachten wir drei häufige Probleme.
onBindViewHolder wird für jede Zelle beim Scrollen aufgerufen. Objekterstellung, JSON-Parsing oder Datenbankaufrufe in dieser Methode führen zu Rucklern (Frame Drops). Lösung: alle schweren Berechnungen im Voraus durchführen und dem Adapter bereits aufbereitete Daten übergeben. Für Bilder Caching-Bibliotheken verwenden — Glide, Coil, Kingfisher.
Der Aufruf von notifyDataSetChanged() bei jeder Aktualisierung zeichnet die gesamte Liste neu, was zu Flimmern und Fokusverlust bei Eingaben führt. Lösung: ListAdapter mit DiffUtil in Android oder DiffableDataSource in iOS verwenden. Die Diff-Berechnung dauert <1 ms für Listen mit bis zu 1000 Elementen und gewährleistet flüssige Animationen.
Der Adapter lebt so lange wie RecyclerView/UITableView. Das Speichern von Bitmaps oder großen Arrays im Adapter führt bei Konfigurationsänderungen (Bildschirmdrehung, Themenwechsel) zu Speicherlecks. Lösung: Der Adapter sollte nur leichte Datenmodelle (data class / struct) speichern und schwere Ressourcen bei Bedarf über ViewHolder laden.
Häufig gestellte Fragen
Adapter wandelt die Adaptee-Schnittstelle in die Target-Schnittstelle um — der Client erhält eine neue Interaktionsmöglichkeit. Proxy bietet dieselbe Schnittstelle wie das ursprüngliche Objekt, fügt aber Zugriffskontrolle, Caching oder verzögerte Initialisierung hinzu. Proxy ändert die Schnittstelle nicht, Adapter schon.
RecyclerView.Adapter wandelt Daten (z.B. eine Liste von User) in einen ViewHolder um, den RecyclerView anzeigen kann. RecyclerView erwartet einen ViewHolder, die Daten liegen im List<User>-Format vor — der Adapter passt eines an das andere an. Zusätzlich verwaltet der Adapter den Lebenszyklus der ViewHolder über den Recycling-Pool und verbessert so die Listenleistung.
Object Adapter verwendet Komposition: er enthält eine Referenz auf Adaptee und delegiert Aufrufe an ihn. Class Adapter verwendet Mehrfachvererbung: er erbt von Adaptee und implementiert Target. In Java, Kotlin und Swift ist nur Object Adapter verfügbar, da Mehrfachvererbung von Klassen fehlt. Class Adapter ist nur in C++ möglich.
Adapter wird verwendet, wenn zwei vorhandene Klassen durch Konvertierung der Schnittstelle einer von ihnen zur Zusammenarbeit gebracht werden müssen. Facade wird verwendet, wenn die Interaktion mit einem komplexen Subsystem durch eine einfache Schnittstelle vereinfacht werden soll. Adapter dient der Kompatibilität, Facade der Vereinfachung.
Ja, mit Closures. Anstelle einer Schnittstelle mit einer Methode kann eine Funktion übergeben werden. Zum Beispiel in Swift: let adapter = { (data: Data) -> [CellConfig] in /* Konvertierung */ }. Dieser Ansatz wird funktionaler Adapter genannt. Für 2+ Methoden bleibt jedoch ein Protokoll/Interface vorzuziehen — es macht den Vertrag explizit.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch