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 promocional da Semana Carreira Tech, organizada pela Alura e FIAP. Texto: 'Descubra como graduações tech estão transformando o futuro. Cinco lives gratuitas para você mergulhar nas áreas mais transformadoras da atualidade, desde o que se estuda nas graduações até a prática do mercado. Garanta sua vaga de 01 a 05 de julho.' Imagem de profissionais usando equipamentos tecnológicos avançados, como óculos de realidade aumentada. Botão destacado com a chamada 'Garanta sua vaga'. Logotipos da Alura e FIAP no canto superior direito.

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. Atualmente cria conteúdo no canal https://www.youtube.com/@AlexFelipeDev.

Veja outros artigos sobre Mobile