Adapter — är ett strukturellt designmönster som omvandlar gränssnittet för en klass till ett annat gränssnitt som klienten förväntar sig. Inom mobilutveckling används Adapter oftast i RecyclerView.Adapter för Android och UITableViewDataSource för iOS. Enligt data från Google I/O (2024) används RecyclerView i 95% av Android-appar för att visa listor, och varje kräver sin egen Adapter-implementering.
Huvudpunkter
Adapter — strukturellt mönster som löser problemet med inkompatibla gränssnitt. Det lindar in ett objekt (Adaptee) i en klass (Adapter) med det gränssnitt som klienten förväntar sig (Target). Klienten arbetar med Target, utan att veta om Adaptee:s existens. Detta är en variant av Wrapper-mönstret.
// Befintlig klass med inkompatibelt gränssnitt
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Målgränssnitt (vad klienten förväntar sig)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — omvandlar LegacyAuthApi till 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 omvandlar anropet login(credentials) till loginWithToken(token) för det gamla API:et. Klienten (ViewModel) arbetar via AuthService-gränssnittet och vet inte om det under ligger den senaste Firebase Auth eller ett tio år gammalt API. Detta gör det möjligt att byta implementeringar utan att ändra klientkoden.
RecyclerView.Adapter — den vanligaste implementeringen av Adapter-mönstret i Android. Det omvandlar data (en lista av objekt) till ViewHolders som RecyclerView visar på skärmen. Med tillkomsten av ListAdapter (Android Architecture Components) fick mönstret inbyggt stöd för diff-beräkning för animering av förändringar.
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 använder ListAdapter med DiffUtil för effektiv omritning av listan. När data uppdateras beräknar DiffCallback skillnaden mellan den gamla och nya listan, och RecyclerView animerar endast de ändrade elementen — utan att rita om hela listan. Detta är en viktig fördel jämfört med äldre ListView som krävde manuell notifyDataSetChanged().
UITableViewDataSource — är både Adapter- och DataSource-mönstret. Det omvandlar modelldata till tabellceller. Från och med iOS 13 introducerade Apple DiffableDataSource — en modern ersättning som, liksom ListAdapter i Android, automatiskt beräknar förändringar och animerar uppdateringar.
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 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 med DiffableDataSource animerar automatiskt förändringar vid uppdatering av data: element visas, försvinner eller flyttas med mjuka animationer. iOS DiffableDataSource och Android ListAdapter löser samma uppgift med olika API:er, men med samma logik — diff-beräkning + automatisk animering.
Object Adapter använder komposition: adaptern innehåller en referens till Adaptee-objektet och delegerar anrop till det. Class Adapter använder arv: adaptern ärver Adaptee och implementerar samtidigt målgränssnittet. I Java, Swift och Kotlin är Class Adapter omöjligt på grund av avsaknaden av multipelt arv — endast Object Adapter återstår.
| Egenskap | Object Adapter | Class Adapter |
|---|---|---|
| Mekanism | Komposition (innehåller Adaptee) | Arv (extends Adaptee) |
| Flexibilitet | Högre — Adaptee kan ändras under körning | Lägre — Adaptee är fast vid kompilering |
| Koppling | Svag (via gränssnitt) | Stark (via arv) |
| Anpassning av underklasser | Ja — Adaptee kan vara vilken underklass som helst | Nej — endast konkret superklass |
| Tillgänglighet | Java, Kotlin, Swift, C++, C# | Endast språk med multipelt arv (C++) |
Inom mobilutveckling används endast Object Adapter. RecyclerView.Adapter innehåller referenser till data och LayoutInflater, UITableViewDataSource innehåller en array av element. Komposition gör koden mer flexibel och testbar jämfört med arv.
Adapter tillämpas inte bara för listor. Låt oss titta på tre scenarier från mobilutvecklingspraktiken där mönstret löser problemet med integrering av inkompatibla komponenter.
| Scenario | Adaptee | Target | Adapter |
|---|---|---|---|
| Integrering av gammalt auth-bibliotek | LegacyAuthLib (callback-based) | AuthService (suspend) | LegacyAuthAdapter |
| Anpassning av JSON till ny modell | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Enhetligt gränssnitt för push-tjänster | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Varje Adapter inkapslar transformationslogiken: GsonAdapter översätter en JSON-sträng till ett objekt via @SerializedName, PushServiceAdapter abstraherar token-registrering för olika plattformar. Klienten (affärslogik) arbetar med ett enhetligt gränssnitt och är inte beroende av en specifik implementering.
Misstag vid implementering av Adapter leder till långsamma listor, minnesläckor och buggar med datauppdateringar. Låt oss analysera tre vanliga problem.
onBindViewHolder anropas för varje cell vid scrollning. Skapa objekt, JSON-tolkning eller databasfrågor i denna metod leder till hugg i listan (frame drops). Lösning: utför alla tunga beräkningar i förväg och skicka färdiga data till adaptern. För bilder, använd bibliotek med cachning — Glide, Coil, Kingfisher.
Att anropa notifyDataSetChanged() vid varje uppdatering ritar om hela listan, vilket orsakar flimmer och fokusförlust på inmatningsfält. Lösning: använd ListAdapter med DiffUtil i Android eller DiffableDataSource i iOS. Diff-beräkning tar <1 ms för listor upp till 1000 element och säkerställer mjuka animationer.
Adaptern lever lika länge som RecyclerView/UITableView. Lagra Bitmap eller stora arrayer i adaptern leder till minnesläckor vid konfigurationsändring (skärmrotation, temaväxling). Lösning: adaptern bör endast lagra lätta datamodeller (data class / struct) och ladda tunga resurser via ViewHolder vid behov.
Vanliga frågor
Adapter omvandlar Adaptees gränssnitt till Targets gränssnitt — klienten får ett nytt sätt att interagera. Proxy ger samma gränssnitt som det ursprungliga objektet, men lägger till åtkomstkontroll, cachning eller lazy-initiering. Proxy ändrar inte gränssnittet, Adapter — ändrar.
RecyclerView.Adapter omvandlar data (t.ex. en lista av User) till ViewHolder som RecyclerView kan visa. RecyclerView förväntar sig ViewHolder, data är i formatet List<User> — Adapter anpassar det ena till det andra. Dessutom hanterar Adapter ViewHolders livscykel via återvinningspoolen, vilket förbättrar listans prestanda.
Object Adapter använder komposition: innehåller en referens till Adaptee och delegerar anrop till det. Class Adapter använder multipelt arv: ärver Adaptee och implementerar Target. I Java, Kotlin och Swift är endast Object Adapter tillgängligt på grund av avsaknaden av multipelt klassarv. Class Adapter är endast möjligt i C++.
Adapter används när två befintliga klasser behöver samarbeta genom att omvandla gränssnittet för en av dem. Facade — när interaktionen med ett komplext delsystem behöver förenklas genom att tillhandahålla ett enkelt gränssnitt. Adapter — för kompatibilitet, Facade — för förenkling.
Ja, med hjälp av slutningar (closures). Istället för ett gränssnitt med en metod kan en funktion skickas. Till exempel i Swift: let adapter = { (data: Data) -> [CellConfig] in /* omvandling */ }. Detta tillvägagångssätt kallas funktionell adapter. För 2+ metoder är dock protokoll/gränssnitt fortfarande att föredra — det gör kontraktet explicit.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också