Biblioteca HTTP de combustível com Kotlin

Biblioteca HTTP de combustível com Kotlin

1. Visão geral

In this tutorial, we’ll have a look at the Fuel HTTP Library, que é, nas palavras do autor, a biblioteca de rede HTTP mais fácil para Kotlin / Android. Além disso, a biblioteca também pode ser usada em Java.

Os principais recursos da biblioteca incluem:

  • Suporte para verbos HTTP básicos (GET, POST, DELETE etc.), solicitações assíncronas e de bloqueio

  • Capacidade de baixar e enviar um arquivo (multipart/form-data)

  • Possibilidade de gerenciar configuração global

  • Módulos de serialização de objetos embutidos (Jackson, Gson, Mhosi, Forge)

  • Suporte para o módulo de corrotinas do Kotlin e RxJava 2.x

  • Configure facilmente o padrão de design do roteador

2. Dependências

The library is composed of different modules so we can easily include the features we need. Alguns deles incluem:

  • Um módulo para suporte a RxJava e Kotlin's Coroutines

  • Um módulo para suporte a Android e Android LiveData Architecture Components

  • Quatro módulos dos quais podemos escolher o módulo de serialização de objetos a ser usado - Gson, Jackson, Moshi ou Forge.

Neste tutorial, vamos nos concentrar no módulo principal, os módulos para Coroutines, RxJava e o módulo de serialização Gson:


    com.github.kittinunf.fuel
    fuel
    ${fuel.version}


    com.github.kittinunf.fuel
    fuel-gson
    ${fuel.version}


    com.github.kittinunf.fuel
    fuel-rxjava
    ${fuel.version}


    com.github.kittinunf.fuel
    fuel-coroutines
    ${fuel.version}

Você pode encontrar as versões mais recentes emJFrog Bintray.

3. Fazendo pedidos

To make a request, Fuel provides a String extension. Além disso e como alternativa, podemos usar a classeFuel que possui um método para cada verbo HTTP.

O Fuel suporta todos os verbos HTTP, exceto PATCH. O motivo é queFuel’s HttpClient is a wrapper over java.net.HttpUrlConnection which doesn’t support PATCH.

Para contornar o problema, o HttpClient converte solicitações PATCH em uma solicitação POST e adiciona um cabeçalhoX-HTTP-Method-Override: PATCH, portanto, precisaremos ter certeza de que nossas APIs estão configuradas para aceitar este cabeçalho por padrão.

Para explicar os recursos do Fuel, vamos usarhttpbin.org, um serviço de solicitação e resposta HTTP simples eJsonPlaceholder - uma API online falsa para teste e prototipagem.

3.1. Solicitação GET

Vamos começar a criar uma solicitação HTTPGET simples no modo assíncrono:

"http://httpbin.org/get".httpGet().response {
  request, response, result ->
    //response handling
}

UsarhttpGet() sobreString nos dá umTriple<Request, Response, Result>.

TheResultis a functional-style data structure that contains the result of the operation (success or failure). Vamos revisitar a estrutura de dadosResult em um estágio posterior.

Também podemos fazer a solicitação no modo de bloqueio:

val (request, response, result) = "http://httpbin.org/get"
  .httpGet().response()

Observe que os parâmetros retornados são os mesmos da versão assíncrona, mas, neste caso, o encadeamento que fez a solicitação está bloqueado.

Além disso, existe a possibilidade de usar parâmetros de URL codificados:

val (request, response, result) =
  "https://jsonplaceholder.typicode.com/posts"
  .httpGet(listOf("userId" to "1")).response()
  // resolve to https://jsonplaceholder.typicode.com/posts?userId=1

O métodohttpGet() (e outros semelhantes) pode receber umList<Pair> para codificar parâmetros de URL.

3.2. Solicitação POST

Podemos fazer solicitações POST da mesma forma que para GET, usandohttpPost() ou usando o métodopost() da classeFuel:

"http://httpbin.org/post".httpPost().response{
  request, response, result ->
    //response handling
}
val (request, response, result) = Fuel.post("http://httpbin.org/post")
  .response()

Se tivermos um corpo, podemos colocá-lo por meio do métodobody() no formato de string JSON:

val bodyJson = """
  { "title" : "foo",
    "body" : "bar",
    "id" : "1"
  }
"""
val (request, response, result) = Fuel.post("https://jsonplaceholder.typicode.com/posts")
  .body(bodyJson)
  .response()

3.3. Outros verbos

Igual ao GET e POST, existe um método para cada um dos verbos restantes:

Fuel.put("http://httpbin.org/put")
Fuel.delete("http://httpbin.org/delete")
Fuel.head("http://httpbin.org/get")
Fuel.patch("http://httpbin.org/patch")

Lembre-se de queFuel.patch() executará uma solicitação POST com um cabeçalhoX-HTTP-Method-Override: PATCH.

4. Configuração

A biblioteca fornece um objeto singleton -FuelManager.instance - para gerenciar a configuração global.

Vamos configurar um caminho base, alguns cabeçalhos e parâmetros comuns. Além disso, vamos configurar alguns interceptores.

4.1. BasePath

UsandobasePath variable, podemos definir um caminho comum para todas as solicitações.

FuelManager.instance.basePath = "http://httpbin.org"
val (request, response, result) = "/get".httpGet().response()
// will perform GET http://httpbin.org/get

4.2. Cabeçalhos

Além disso, podemos gerenciar cabeçalhos HTTP comuns usando o mapabaseHeaders:

FuelManager.instance.baseHeaders = mapOf("OS" to "Debian")

De forma alternativa, se quisermos definir um cabeçalho local, podemos usar o métodoheader() na solicitação:

val (request, response, result) = "/get"
  .httpGet()
  .header(mapOf("OS" to "Debian"))
  .response()

4.3. Params

Finalmente, também podemos definir parâmetros comuns usando a listabaseParams:

FuelManager.instance.baseParams = listOf("foo" to "bar")

4.4. Outras opções

Existem muitas outras opções que podemos gerenciar por meio deFuelManager:

  • keystore which énull por padrão

  • socketFactory que será fornecido pelo usuário ou derivado dekeystore se não fornull

  • hostnameVerifier que é definido por padrão para usar aquele fornecido pela classeHttpsURLConnection

  • requestInterceptors eresponseInterceptors

  • timeout etimeoutRead para um pedido

4.5. Request/Response Interceptors

Em relação aos interceptores,we can add supplied request/response interceptors like cUrlLoggingRequestInterceptors(), or we can define ours:

FuelManager.instance.addRequestInterceptor(cUrlLoggingRequestInterceptor())
FuelManager.instance.addRequestInterceptor(tokenInterceptor())
fun tokenInterceptor() = {
    next: (Request) -> Request ->
    { req: Request ->
        req.header(mapOf("Authorization" to "Bearer AbCdEf123456"))
        next(req)
    }
}

5. Tratamento de respostas

Anteriormente, introduzimos uma estrutura de dados funcional -Result - que representa o resultado da operação (sucesso ou falha).

Trabalhar comResult é fácil, é uma classe de dados que pode conter a resposta emByteArray,String, JSON, ou um objetoT genérico:

fun response(handler: (Request, Response, Result) -> Unit)
fun responseString(handler: (Request, Response, Result) -> Unit)
fun responseJson(handler: (Request, Response, Result) -> Unit)
fun  responseObject(deserializer: ResponseDeserializable,
  handler: (Request, Response, Result) -> Unit)

Vamos obter uma resposta comoString para ilustrar isso:

val (request, response, result) = Fuel.post("http://httpbin.org/post")
  .responseString()
val (payload, error) = result // payload is a String

Observe que a resposta no formato JSON requer dependências do Android.


    com.github.kittinunf.fuel
    fuel-android
    ${fuel.version}

6. Serialização / desserialização JSON

O Fuel fornece suporte integrado para desserialização de resposta com quatro métodos que, dependendo de nossas necessidades e da biblioteca de análise JSON que escolhemos, somos obrigados a implementar:

public fun deserialize(bytes: ByteArray): T?
public fun deserialize(inputStream: InputStream): T?
public fun deserialize(reader: Reader): T?
public fun deserialize(content: String): T?

Ao incluir o módulo Gson, podemos desserializar e serializar objetos:

data class Post(var userId:Int,
                var id:Int,
                var title:String,
                var body:String){

    class Deserializer : ResponseDeserializable> {
        override fun deserialize(content: String): Array
          = Gson().fromJson(content, Array::class.java)
    }
}

Podemos desserializar objetos com o desserializador personalizado:

"https://jsonplaceholder.typicode.com/posts"
  .httpGet().responseObject(Post.Deserializer()){
    _,_, result ->
      val postsArray = result.component1()
  }

Ou através do responseObject , que usa o desserializador interno do Gson:

"https://jsonplaceholder.typicode.com/posts/1"
  .httpGet().responseObject { _, _, result ->
    val post = result.component1()
  }

Por outro lado, podemos serializar usandoGson().toJson():

val post = Post(1, 1, "Lorem", "Lorem Ipse dolor sit amet")

val (request, response, result)
  = Fuel.post("https://jsonplaceholder.typicode.com/posts")
    .header("Content-Type" to "application/json")
    .body(Gson().toJson(post).toString())

É importante definir oContent-Type, caso contrário, o servidor pode receber o objeto dentro de outro objeto JSON.

Eventualmente, de maneira semelhante, podemos fazer isso usando as dependências de Jackson, Moshi ou Forge.

7. Baixar e enviar arquivo

A biblioteca Fuel inclui todos os recursos necessários para baixar e fazer upload de arquivos.

7.1. Baixar

Com o métododownload(), podemos facilmente baixar um arquivo e salvá-lo no arquivo retornado pordestination() lambda:

Fuel.download("http://httpbin.org/bytes/32768")
  .destination { response, url ->
    File.createTempFile("temp", ".tmp")
  }

We can also download a file with a progress handler:

Fuel.download("http://httpbin.org/bytes/327680")
  .progress { readBytes, totalBytes ->
    val progress = readBytes.toFloat() / totalBytes.toFloat()
    //...
  }

7.2. Envio

Da mesma forma,we can upload a file using upload() method, indicando o arquivo a ser carregado com o métodosource():

Fuel.upload("/upload").source { request, url ->
  File.createTempFile("temp", ".tmp")
}

Observe queupload() usa o verbo POST por padrão. Se quisermos usar outro verbo HTTP, podemos especificá-lo:

Fuel.upload("/upload", Method.PUT).source { request, url ->
  File.createTempFile("temp", ".tmp")
}

Além disso, podemos fazer upload de vários arquivos usando o métodosources(), que aceita uma lista de arquivos:

Fuel.upload("/post").sources { request, url ->
  listOf(
    File.createTempFile("temp1", ".tmp"),
    File.createTempFile("temp2", ".tmp")
  )
}

Por último, podemos fazer upload de um blob de dados de umInputStream:

Fuel.upload("/post").blob { request, url ->
  Blob("filename.png", someObject.length, { someObject.getInputStream() })
}

8. Suporte RxJava e Corrotinas

O Fuel fornece suporte para RxJava e Coroutines, duas formas de escrever código assíncrono, sem bloqueio.

RxJava é uma implementação Java VM deReactive Extensions, uma biblioteca para compor programas assíncronos e baseados em eventos.

Ele estende oObserver pattern para suportar sequências de dados / eventos e adiciona operadores que permitem compor sequências juntas declarativamente sem se preocupar com sincronização, segurança de thread e estruturas de dados simultâneas.

Kotlin’s Coroutines são como threads leves e, como tal, podem rodar em paralelo, esperar uns pelos outros e se comunicar ... A maior diferença é que as corrotinas são muito baratas; podemos criar milhares deles e pagar muito pouco em termos de memória.

8.1. RxJava

Para dar suporte ao RxJava 2.x, o Fuel fornece seis extensões:

fun Request.rx_response(): Single>>
fun Request.rx_responseString(charset: Charset): Single>>
fun  Request.rx_responseObject(deserializable: Deserializable):
  Single>>
fun Request.rx_data(): Single>
fun Request.rx_string(charset: Charset): Single>
fun  Request.rx_object(deserializable: Deserializable): Single>

Observe que, para suportar todos os diferentes tipos de resposta, cada método retorna umSingle<Result<>>. diferente

Podemos facilmente usar métodos “Rx” invocando o mais relevante em umRequest:

 "https://jsonplaceholder.typicode.com/posts?id=1"
  .httpGet().rx_object(Post.Deserializer()).subscribe{
    res, throwable ->
      val post = res.component1()
  }

8.2. Coroutines

Com o módulo de co-rotinas,Fuel provides extension functions to wrap a response inside a coroutine and handle its result.

Para usar corrotinas, APIs semelhantes são disponibilizados, por exemplo,responseString() tornou-seawaitStringResponse():

runBlocking {
    Fuel.get("http://httpbin.org/get").awaitStringResponse()
}

Ele também fornece métodos úteis para manipular objetos diferentes deString ouByteArray  (awaitByteArrayResponse()) usandoawaitObject(), awaitObjectResult() or awaitObjectResponse():

runBlocking {
    Fuel.get("https://jsonplaceholder.typicode.com/posts?id=1")
      .awaitObjectResult(Post.Deserializer())
}

Lembre-se de que as corrotinas de Kotlin são experimentais, o que significa que podem ser alteradas nos próximos lançamentos.

9. Roteamento de API

Por fim, mas não menos importante, para lidar com as rotas de rede, o Fuel fornece suporte implementando o padrão de design do roteador.

Com o padrão de roteador, podemos centralizar o gerenciamento da API usando a interfaceFuelRouting, que fornece uma combinação de métodos para definir o verbo, caminho, parâmetros e cabeçalhos HTTP apropriados de acordo com o endpoint chamado.

A interface define cinco propriedades pelas quais é possível configurar nosso roteador:

sealed class PostRoutingAPI : FuelRouting {
    class posts(val userId: String, override val body: String?): PostRoutingAPI()
    class comments(val postId: String, override val body: String?): PostRoutingAPI()
    override val basePath = "https://jsonplaceholder.typicode.com"

    override val method: Method
        get() {
            return when(this) {
                is PostRoutingAPI.posts -> Method.GET
                is PostRoutingAPI.comments -> Method.GET
            }
        }

    override val path: String
        get() {
            return when(this) {
                is PostRoutingAPI.posts -> "/posts"
                is PostRoutingAPI.comments -> "/comments"
            }
        }

    override val params: List>?
        get() {
            return when(this) {
                is PostRoutingAPI.posts -> listOf("userId" to this.userId)
                is PostRoutingAPI.comments -> listOf("postId" to this.postId)
            }
        }

    override val headers: Map?
        get() {
            return null
        }
}

Para escolher o verbo HTTP a utilizar temos a propriedademethod, da mesma forma, podemos sobrescrever a propriedadepath, de modo a escolher o caminho apropriado.

Ainda mais com a propriedadeparams, temos a oportunidade de definir os parâmetros da solicitação e se precisarmos definir cabeçalhos HTTP, podemos fazer isso substituindo a propriedade em questão.

Portanto, nós o usamos da mesma maneira que fizemos em todo o tutorial com o métodorequest():

Fuel.request(PostRoutingAPI.posts("1",null))
  .responseObject(Post.Deserializer()) {
      request, response, result ->
        //response handling
  }
Fuel.request(PostRoutingAPI.comments("1",null))
  .responseString { request, response, result ->
      //response handling
  }

10. Conclusão

Neste artigo, mostramos a Biblioteca HTTP Fuel para Kotlin e seus recursos mais úteis para qualquer caso de uso.

A biblioteca está em constante evolução, portanto, dê uma olhada em seu repositórioGitHub - para acompanhar os novos recursos.

Como de costume, todos os trechos de código mencionados no tutorial podem ser encontrados em nossoGitHub repository.