Adapter es un patrón de diseño estructural que convierte la interfaz de una clase en otra interfaz esperada por el cliente. En el desarrollo móvil, Adapter se usa más frecuentemente en RecyclerView.Adapter para Android y UITableViewDataSource para iOS. Según Google I/O (2024), RecyclerView se utiliza en el 95% de las aplicaciones Android para mostrar listas, y cada una requiere su propia implementación de Adapter.
Puntos clave
Adapter es un patrón estructural que resuelve el problema de incompatibilidad de interfaces. Envuelve un objeto (Adaptee) en una clase (Adapter) con la interfaz esperada por el cliente (Target). El cliente trabaja con Target sin saber de la existencia de Adaptee. Es una variante del patrón Wrapper.
// Clase existente con interfaz incompatible
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Interfaz objetivo (lo que espera el cliente)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — transforma 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 convierte la llamada login(credentials) en loginWithToken(token) para la API antigua. El cliente (ViewModel) trabaja a través de la interfaz AuthService y no sabe si debajo tiene el último Firebase Auth o una API heredada de hace una década. Esto permite reemplazar implementaciones sin cambiar el código del cliente.
RecyclerView.Adapter es la implementación más común del patrón Adapter en Android. Convierte datos (una lista de objetos) en ViewHolders que RecyclerView muestra en pantalla. Con la llegada de ListAdapter (Android Architecture Components), el patrón obtuvo soporte integrado para cálculo de diferencias (diff) para animar cambios.
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 para un redibujado eficiente de la lista. Cuando los datos se actualizan, DiffCallback calcula la diferencia entre la lista antigua y la nueva, y RecyclerView anima solo los elementos cambiados — sin redibujar toda la lista. Esta es una ventaja clave sobre el anterior ListView, que requería notifyDataSetChanged() manual.
UITableViewDataSource es a la vez el patrón Adapter y el patrón DataSource. Convierte datos del modelo en celdas de tabla. Desde iOS 13, Apple presentó DiffableDataSource — un reemplazo moderno que, como ListAdapter en Android, calcula automáticamente los cambios y anima las actualizaciones.
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
}
}
// Versión 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 automáticamente los cambios al actualizar los datos: los elementos aparecen, desaparecen o se mueven con animaciones suaves. DiffableDataSource de iOS y ListAdapter de Android resuelven el mismo problema con diferentes API pero la misma lógica — cálculo de diferencias + animación automática.
Object Adapter usa composición: el adaptador contiene una referencia al objeto Adaptee y le delega las llamadas. Class Adapter usa herencia: el adaptador hereda de Adaptee e implementa la interfaz objetivo simultáneamente. En Java, Swift y Kotlin, Class Adapter es imposible por la falta de herencia múltiple — solo queda Object Adapter.
| Característica | Object Adapter | Class Adapter |
|---|---|---|
| Mecanismo | Composición (contiene Adaptee) | Herencia (extiende Adaptee) |
| Flexibilidad | Mayor — Adaptee se puede cambiar en tiempo de ejecución | Menor — Adaptee se fija en tiempo de compilación |
| Acoplamiento | Débil (a través de interfaz) | Fuerte (a través de herencia) |
| Adaptación de subclases | Sí — Adaptee puede ser cualquier subclase | No — solo una superclase específica |
| Disponibilidad | Java, Kotlin, Swift, C++, C# | Solo lenguajes con herencia múltiple (C++) |
En el desarrollo móvil, solo se usa Object Adapter. RecyclerView.Adapter contiene referencias a datos y LayoutInflater, UITableViewDataSource contiene un array de elementos. La composición hace que el código sea más flexible y comprobable en comparación con la herencia.
Adapter no solo se usa para listas. Consideremos tres escenarios de la práctica de desarrollo móvil donde el patrón resuelve el problema de integrar componentes incompatibles.
| Escenario | Adaptee | Target | Adapter |
|---|---|---|---|
| Integración de una librería auth antigua | LegacyAuthLib (basado en callbacks) | AuthService (suspend) | LegacyAuthAdapter |
| Adaptación de JSON a un nuevo modelo | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Interfaz unificada para servicios push | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Cada Adapter encapsula la lógica de conversión: GsonAdapter traduce una cadena JSON a un objeto mediante @SerializedName, PushServiceAdapter abstrae el registro de tokens para diferentes plataformas. El cliente (lógica de negocio) trabaja con una interfaz unificada y no depende de la implementación específica.
Los errores en la implementación de Adapter provocan lentitud en las listas, fugas de memoria y fallos en la actualización de datos. Veamos tres problemas comunes.
onBindViewHolder se llama para cada celda durante el desplazamiento. Crear objetos, analizar JSON o llamar a la base de datos en este método provoca caídas de fotogramas (frame drops). Solución: realizar todos los cálculos pesados de antemano, pasando datos ya preparados al adaptador. Usar librerías de caché para imágenes — Glide, Coil, Kingfisher.
Llamar a notifyDataSetChanged() en cada actualización redibuja toda la lista, causando parpadeo y pérdida de foco en las entradas. Solución: usar ListAdapter con DiffUtil en Android o DiffableDataSource en iOS. El cálculo de diferencias toma <1 ms para listas de hasta 1000 elementos y garantiza animaciones suaves.
El adaptador vive tanto como RecyclerView/UITableView. Almacenar Bitmaps o arrays grandes en el adaptador provoca fugas de memoria en cambios de configuración (rotación de pantalla, cambio de tema). Solución: el adaptador debe almacenar solo modelos de datos ligeros (data class / struct), y cargar recursos pesados a través de ViewHolder bajo demanda.
Preguntas frecuentes
Adapter convierte la interfaz de Adaptee a la interfaz de Target — el cliente obtiene una nueva forma de interacción. Proxy proporciona la misma interfaz que el objeto original, pero añade control de acceso, caché o inicialización diferida. Proxy no cambia la interfaz, Adapter sí.
RecyclerView.Adapter convierte datos (por ejemplo, una lista de User) en un ViewHolder que RecyclerView puede mostrar. RecyclerView espera un ViewHolder, los datos están en formato List<User> — el Adapter adapta uno al otro. Adicionalmente, el Adapter gestiona el ciclo de vida de los ViewHolders a través del pool de reciclaje, mejorando el rendimiento de la lista.
Object Adapter usa composición: contiene una referencia a Adaptee y le delega las llamadas. Class Adapter usa herencia múltiple: hereda de Adaptee e implementa Target. En Java, Kotlin y Swift, solo está disponible Object Adapter debido a la falta de herencia múltiple de clases. Class Adapter solo es posible en C++.
Adapter se usa cuando se necesita hacer que dos clases existentes funcionen juntas convirtiendo la interfaz de una de ellas. Facade se usa cuando se necesita simplificar la interacción con un subsistema complejo proporcionando una interfaz simple. Adapter es para compatibilidad, Facade es para simplificación.
Sí, mediante cierres. En lugar de una interfaz con un método, se puede pasar una función. Por ejemplo, en Swift: let adapter = { (data: Data) -> [CellConfig] in /* conversión */ }. Este enfoque se llama adaptador funcional. Sin embargo, para 2+ métodos, un protocolo/interfaz sigue siendo preferible — hace explícito el contrato.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también