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:

  1. 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.
  2. Serialização Livre de Reflexão: Em contraste com o Gson ou bibliotecas legadas que inspecionam classes em tempo de execução via reflexão lenta, a biblioteca kotlinx.serialization gera 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.
  3. 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

  1. Esquecer ignoreUnknownKeys = true: Por padrão, o kotlinx.serialization lança uma exceção fatal se o servidor JSON devolver um novo campo que não esteja mapeado na sua data class. Habilitar ignoreUnknownKeys = true é mandatório para resiliência a atualizações de microsserviços.
  2. 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 uma SecurityException.
  3. Instanciar Múltiplos HttpClient por Requisição: Cada instância de HttpClient aloca pools de sockets e recursos de conexão. Sempre registre o HttpClient como Singleton no seu grafo de injeção de dependências (Koin/Hilt).