Adapter é um padrão de design estrutural que converte a interface de uma classe em outra interface esperada pelo cliente. No desenvolvimento móvel, Adapter é mais frequentemente usado em RecyclerView.Adapter para Android e UITableViewDataSource para iOS. De acordo com o Google I/O (2024), RecyclerView é usado em 95% dos aplicativos Android para exibir listas, e cada um requer sua própria implementação de Adapter.
Pontos principais
Adapter é um padrão estrutural que resolve o problema de incompatibilidade de interfaces. Ele envolve um objeto (Adaptee) em uma classe (Adapter) com a interface esperada pelo cliente (Target). O cliente trabalha com Target sem saber da existência de Adaptee. É uma variante do padrão Wrapper.
// Classe existente com interface incompatível
class LegacyAuthApi {
fun loginWithToken(token: String): Map {
return mapOf("status" to "ok", "user_id" to 42)
}
}
// Interface alvo (o que o cliente espera)
interface AuthService {
suspend fun login(credentials: Credentials): Result
}
// Adapter — transforma LegacyAuthApi em 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 a chamada login(credentials) em loginWithToken(token) para a API antiga. O cliente (ViewModel) trabalha através da interface AuthService e não sabe se por baixo está o mais recente Firebase Auth ou uma API legada de uma década atrás. Isso permite substituir implementações sem alterar o código do cliente.
RecyclerView.Adapter é a implementação mais comum do padrão Adapter no Android. Ele converte dados (uma lista de objetos) em ViewHolders que o RecyclerView exibe na tela. Com a introdução do ListAdapter (Android Architecture Components), o padrão ganhou suporte embutido para cálculo de diferenças (diff) para animar alterações.
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 com DiffUtil para redesenho eficiente da lista. Quando os dados são atualizados, DiffCallback calcula a diferença entre a lista antiga e a nova, e o RecyclerView anima apenas os itens alterados — sem redesenhar a lista inteira. Esta é uma vantagem chave sobre o ListView antigo, que exigia notifyDataSetChanged() manual.
UITableViewDataSource é ao mesmo tempo o padrão Adapter e o padrão DataSource. Ele converte dados do modelo em células de tabela. Desde o iOS 13, a Apple introduziu o DiffableDataSource — uma substituição moderna que, como o ListAdapter no Android, calcula automaticamente as alterações e anima as atualizações.
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
}
}
// Versão 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 com DiffableDataSource anima automaticamente as alterações quando os dados são atualizados: os itens aparecem, desaparecem ou se movem com animações suaves. O DiffableDataSource do iOS e o ListAdapter do Android resolvem o mesmo problema com APIs diferentes, mas a mesma lógica — cálculo de diferenças + animação automática.
Object Adapter usa composição: o adaptador contém uma referência ao objeto Adaptee e delega chamadas a ele. Class Adapter usa herança: o adaptador herda de Adaptee e implementa a interface alvo simultaneamente. Em Java, Swift e Kotlin, Class Adapter é impossível devido à falta de herança múltipla — apenas Object Adapter permanece.
| Característica | Object Adapter | Class Adapter |
|---|---|---|
| Mecanismo | Composição (contém Adaptee) | Herança (estende Adaptee) |
| Flexibilidade | Maior — Adaptee pode ser alterado em tempo de execução | Menor — Adaptee é fixo em tempo de compilação |
| Acoplamento | Fraco (via interface) | Forte (via herança) |
| Adaptação de subclasses | Sim — Adaptee pode ser qualquer subclasse | Não — apenas uma superclasse específica |
| Disponibilidade | Java, Kotlin, Swift, C++, C# | Apenas linguagens com herança múltipla (C++) |
No desenvolvimento móvel, apenas Object Adapter é usado. RecyclerView.Adapter contém referências a dados e LayoutInflater, UITableViewDataSource contém um array de itens. A composição torna o código mais flexível e testável em comparação com a herança.
Adapter não é usado apenas para listas. Vamos considerar três cenários da prática de desenvolvimento móvel onde o padrão resolve o problema de integrar componentes incompatíveis.
| Cenário | Adaptee | Target | Adapter |
|---|---|---|---|
| Integração de biblioteca auth legada | LegacyAuthLib (baseada em callbacks) | AuthService (suspend) | LegacyAuthAdapter |
| Adaptação de JSON para novo modelo | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Interface unificada para serviços push | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Cada Adapter encapsula a lógica de conversão: GsonAdapter traduz uma string JSON em um objeto via @SerializedName, PushServiceAdapter abstrai o registro de token para diferentes plataformas. O cliente (lógica de negócios) trabalha com uma interface unificada e não depende da implementação específica.
Erros na implementação de Adapter levam a lentidão em listas, vazamentos de memória e bugs com atualizações de dados. Vamos ver três problemas comuns.
onBindViewHolder é chamado para cada célula durante a rolagem. Criar objetos, analisar JSON ou chamar o banco de dados neste método leva a quedas de quadros (frame drops). Solução: realize todos os cálculos pesados antecipadamente, passando dados prontos para o adaptador. Use bibliotecas de cache para imagens — Glide, Coil, Kingfisher.
Chamar notifyDataSetChanged() em cada atualização redesenha a lista inteira, causando oscilação e perda de foco em entradas. Solução: use ListAdapter com DiffUtil no Android ou DiffableDataSource no iOS. O cálculo de diferenças leva <1 ms para listas de até 1000 itens e garante animações suaves.
O adaptador vive enquanto o RecyclerView/UITableView existir. Armazenar Bitmaps ou arrays grandes no adaptador leva a vazamentos de memória em mudanças de configuração (rotação de tela, mudança de tema). Solução: o adaptador deve armazenar apenas modelos de dados leves (data class / struct), e carregar recursos pesados através do ViewHolder sob demanda.
Perguntas frequentes
Adapter converte a interface de Adaptee para a interface de Target — o cliente obtém uma nova forma de interação. Proxy fornece a mesma interface que o objeto original, mas adiciona controle de acesso, cache ou inicialização preguiçosa. Proxy não altera a interface, Adapter altera.
RecyclerView.Adapter converte dados (por exemplo, uma lista de User) em um ViewHolder que o RecyclerView pode exibir. RecyclerView espera um ViewHolder, os dados estão no formato List<User> — o Adapter adapta um ao outro. Além disso, o Adapter gerencia o ciclo de vida do ViewHolder através do pool de reciclagem, melhorando o desempenho da lista.
Object Adapter usa composição: contém uma referência a Adaptee e delega chamadas a ele. Class Adapter usa herança múltipla: herda de Adaptee e implementa Target. Em Java, Kotlin e Swift, apenas Object Adapter está disponível devido à falta de herança múltipla de classes. Class Adapter só é possível em C++.
Adapter é usado quando você precisa fazer duas classes existentes funcionarem juntas convertendo a interface de uma delas. Facade é usado quando você precisa simplificar a interação com um subsistema complexo fornecendo uma interface simples. Adapter é para compatibilidade, Facade é para simplificação.
Sim, usando closures. Em vez de uma interface com um método, você pode passar uma função. Por exemplo, em Swift: let adapter = { (data: Data) -> [CellConfig] in /* conversão */ }. Esta abordagem é chamada de adaptador funcional. No entanto, para 2+ métodos, um protocolo/interface continua preferível — torna o contrato explícito.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também