Consumindo REST API no Android com o Ktor

Consumindo REST API no Android com o Ktor

No curso Jetpack Compose: Comunicação com REST API, aprendemos como fazer requisições a uma API a partir de um aplicação Android. Para fazer isso, utilizamos o Retrofit - uma lib famosa na comunidade Android -, porém, vimos que temos uma possibilidade de implementação semelhante com o Ktor - uma lib desenvolvida em Kotlin e baseada em Coroutines!

Pensando em demonstrar a implementação com o Ktor, vou mostrar para você como implementar o desafio proposto no curso de REST API que pede para carregar o endereço automaticamente a partir do CEP consumindo a API do viacep, bora lá?

Passo 1: Instalando o Ktor no projeto Android

Como primeiro passo, adicione as dependências do Ktor no Android no app/build.gradle:

dependencies {
        //other dependencies

    def ktor_version = '2.2.4'

    implementation "io.ktor:ktor-client-android:$ktor_version"
    implementation "io.ktor:ktor-client-logging:$ktor_version"
    implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
    implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
    implementation "org.slf4j:slf4j-android:1.7.36"
}

Agora vamos entender para que serve cada uma das dependências:

  • io.ktor:ktor-client-android → Engine de cliente HTTP para Android;
  • io.ktor:ktor-client-logging → Permite adicionar o logging das requisições feitas pelo cliente HTTP;
  • io.ktor:ktor-client-content-negotiation → Responsável em configurar qual o tipo de conteúdo o cliente vai lidar, por exemplo, JSON ou XML;
  • io.ktor:ktor-serialization-kotlinx-json → Conversor de objetos análogo ao Gson, Moshi etc;
  • org.slf4j:slf4j-android → Implementação de logger utilizado para realizar o logging.

Após adicionar as dependências, configure as necessárias etapas a mais para a serialização da lib io.ktor:ktor-serialization-kotlinx-json funcionar corretamente:

  • Adicione o plugin no app/build.gradle:
    plugins {
        //other plugins
        id 'kotlinx-serialization'
    }
  • Adicione repositório e dependência no classpath no build.gradle:
    buildscript {
        repositories { mavenCentral() }
        dependencies {
            classpath "org.jetbrains.kotlin:kotlin-serialization:1.8.10"
        }
    }

Lembre-se de adicionar o buildscript no topo do arquivo.

Com a instalação inicial realizada, sincronize o projeto e você terá acesso a tudo que precisa para configurar o Ktor.

Banner da Escola de Mobile: Matricula-se na escola de Mobile. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

Passo 2: Configurando o cliente HTTP do Ktor

Para configurar o cliente do Ktor, faça seguinte código:

HttpClient(Android) {
  install(Logging) {
      level = LogLevel.ALL
  }
  install(ContentNegotiation) {
      json(Json {
          ignoreUnknownKeys = true
      })
  }
}

Agora vamos entender o que esse código faz:

  • HttpClient → Referência de cliente HTTP do ktor para realizar as requisições;
    • Android → Engine que determina qual cliente HTTP será utilizado por baixo dos panos, por exemplo, HttpsUrlConnection, OkHttp etc. Essa implementação de Engine tem o foco em dar suporte para versões do Android mais antigas. Para saber mais detalhes, recomendo que consulte a documentação para conhecer as possibilidades;
  • install() → Instala plugins específicos ao Ktor;
    • Logging → Plugin para logging do cliente HTTP;
      • level → Determina o nível de logging do plugin - nesse caso, LogLevel.ALL seria um log mais completo;
    • ContentNegotiation → Plugin para configurar o content negotiation da requisição;
      • json() → Registra que o tipo de conteúdo da requisição ContentType é para json;
        • Json { } → Cria a instância de Json do serializador do Kotlin e permite configurar via lambda, como por exemplo o ignoreUnknownKeys que ignora campos desconhecidos durante o parsing.

Essa configuração é útil em situações nas quais o objeto espera apenas alguns campos do JSON de resposta; se essa configuração não for feita, uma exception é lançada durante o parsing caso o objeto não tenha todos os campos esperados.

Independente da REST API que você vai consumir, essa será a configuração base genérica.

Agora que sabemos como adicionar o Ktor em qualquer projeto Android, vamos ao projeto de exemplo que usaremos no artigo.

Passo 3: Conhecendo o projeto de exemplo do artigo

Para a demonstração, vamos considerar o código com Retrofit do desafio de buscar o endereço automaticamente pelo CEP.

Em resumo, vamos modificar o código final do desafio e utilizar o Ktor. Assim, faça o seguinte:

  • Acesse o código fonte a partir da branch desafio-endereco;
  • Rode o aplicativo e verifique se ele apresenta o seguinte comportamento:
Imagem que mostra uma tela do aplicativo. A tela é um formulário de cadastro de cliente com o título “Cadastro de endereço” e solicita os dados de CEP, logradouro, número, bairro, cidade, estado e complemento. Por fim, na parte inferior da tela, há um botão na cor roxa com a opção de salvar os dados do cadastro.

Se tudo estiver rodando conforme o gif acima, prossiga para o próximo passo, pois vamos mexer neste projeto e fazer a implementação de requisição com o Ktor!

Passo 4: Implementando a requisição com o Ktor

Implementar a requisição do Ktor é similar ao Retrofit; a grande diferença é que, no Retrofit, implementamos uma interface que geralmente tem o padrão com sufixo Service:

interface AddressService {

    @GET("{cep}/json")
    suspend fun findAddress(
        @Path("cep") cep: String
    ): AddressResponse

}

Já no Ktor, podemos utilizar um padrão de nossa preferência, por exemplo, AddressRestApi:

package br.com.alura.anyflix.network.restapi

import br.com.alura.anyflix.network.responses.AddressResponse
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import javax.inject.Inject
import javax.inject.Singleton

private const val BASE_URL = "https://viacep.com.br/ws"

@Singleton
class AddressRestApi @Inject constructor(
    private val client: HttpClient
) {

    suspend fun findAddress(cep: String): AddressResponse {
        return client.get("$BASE_URL/$cep/json").body()
    }

}

Observe que, no Ktor, podemos utilizar uma classe em vez de uma interface (padrão do Retrofit), e também, é nesse código do Ktor em que fazemos as configurações de URL base e end-points. Agora, vamos entender o que foi feito:

  • get() → Faz a requisição GET a partir da URL via coroutine;
    • body() → Retorna o corpo da requisição de acordo com o tipo esperado, nesse caso, AddressResponse.

Caso não tenha uma referência que representa o corpo, pode retornar como String.

A configuração de injeção de dependência é feita com o Hilt, por isso temos o @Singleton e o @Inject. Inclusive, a instância do HttpClient é oferecida pela configuração do módulo RestApiModule:

@Module
@InstallIn(SingletonComponent::class)
object RestApiModule {

        //rest of the code

    @Provides
    @Singleton
    fun provideHttpClient(): HttpClient {
        return HttpClient(Android) {
            install(Logging) {
                level = LogLevel.ALL
            }
            install(ContentNegotiation) {
                json(Json {
                    ignoreUnknownKeys = true
                })
            }
        }
    }

}

Pronto, temos tudo para testar o Ktor e vamos implementar o código que integra o Ktor com o App.

Passo 5: Implementando o Ktor no repositório

Para fazer o primeiro teste, ajuste o repositório para utilizar o AddressRestApi em vez do AddressService:

class AddressRepository @Inject constructor(
//    private val service: AddressService,
    private val restApi: AddressRestApi
) {

    suspend fun findAddress(cep: String): Address? {
        return try {
//            service.findAddress(cep).toAddress()
            restApi.findAddress(cep).toAddress()
        } catch (e: HttpException) {
            Log.e(TAG, "findAddress: ", e)
            null
        } catch (e: ConnectException) {
            Log.e(TAG, "findAddress: ", e)
            null
        }
    }

}

Pronto! Isso é o suficiente para utilizar o Ktor! O que você achou?

Para saber mais

Dica: Além de realizar requisições HTTP, o Ktor é uma ferramenta capaz de implementar aplicações servidoras, como é o caso de REST APIs. Se você tem interesse em aprender esse tipo de implementação, sugiro que confira o artigo Utilizando o Ktor para criar um CRUD e REST API com Kotlin, aqui da Alura.

Conclusão

Neste artigo aprendemos:

  • Instalar o cliente do Ktor em um projeto Android
  • Configurar o cliente HTTP do Ktor junto com o Hilt
  • Implementar requisição GET com o Ktor
  • Integrar a requisição do Ktor com o repositório do projeto

Caso deseje verificar o código final junto com o projeto de exemplo, você pode consultar a branch ktor no repositório do GitHub, ou então, acessar o commit com as mudanças.

Aproveite esse momento para colocar em prática o que aprendeu e compartilhe suas impressões com a gente nas redes sociais ou Discord. 😉

Bons estudos e até mais!

Alex Felipe
Alex Felipe

Alex é instrutor e desenvolvedor e possui experiência em Java, Kotlin, Android. Criador de mais de 40 cursos, como Kotlin, Flutter, Android, persistência de dados, comunicação com Web API, personalização de telas, testes automatizados, arquitetura de Apps e Firebase. É expert em Programação Orientada a Objetos, visando sempre compartilhar as boas práticas e tendências do mercado de desenvolvimento de software. Atuou 2 anos como editor de conteúdo no blog da Alura e hoje ainda escreve artigos técnicos.

Veja outros artigos sobre Mobile