Picu Perpustakaan HTTP dengan Kotlin

1. Ikhtisar

Dalam tutorial ini, kita akan melihat Perpustakaan HTTP Bahan Bakar , yang, menurut penulis, perpustakaan jaringan HTTP termudah untuk Kotlin / Android. Selain itu, perpustakaan juga dapat digunakan di Java.

Fitur utama perpustakaan ini meliputi:

  • Dukungan untuk kata kerja HTTP dasar (GET, POST, DELETE, dll.) Baik permintaan asinkron maupun pemblokiran
  • Kemampuan untuk mengunduh dan mengunggah file ( multipart / form-data )
  • Kemungkinan untuk mengelola konfigurasi global
  • Modul serialisasi objek bawaan (Jackson, Gson, Mhosi, Forge)
  • Dukungan untuk modul coroutines Kotlin dan RxJava 2.x
  • Atur pola desain Router dengan mudah

2. Dependensi

Pustaka terdiri dari modul yang berbeda sehingga kita dapat dengan mudah memasukkan fitur yang kita butuhkan. Beberapa di antaranya adalah:

  • Modul untuk dukungan Coroutines RxJava dan Kotlin
  • Modul untuk dukungan Komponen Arsitektur LiveData Android dan Android
  • Empat modul dari mana kita dapat memilih modul serialisasi objek untuk digunakan - Gson, Jackson, Moshi atau Forge.

Dalam tutorial ini, kita akan fokus pada modul inti, modul untuk Coroutines, RxJava dan modul serialisasi 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} 

Anda dapat menemukan versi terbaru di JFrog Bintray.

3. Membuat Permintaan

Untuk membuat permintaan, Fuel menyediakan ekstensi String . Sebagai tambahan dan sebagai alternatif, kita dapat menggunakan kelas Fuel yang memiliki metode untuk setiap verba HTTP.

Bahan bakar mendukung semua kata kerja HTTP kecuali PATCH. Alasannya adalah bahwa HttpClient Fuel adalah pembungkus dari java.net.HttpUrlConnection yang tidak mendukung PATCH.

Untuk mengatasi masalah, HttpClient mengonversi permintaan PATCH menjadi permintaan POST dan menambahkan header X-HTTP-Method-Override: PATCH , jadi kita perlu memastikan API kita dikonfigurasi untuk menerima header ini secara default.

Untuk menjelaskan fitur-fitur Fuel, kita akan menggunakan httpbin.org, sebuah permintaan HTTP sederhana dan layanan respon, dan JsonPlaceholder - API online palsu untuk pengujian dan pembuatan prototipe.

3.1. DAPATKAN Permintaan

Mari mulai membuat permintaan HTTP GET sederhana dalam mode async:

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

Menggunakan httpGet () melalui String memberi kita Triple .

The Hasil adalah struktur data fungsional-gaya yang berisi hasil dari operasi (keberhasilan atau kegagalan). Kami akan meninjau kembali struktur data Hasil di tahap selanjutnya.

Kami juga dapat membuat permintaan dalam mode pemblokiran:

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

Perhatikan bahwa parameter yang dikembalikan sama dengan versi async, tetapi dalam kasus ini, utas yang melakukan permintaan diblokir.

Juga, ada kemungkinan untuk menggunakan parameter URL yang disandikan:

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

Metode httpGet () (dan metode serupa lainnya) dapat menerima Daftar untuk menyandikan parameter URL.

3.2. Permintaan POST

Kita dapat membuat permintaan POST dengan cara yang sama seperti untuk GET, menggunakan httpPost () atau menggunakan metode post () dari kelas Bahan Bakar :

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

Jika kita memiliki body, kita bisa meletakkannya melalui metode body () dalam format string JSON:

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

3.3. Kata kerja lain

Sama seperti GET dan POST, ada metode untuk setiap kata kerja yang tersisa:

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

Ingat bahwa Fuel.patch () akan melakukan permintaan POST dengan header X-HTTP-Method-Override: PATCH .

4. Konfigurasi

Pustaka ini menyediakan objek tunggal - FuelManager.instance - untuk mengelola konfigurasi global.

Mari kita konfigurasikan jalur dasar, beberapa header, dan parameter umum. Juga, mari kita konfigurasikan beberapa interseptor.

4.1. BasePath

Menggunakan variabel basePath kita dapat mengatur jalur umum untuk semua permintaan.

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

4.2. Header

Selanjutnya, kita dapat mengelola header HTTP umum menggunakan peta baseHeaders :

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

Sebagai alternatif, jika kita ingin mengatur header lokal, kita bisa menggunakan metode header () atas permintaan:

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

4.3. Params

Terakhir, kami juga dapat menyetel parameter umum menggunakan daftar baseParams :

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

4.4. Pilihan lain

Masih banyak lagi opsi yang bisa kita kelola melalui FuelManager:

  • keystore yang secara default bernilai null
  • socketFactory yang akan disediakan oleh pengguna atau diturunkan dari keystore jika bukan null
  • hostnameVerifier that is set by default to use the one provided by HttpsURLConnection class
  • requestInterceptors and responseInterceptors
  • timeout and timeoutRead for a request

4.5. Request/Response Interceptors

Regarding interceptors, 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. Response Handling

Previously, we introduced a functional data structure – Result – that represents the operation result (success or failure).

Working with Result is easy, it is a data class that can contain the response in ByteArray, String, JSON, or a generic T object:

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) 

Let's get a response as a String to illustrate this:

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

Note that the response in JSON format requires Android dependencies.

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

6. JSON Serialization/Deserialization

Fuel provides built-in support for response deserialization with four methods which, depending on our needs and on the JSON parsing library we choose, we're required to implement:

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

By including the Gson module we can deserialize and serialize objects:

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) } }
    

We can deserialize objects with custom deserializer:

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

Or via responseObject which uses internal Gson deserializer:

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

On the other hand, we can serialize using Gson().toJson():

val post = Post(1, 1, "Lorem", "Lorem Ipse dolor sit amet") val (request, response, result) = Fuel.post("//jsonplaceholder.typicode.com/posts") .header("Content-Type" to "application/json") .body(Gson().toJson(post).toString())

It's important to set the Content-Type, otherwise, the server may receive the object within another JSON object.

Eventually, in a similar way, we can do it by using Jackson, Moshi or Forge dependencies.

7. Download and Upload File

The Fuel library includes all the necessary features to download and upload files.

7.1. Download

With the download() method we can easily download a file and save it into the file returned by the destination() lambda:

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

We can also download a file with a progress handler:

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

7.2. Upload

In the same way, we can upload a file using upload() method, indicating the file to upload with the source() method:

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

Note that upload() uses the POST verb by default. If we want to use another HTTP verb we can specify it:

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

Moreover, we can upload multiple files using sources() method which accepts a list of files:

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

Lastly, we can upload a blob of data from an InputStream:

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

8. RxJava and Coroutines Support

Fuel provides support for RxJava and Coroutines, two way of writing asyncrhonus, non-blocking code.

RxJava is a Java VM implementation of Reactive Extensions, a library for composing asynchronous and event-based programs.

It extends the Observer pattern to support sequences of data/events and adds operators that allow composing sequences together declaratively without worrying about synchronization, thread-safety, and concurrent data structures.

Kotlin's Coroutines are like light-weight threads and, as such, they can run in parallel, wait for each other and communicate… The biggest difference is that coroutines are very cheap; we can create thousands of them, and pay very little in terms of memory.

8.1. RxJava

To support RxJava 2.x, Fuel provides six extensions:

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
         
        
       
      
     
    

Note that, to support all different response types, each method returns a different Single .

We can easily use “Rx” methods by invoking the more relevant one over a Request:

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

8.2. Coroutines

With the coroutines module, Fuel provides extension functions to wrap a response inside a coroutine and handle its result.

To use Coroutines, similar APIs are made available, e.g responseString() became awaitStringResponse():

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

It also provides useful methods for handling objects other than String or ByteArray (awaitByteArrayResponse()) using awaitObject(), awaitObjectResult() or awaitObjectResponse():

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

Remember that Kotlin's Coroutines are experimental, which means that it might be changed in the upcoming releases.

9. API Routing

Last but not least, in order to handle network routes, Fuel provides the support by implementing the Router design pattern.

With the router pattern, we can centralize the management of the API using the FuelRouting interface, which provides a combination of methods for setting the appropriate HTTP verb, path, params and headers according to the endpoint called.

The interface defines five properties by which it is possible to configure our Router:

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 = "//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 } }
    

In order to choose which HTTP verb to use we have method property, likewise, we can override the path property, so as to choose the appropriate path.

Even more with the params property, we have the opportunity to set the parameters of the request and if we need to set HTTP headers, we can do it overriding the concerning property.

Karenanya, kami menggunakannya dengan cara yang sama seperti yang kami lakukan di seluruh tutorial dengan metode request () :

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. Kesimpulan

Dalam artikel ini, kami telah menunjukkan Pustaka HTTP Bahan Bakar untuk Kotlin dan fitur-fiturnya yang lebih berguna untuk semua kasus penggunaan.

Pustaka ini terus berkembang, oleh karena itu, lihat repo GitHub mereka - untuk melacak fitur baru.

Seperti biasa, semua potongan kode yang disebutkan dalam tutorial dapat ditemukan di repositori GitHub kami.