Adapter è un pattern di progettazione strutturale che converte l'interfaccia di una classe in un'altra interfaccia attesa dal client. Nello sviluppo mobile, Adapter è utilizzato più frequentemente in RecyclerView.Adapter per Android e UITableViewDataSource per iOS. Secondo Google I/O (2024), RecyclerView è utilizzato nel 95% delle app Android per visualizzare elenchi, e ciascuna richiede la propria implementazione di Adapter.
Punti chiave
Adapter è un pattern strutturale che risolve il problema dell'incompatibilità delle interfacce. Incapsula un oggetto (Adaptee) in una classe (Adapter) con l'interfaccia attesa dal client (Target). Il client lavora con Target senza conoscere l'esistenza di Adaptee. È una variante del pattern Wrapper.
// Classe esistente con interfaccia incompatibile
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Interfaccia target (cosa si aspetta il client)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — trasforma LegacyAuthApi in 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 converte la chiamata login(credentials) in loginWithToken(token) per la vecchia API. Il client (ViewModel) lavora attraverso l'interfaccia AuthService e non sa se sotto c'è il più recente Firebase Auth o un'API legacy di dieci anni fa. Questo consente di sostituire le implementazioni senza modificare il codice client.
RecyclerView.Adapter è l'implementazione più comune del pattern Adapter in Android. Converte i dati (un elenco di oggetti) in ViewHolder che RecyclerView visualizza sullo schermo. Con l'introduzione di ListAdapter (Android Architecture Components), il pattern ha ottenuto il supporto integrato per il calcolo delle differenze (diff) per animare i cambiamenti.
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 usa ListAdapter con DiffUtil per un ridisegno efficiente dell'elenco. Quando i dati vengono aggiornati, DiffCallback calcola la differenza tra l'elenco vecchio e quello nuovo, e RecyclerView anima solo gli elementi modificati — senza ridisegnare l'intero elenco. Questo è un vantaggio chiave rispetto al vecchio ListView, che richiedeva notifyDataSetChanged() manuale.
UITableViewDataSource è contemporaneamente il pattern Adapter e il pattern DataSource. Converte i dati del modello in celle della tabella. Da iOS 13, Apple ha introdotto DiffableDataSource — un sostituto moderno che, come ListAdapter in Android, calcola automaticamente i cambiamenti e anima gli aggiornamenti.
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
}
}
// Versione moderna: 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 con DiffableDataSource anima automaticamente i cambiamenti quando i dati vengono aggiornati: gli elementi appaiono, scompaiono o si muovono con animazioni fluide. DiffableDataSource di iOS e ListAdapter di Android risolvono lo stesso problema con API diverse ma la stessa logica — calcolo delle differenze + animazione automatica.
Object Adapter usa la composizione: l'adattatore contiene un riferimento all'oggetto Adaptee e gli delega le chiamate. Class Adapter usa l'ereditarietà: l'adattatore eredita da Adaptee e implementa contemporaneamente l'interfaccia target. In Java, Swift e Kotlin, Class Adapter è impossibile a causa della mancanza di ereditarietà multipla — rimane solo Object Adapter.
| Caratteristica | Object Adapter | Class Adapter |
|---|---|---|
| Meccanismo | Composizione (contiene Adaptee) | Ereditarietà (estende Adaptee) |
| Flessibilità | Maggiore — Adaptee può essere cambiato a runtime | Minore — Adaptee è fisso in fase di compilazione |
| Accoppiamento | Debole (tramite interfaccia) | Forte (tramite ereditarietà) |
| Adattamento sottoclassi | Sì — Adaptee può essere qualsiasi sottoclasse | No — solo una superclasse specifica |
| Disponibilità | Java, Kotlin, Swift, C++, C# | Solo linguaggi con ereditarietà multipla (C++) |
Nello sviluppo mobile, viene utilizzato solo Object Adapter. RecyclerView.Adapter contiene riferimenti ai dati e a LayoutInflater, UITableViewDataSource contiene un array di elementi. La composizione rende il codice più flessibile e testabile rispetto all'ereditarietà.
Adapter non è utilizzato solo per gli elenchi. Consideriamo tre scenari dalla pratica dello sviluppo mobile in cui il pattern risolve il problema dell'integrazione di componenti incompatibili.
| Scenario | Adaptee | Target | Adapter |
|---|---|---|---|
| Integrazione di libreria auth legacy | LegacyAuthLib (basata su callback) | AuthService (suspend) | LegacyAuthAdapter |
| Adattamento JSON a un nuovo modello | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Interfaccia unificata per servizi push | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Ogni Adapter incapsula la logica di conversione: GsonAdapter traduce una stringa JSON in un oggetto tramite @SerializedName, PushServiceAdapter astrae la registrazione del token per diverse piattaforme. Il client (logica di business) lavora con un'interfaccia unificata e non dipende dall'implementazione specifica.
Gli errori nell'implementazione di Adapter portano a lentezza degli elenchi, perdite di memoria e bug con gli aggiornamenti dei dati. Esaminiamo tre problemi comuni.
onBindViewHolder viene chiamato per ogni cella durante lo scorrimento. Creare oggetti, analizzare JSON o chiamare il database in questo metodo porta a cadute di frame (frame drops). Soluzione: eseguire tutti i calcoli pesanti in anticipo, passando dati già pronti all'adattatore. Utilizzare librerie di caching per le immagini — Glide, Coil, Kingfisher.
Chiamare notifyDataSetChanged() a ogni aggiornamento ridisegna l'intero elenco, causando sfarfallio e perdita di focus sugli input. Soluzione: utilizzare ListAdapter con DiffUtil in Android o DiffableDataSource in iOS. Il calcolo delle differenze richiede <1 ms per elenchi fino a 1000 elementi e garantisce animazioni fluide.
L'adattatore vive quanto RecyclerView/UITableView. Conservare Bitmap o grandi array nell'adattatore porta a perdite di memoria in caso di cambiamenti di configurazione (rotazione dello schermo, cambio tema). Soluzione: l'adattatore dovrebbe conservare solo modelli di dati leggeri (data class / struct) e caricare risorse pesanti tramite ViewHolder su richiesta.
Domande frequenti
Adapter converte l'interfaccia di Adaptee nell'interfaccia di Target — il client ottiene un nuovo modo di interazione. Proxy fornisce la stessa interfaccia dell'oggetto originale, ma aggiunge controllo degli accessi, caching o inizializzazione pigra. Proxy non cambia l'interfaccia, Adapter sì.
RecyclerView.Adapter converte i dati (ad esempio, un elenco di User) in un ViewHolder che RecyclerView può visualizzare. RecyclerView si aspetta un ViewHolder, i dati sono in formato List<User> — l'Adapter adatta l'uno all'altro. Inoltre, l'Adapter gestisce il ciclo di vita dei ViewHolder attraverso il pool di riciclo, migliorando le prestazioni dell'elenco.
Object Adapter usa la composizione: contiene un riferimento ad Adaptee e gli delega le chiamate. Class Adapter usa l'ereditarietà multipla: eredita da Adaptee e implementa Target. In Java, Kotlin e Swift, è disponibile solo Object Adapter a causa della mancanza di ereditarietà multipla delle classi. Class Adapter è possibile solo in C++.
Adapter si usa quando è necessario far funzionare insieme due classi esistenti convertendo l'interfaccia di una di esse. Facade si usa quando è necessario semplificare l'interazione con un sottosistema complesso fornendo un'interfaccia semplice. Adapter è per la compatibilità, Facade per la semplificazione.
Sì, usando le closure. Invece di un'interfaccia con un metodo, si può passare una funzione. Ad esempio, in Swift: let adapter = { (data: Data) -> [CellConfig] in /* conversione */ }. Questo approccio si chiama adattatore funzionale. Tuttavia, per 2+ metodi, un protocollo/interfaccia rimane preferibile — rende il contratto esplicito.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche