Capítulo 15: Consumo de APIs REST com Ktor Client e Kotlinx Serialization
Capítulo 15: Consumo de APIs REST com Ktor Client e Kotlinx Serialization
Especialização em Mobile Nativo com Android • Kotlin 2.0 & Jetpack Compose
🗺️ Mapa Conceitual da Arquitetura de Rede com Ktor Client
flowchart TD
APP["Repositório / Service (Kotlin Coroutines)"] --> KTOR["HttpClient (Engine: Android / OkHttp / CIO)"]
subgraph PLUGINS["Pipeline de Plugins Interceptadores (Ktor Architecture)"]
AUTH["Auth Plugin (Bearer JWT / Refresh Automático de Token)"]
LOG["Logging Plugin (LogLevel.ALL / Android Logcat)"]
TIMEOUT["HttpTimeout (Connect: 15s, Request: 30s, Socket: 15s)"]
NEGOTIATION["ContentNegotiation (json(Json { ignoreUnknownKeys = true }))"]
end
KTOR --> AUTH
AUTH --> LOG
LOG --> TIMEOUT
TIMEOUT --> NEGOTIATION
subgraph NETWORK["Camada de Transporte e Rede Móvel"]
NEGOTIATION --> ENGINE["Engine Android / OkHttp Driver"]
ENGINE --> SSL["TLS 1.3 / Certificate Pinning"]
SSL --> ENDPOINT["Servidor REST / Gateway de Microsserviços"]
end
ENDPOINT -->|"JSON Response (200 OK / 401 / 500)"| DTO["Data Transfer Object (@Serializable DTO)"]
DTO --> APP
style APP fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style KTOR fill:#b3e5fc,stroke:#0288d1,stroke-width:2px
style NEGOTIATION fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style ENDPOINT fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style DTO fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. O Ktor Client como Padrão Ouro de Rede em Kotlin Moderno
Durante anos, a combinação de Retrofit com OkHttp e Gson dominou as requisições HTTP no Android. No entanto, com a ascensão do Kotlin Multiplatform (KMP) e do compilador Kotlin 2.0, o Ktor Client tornou-se a escolha estratégica definitiva por engenheiros de software mobile:
- Multiplataforma Nativa: O Ktor não depende do runtime Java (
java.lang.reflect) nem do bytecode específico do Android. O mesmo código de cliente HTTP, interceptação e regras de serialização roda inalterado no Android, iOS, Desktop e Web. - Serialização Livre de Reflexão: Em contraste com o
Gsonou bibliotecas legadas que inspecionam classes em tempo de execução via reflexão lenta, a bibliotecakotlinx.serializationgera os serializadores e deserializadores de JSON em tempo de compilação. Isso reduz o tempo de inicialização a frio do app e economiza preciosa memória RAM. - Arquitetura Modular por Plugins: No Ktor, tudo é um plugin desacoplado. Você adiciona apenas o que seu app estritamente necessita: negociação de conteúdo JSON, autenticação OAuth2 com refresh token automático, timeouts de conexão e logs de auditoria de tráfego.
📊 2. Comparativo Estrutural: Retrofit (OkHttp) vs Ktor Client
| Dimensão Técnica | Retrofit 2 + OkHttp | Ktor Client Moderno |
|---|---|---|
| Portabilidade de Plataforma | Restrito à JVM / Android | Multiplataforma (Android, iOS nativo, Web, JVM) |
| Definição de Endpoints | Interfaces Java anotadas com @GET, @POST |
Funções declarativas com construtor de URL fluido |
| Engine de Serialização | Gson ou Moshi (Reflexão pesada) | kotlinx.serialization (Compilador estático K2) |
| Tamanho do Binário | Médio (OkHttp + Retrofit + Conversores) | Extremamente leve (módulos atômicos) |
| Customização de Requisições | Interceptors complexos do OkHttp | Pipeline limpo de lambdas e blocos de configuração |
💻 3. Laboratório Prático: Cliente Ktor com Injeção de Headers e Parsing Seguro
O exemplo abaixo apresenta a criação de um cliente HTTP corporativo com configuração de timeouts, serialização JSON com descarte de campos desconhecidos e tratamento seguro de respostas:
package br.com.portal.mobile.android.cap15
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
// 1. Data Transfer Objects (DTO) Serializáveis em Tempo de Compilação
@Serializable
data class NoticiaDto(
@SerialName("id") val id: Long,
@SerialName("title") val titulo: String,
@SerialName("summary") val resumo: String,
@SerialName("published_at") val dataPublicacao: String
)
// 2. Criação da Fábrica do Cliente Ktor
fun criarKtorClient(tokenAutenticacao: String?): HttpClient {
return HttpClient(Android) {
// Plugin de Negociação de Conteúdo JSON
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true // Evita crash caso o backend envie novos campos
})
}
// Plugin de Timeouts Resilientes
install(HttpTimeout) {
requestTimeoutMillis = 15_000L
connectTimeoutMillis = 10_000L
socketTimeoutMillis = 15_000L
}
// Plugin de Auditoria e Logs
install(Logging) {
level = LogLevel.INFO
}
}
}
// 3. Serviço de API com Tratamento de Erros HTTP
class NoticiaApiService(private val client: HttpClient) {
suspend fun buscarNoticiasDestaque(): List<NoticiaDto> {
val resposta: HttpResponse = client.get("https://api.portal.com/v1/noticias") {
headers {
append(HttpHeaders.Accept, "application/json")
append(HttpHeaders.UserAgent, "PortalMobileAndroid/1.0")
}
}
return when (resposta.status) {
HttpStatusCode.OK -> resposta.body<List<NoticiaDto>>()
HttpStatusCode.Unauthorized -> throw IllegalStateException("Sessão expirada. Faça login novamente.")
HttpStatusCode.ServiceUnavailable -> throw IllegalStateException("Servidor em manutenção temporária.")
else -> throw RuntimeException("Falha na requisição: Código HTTP ${resposta.status.value}")
}
}
}
⚠️ 4. Armadilhas Comuns no Consumo de APIs no Android
- Esquecer
ignoreUnknownKeys = true: Por padrão, okotlinx.serializationlança uma exceção fatal se o servidor JSON devolver um novo campo que não esteja mapeado na suadata class. HabilitarignoreUnknownKeys = trueé mandatório para resiliência a atualizações de microsserviços. - Esquecer a Permissão de Internet no Manifest: No arquivo
AndroidManifest.xml, declare obrigatoriamente<uses-permission android:name="android.permission.INTERNET" />. Sem essa declaração, qualquer requisição dispara instantaneamente umaSecurityException. - Instanciar Múltiplos
HttpClientpor Requisição: Cada instância deHttpClientaloca pools de sockets e recursos de conexão. Sempre registre oHttpClientcomo Singleton no seu grafo de injeção de dependências (Koin/Hilt).