GraphQL aparece cada vez mais em vagas Android porque resolve um problema real de produto: telas mobile raramente precisam exatamente do mesmo payload que o backend REST já expõe. Uma home pode precisar de nome do usuário, saldo resumido, banners, atalhos e notificações; uma tela de detalhe pode misturar informações de catálogo, preço, disponibilidade e recomendações. Quando cada tela depende de várias chamadas REST, o app fica mais sensível a latência, versionamento e campos desnecessários.

Com Kotlin no Android, o caminho mais comum para consumir GraphQL é usar Apollo Kotlin. A biblioteca gera modelos tipados a partir do schema, integra bem com coroutines, pode expor resultados como Flow e oferece cache normalizado para cenários em que múltiplas queries compartilham os mesmos objetos. Isso combina com arquiteturas modernas baseadas em MVVM, Clean Architecture e apps offline-first.

Este guia mostra quando GraphQL faz sentido no Android, como estruturar o client com Kotlin, quais cuidados tomar com cache e erros, e como transformar esse conhecimento em diferencial para projetos e entrevistas em 2026.

Quando GraphQL vale a pena no Android?

GraphQL não é automaticamente melhor que REST. Para uma API pequena, estável e com endpoints bem desenhados, REST continua simples, barato e suficiente. O ganho do GraphQL aparece quando o app tem telas ricas, múltiplos consumidores, evolução frequente de produto ou necessidade de compor dados sem criar um endpoint novo para cada variação de tela.

No Android, ele costuma valer a pena quando:

  • uma mesma entidade aparece em várias telas com subconjuntos diferentes de campos;
  • o app precisa reduzir chamadas em redes móveis instáveis;
  • o backend atende Android, iOS, web e parceiros externos;
  • o time quer tipagem forte entre contrato e client;
  • features mudam rápido e exigem evolução gradual de campos;
  • a empresa já usa GraphQL em BFFs, gateways ou federação.

O ponto principal é tratar GraphQL como contrato de produto, não como moda de API. Se o backend expõe um schema confuso, sem paginação, sem política de erro e sem observabilidade, o app Android só troca um tipo de acoplamento por outro.

Configurando Apollo Kotlin no Gradle

Em um projeto Android moderno, o Apollo entra pelo build.gradle.kts. A versão exata deve acompanhar a documentação oficial e o padrão do seu time, mas a estrutura é parecida:

plugins {
    id("com.android.application")
    kotlin("android")
    id("com.apollographql.apollo") version "4.0.0"
}

apollo {
    service("api") {
        packageName.set("br.dev.kotlin.app.graphql")
        schemaFile.set(file("src/main/graphql/schema.graphqls"))
    }
}

As queries ficam em arquivos .graphql, normalmente em src/main/graphql. Por exemplo:

query PerfilResumo($id: ID!) {
  usuario(id: $id) {
    id
    nome
    avatarUrl
    plano
  }
}

O Apollo gera classes Kotlin para essa operação. Isso reduz erros comuns de string solta, nome de campo digitado errado e parsing manual de JSON. Quando o schema muda, o build acusa incompatibilidades antes de o app chegar ao QA.

Criando um ApolloClient idiomático

O client deve ser configurado em um ponto central, normalmente via injeção de dependência. Se você usa Hilt, Koin ou injeção manual, a ideia é a mesma: criar uma instância reaproveitável, com endpoint, headers, interceptors e política de cache.

fun criarApolloClient(tokenProvider: TokenProvider): ApolloClient {
    return ApolloClient.Builder()
        .serverUrl("https://api.exemplo.com/graphql")
        .addHttpHeader("Accept", "application/json")
        .addInterceptor { request, chain ->
            val token = tokenProvider.tokenAtual()
            val autenticada = request.newBuilder()
                .addHttpHeader("Authorization", "Bearer $token")
                .build()
            chain.proceed(autenticada)
        }
        .build()
}

Em produção, não deixe token espalhado em ViewModel, tela Compose ou helper global. O client pertence à camada de dados. A UI deveria depender de casos de uso ou repositories que já devolvem estado de tela pronto.

Repository com coroutines e Flow

Uma chamada simples pode usar suspend:

class PerfilRepository(
    private val apolloClient: ApolloClient,
) {
    suspend fun buscarPerfil(id: String): PerfilResumo {
        val response = apolloClient
            .query(PerfilResumoQuery(id = id))
            .execute()

        val usuario = response.data?.usuario
            ?: throw IllegalStateException("Perfil não encontrado")

        return PerfilResumo(
            id = usuario.id,
            nome = usuario.nome,
            avatarUrl = usuario.avatarUrl,
            plano = usuario.plano,
        )
    }
}

Para telas que precisam reagir a cache, refresh ou mudanças locais, Flow costuma deixar o desenho mais claro. A ViewModel observa o repository, transforma domínio em estado de UI e a tela Compose apenas renderiza.

class PerfilViewModel(
    private val repository: PerfilRepository,
) : ViewModel() {
    private val usuarioId = MutableStateFlow<String?>(null)

    val uiState: StateFlow<PerfilUiState> = usuarioId
        .filterNotNull()
        .flatMapLatest { id -> repository.observarPerfil(id) }
        .map { perfil -> PerfilUiState.Carregado(perfil) }
        .catch { erro -> emit(PerfilUiState.Erro(erro.message.orEmpty())) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = PerfilUiState.Carregando,
        )
}

Esse modelo conversa bem com Jetpack Compose, porque evita callback solto e concentra loading, erro e dados em um único estado observável.

Cache normalizado não é cache mágico

Um dos recursos mais poderosos do Apollo é o cache normalizado. Em vez de guardar apenas a resposta bruta de cada query, o client pode armazenar entidades por identificador. Se duas queries retornam o mesmo Usuario, atualizar uma pode refletir na outra.

Isso é útil, mas exige disciplina. O schema precisa expor IDs estáveis. O time precisa decidir quando ler da rede, quando aceitar cache e quando invalidar dados. Sem essa política, a tela pode exibir informação antiga em momentos sensíveis.

Uma estratégia prática:

  • use cache para dados de leitura frequente e baixa criticidade, como perfil resumido, categorias e preferências visuais;
  • force rede para ações críticas, como pagamento, confirmação de pedido ou mudança de permissão;
  • registre claramente o estado “atualizando” quando a UI mostra cache enquanto busca dados novos;
  • combine cache GraphQL com Room apenas quando houver uma razão clara para consulta local, histórico ou modo offline robusto.

Se o app precisa funcionar sem internet de verdade, veja GraphQL como camada de sincronização, não como substituto automático para banco local. A arquitetura offline-first ainda costuma precisar de Room, fila de operações pendentes e WorkManager.

Tratamento de erros em GraphQL

REST costuma sinalizar falhas principalmente por status HTTP. GraphQL pode retornar HTTP 200 com campo errors quando parte da operação falhou. Isso muda o desenho do tratamento de erro.

No Android, separe pelo menos quatro categorias:

  1. erro de rede, como timeout, DNS ou ausência de conexão;
  2. erro HTTP, como 401, 403 ou 500;
  3. erro GraphQL de negócio, vindo no array errors;
  4. dado ausente ou incompatível com o estado esperado pela tela.

Não transforme tudo em “algo deu errado”. Para o usuário, uma sessão expirada pede login; uma conexão instável pede tentar novamente; uma regra de negócio pode pedir ajuste de formulário; um bug de contrato deve ir para observabilidade.

Também vale mapear erros para tipos de domínio:

sealed interface FalhaPerfil {
    data object SemInternet : FalhaPerfil
    data object NaoAutorizado : FalhaPerfil
    data class RegraNegocio(val mensagem: String) : FalhaPerfil
    data class Desconhecida(val causa: Throwable?) : FalhaPerfil
}

Esse tipo de modelagem deixa testes mais simples e evita try/catch repetido em cada ViewModel.

Testando queries e repositories

Testar GraphQL no Android não precisa depender sempre de servidor real. Para lógica de mapeamento, use respostas falsas ou mocks do client. Para integração, mantenha testes que validem operações críticas contra um ambiente controlado ou contrato gerado.

O que vale testar:

  • query bem-sucedida mapeia corretamente para modelo de domínio;
  • resposta com errors vira falha de negócio;
  • ausência de dados obrigatórios não quebra a tela silenciosamente;
  • ViewModel emite loading, sucesso e erro na ordem esperada;
  • cache não mostra dados de um usuário para outro após logout.

Se o app usa Flow, ferramentas como Turbine ajudam a validar emissões. O guia de testes em Kotlin aprofunda esse ponto com kotlinx-coroutines-test, MockK e estratégias para código assíncrono.

GraphQL em entrevistas e vagas Android

Em entrevistas, saber “chamar uma query” é só o começo. O que diferencia uma pessoa Android mais forte é explicar trade-offs: por que GraphQL reduz overfetching, como lidar com erros parciais, quando cache normalizado ajuda, quando REST é suficiente e como evitar acoplamento excessivo entre schema e UI.

Um bom projeto de portfólio pode ter uma tela de catálogo, uma tela de perfil e uma tela de detalhes consumindo GraphQL, com repository, estados de UI, testes e tratamento explícito de erro. Se você quer reforçar o lado de carreira, conecte esse projeto ao seu portfólio de desenvolvedor Kotlin e ao roadmap Android.

Para quem também trabalha no backend, vale comparar com APIs REST em outras linguagens. O ecossistema de Go para APIs de alta performance e o de Python para prototipagem de APIs ajudam a entender por que GraphQL costuma entrar como camada de composição, não como substituto universal para todo endpoint.

Checklist prático para produção

Antes de colocar GraphQL em produção no Android, revise:

  • schema versionado e revisado junto com backend;
  • queries pequenas, orientadas a tela, sem campos inúteis;
  • autenticação centralizada no ApolloClient;
  • mapeamento de DTO gerado para modelos de domínio;
  • tratamento separado para rede, HTTP, GraphQL e dados ausentes;
  • política clara de cache e logout;
  • testes de repository e ViewModel;
  • logs sem dados sensíveis;
  • métricas de latência, falhas e tamanho de resposta.

GraphQL com Kotlin funciona muito bem quando o time trata contrato, cache, erros e arquitetura como partes do mesmo sistema. Apollo Kotlin entrega a base técnica; a qualidade vem de usar essa base sem jogar regra de negócio na tela, sem esconder falhas parciais e sem transformar cache em fonte de inconsistência. Em 2026, esse é um conhecimento valioso para Android, backend-for-frontend e equipes que precisam evoluir produto rápido sem perder segurança no app mobile.