Adapter est un patron de conception structurel qui convertit l'interface d'une classe en une autre interface attendue par le client. Dans le développement mobile, Adapter est le plus souvent utilisé dans RecyclerView.Adapter pour Android et UITableViewDataSource pour iOS. Selon Google I/O (2024), RecyclerView est utilisé dans 95% des applications Android pour afficher des listes, et chacune nécessite sa propre implémentation d'Adapter.
Points clés
Adapter est un patron structurel qui résout le problème d'incompatibilité des interfaces. Il encapsule un objet (Adaptee) dans une classe (Adapter) avec l'interface attendue par le client (Target). Le client travaille avec Target sans connaître l'existence d'Adaptee. C'est une variante du pattern Wrapper.
// Classe existante avec interface incompatible
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Interface cible (ce que le client attend)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — transforme LegacyAuthApi en 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 convertit l'appel login(credentials) en loginWithToken(token) pour l'ancienne API. Le client (ViewModel) travaille via l'interface AuthService et ne sait pas si en dessous se trouve le tout dernier Firebase Auth ou une API legacy vieille d'une décennie. Cela permet de remplacer les implémentations sans modifier le code client.
RecyclerView.Adapter est l'implémentation la plus courante du pattern Adapter dans Android. Il convertit les données (une liste d'objets) en ViewHolders que RecyclerView affiche à l'écran. Avec l'introduction de ListAdapter (Android Architecture Components), le pattern a obtenu un support intégré de calcul de différences pour animer les changements.
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 utilise ListAdapter avec DiffUtil pour un redessin efficace de la liste. Lorsque les données sont mises à jour, DiffCallback calcule la différence entre l'ancienne et la nouvelle liste, et RecyclerView n'anime que les éléments modifiés — sans redessiner la liste entière. C'est un avantage clé par rapport à l'ancien ListView qui nécessitait un notifyDataSetChanged() manuel.
UITableViewDataSource est à la fois le pattern Adapter et le pattern DataSource. Il convertit les données du modèle en cellules de tableau. Depuis iOS 13, Apple a introduit DiffableDataSource — un remplacement moderne qui, comme ListAdapter dans Android, calcule automatiquement les changements et anime les mises à jour.
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
}
}
// Version moderne : 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 avec DiffableDataSource anime automatiquement les changements lors de la mise à jour des données : les éléments apparaissent, disparaissent ou se déplacent avec des animations fluides. DiffableDataSource d'iOS et ListAdapter d'Android résolvent le même problème avec des API différentes mais la même logique — calcul de différences + animation automatique.
Object Adapter utilise la composition : l'adaptateur contient une référence à l'objet Adaptee et lui délègue les appels. Class Adapter utilise l'héritage : l'adaptateur hérite d'Adaptee et implémente simultanément l'interface cible. En Java, Swift et Kotlin, Class Adapter est impossible en raison de l'absence d'héritage multiple — seul Object Adapter reste possible.
| Caractéristique | Object Adapter | Class Adapter |
|---|---|---|
| Mécanisme | Composition (contient Adaptee) | Héritage (étend Adaptee) |
| Flexibilité | Plus élevée — Adaptee peut être changé à l'exécution | Plus faible — Adaptee est fixé à la compilation |
| Couplage | Faible (via interface) | Fort (via héritage) |
| Adaptation des sous-classes | Oui — Adaptee peut être n'importe quelle sous-classe | Non — seulement une superclasse spécifique |
| Disponibilité | Java, Kotlin, Swift, C++, C# | Langues avec héritage multiple uniquement (C++) |
Dans le développement mobile, seul Object Adapter est utilisé. RecyclerView.Adapter contient des références aux données et à LayoutInflater, UITableViewDataSource contient un tableau d'éléments. La composition rend le code plus flexible et testable par rapport à l'héritage.
Adapter n'est pas utilisé seulement pour les listes. Considérons trois scénarios de la pratique du développement mobile où le pattern résout le problème d'intégration de composants incompatibles.
| Scénario | Adaptee | Target | Adapter |
|---|---|---|---|
| Intégration d'une bibliothèque auth legacy | LegacyAuthLib (basée sur callbacks) | AuthService (suspend) | LegacyAuthAdapter |
| Adaptation JSON à un nouveau modèle | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Interface unifiée pour les services push | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Chaque Adapter encapsule la logique de conversion : GsonAdapter traduit une chaîne JSON en objet via @SerializedName, PushServiceAdapter abstrait l'enregistrement de token pour différentes plateformes. Le client (logique métier) travaille avec une interface unifiée et ne dépend pas de l'implémentation spécifique.
Les erreurs dans l'implémentation d'Adapter entraînent des ralentissements de listes, des fuites mémoire et des bugs de mise à jour des données. Examinons trois problèmes courants.
onBindViewHolder est appelé pour chaque cellule lors du défilement. La création d'objets, l'analyse JSON ou l'appel à la base de données dans cette méthode provoquent des chutes d'images (frame drops). Solution : effectuer tous les calculs lourds à l'avance, en passant des données prêtes à l'adaptateur. Utiliser des bibliothèques de cache pour les images — Glide, Coil, Kingfisher.
Appeler notifyDataSetChanged() à chaque mise à jour redessine toute la liste, provoquant un scintillement et une perte de focus sur les entrées. Solution : utiliser ListAdapter avec DiffUtil dans Android ou DiffableDataSource dans iOS. Le calcul de différences prend <1 ms pour des listes jusqu'à 1000 éléments et garantit des animations fluides.
L'adaptateur vit aussi longtemps que RecyclerView/UITableView. Stocker des Bitmaps ou de grands tableaux dans l'adaptateur entraîne des fuites mémoire lors des changements de configuration (rotation d'écran, changement de thème). Solution : l'adaptateur ne doit stocker que des modèles de données légers (data class / struct), et charger les ressources lourdes via ViewHolder à la demande.
Foire aux questions
Adapter convertit l'interface d'Adaptee en l'interface de Target — le client obtient une nouvelle façon d'interagir. Proxy fournit la même interface que l'objet original, mais ajoute un contrôle d'accès, du cache ou une initialisation paresseuse. Proxy ne change pas l'interface, Adapter si.
RecyclerView.Adapter convertit les données (par exemple, une liste d'User) en un ViewHolder que RecyclerView peut afficher. RecyclerView attend un ViewHolder, les données sont au format List<User> — l'Adapter adapte l'un à l'autre. De plus, l'Adapter gère le cycle de vie des ViewHolders via le pool de recyclage, améliorant les performances de la liste.
Object Adapter utilise la composition : il contient une référence à Adaptee et lui délègue les appels. Class Adapter utilise l'héritage multiple : il hérite d'Adaptee et implémente Target. En Java, Kotlin et Swift, seul Object Adapter est disponible en raison de l'absence d'héritage multiple de classes. Class Adapter n'est possible qu'en C++.
Adapter est utilisé lorsque vous devez faire fonctionner deux classes existantes ensemble en convertissant l'interface de l'une d'elles. Facade est utilisé lorsque vous devez simplifier l'interaction avec un sous-système complexe en fournissant une interface simple. Adapter est pour la compatibilité, Facade pour la simplification.
Oui, en utilisant des fermetures. Au lieu d'une interface avec une méthode, vous pouvez passer une fonction. Par exemple, en Swift : let adapter = { (data: Data) -> [CellConfig] in /* conversion */ }. Cette approche s'appelle un adaptateur fonctionnel. Cependant, pour 2+ méthodes, un protocole/interface reste préférable — il rend le contrat explicite.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi