Trang web JSONPlaceholder cung cấp một bộ API REST miễn phí, phù hợp cho việc thử nghiệm và phát triển ứng dụng. Bài viết này trình bày cách sử dụng thư viện Fuel kết hợp với Kotlin Coroutines để thực hiện các thao tác HTTP cơ bản (GET, POST, PUT, DELETE) trên các tài nguyên /posts, đồng thời xử lý dữ liệu JSON một cách an toàn và rõ ràng.
Cấu hình dự án Gradle
Khởi tạo dự án Kotlin JVM bằng IntelliJ IDEA với cấu hình sau:
- Template: Console Application
- Build system: Gradle Kotlin DSL
- JDK: 1.8 trở lên
Cập nhật tập tin build.gradle.kts để khai báo các dependency cần thiết:
dependencies {
testImplementation(kotlin("test-junit"))
val fuelVersion = "2.3.1"
implementation("com.github.kittinunf.fuel:fuel:$fuelVersion")
implementation("com.github.kittinunf.fuel:fuel-coroutines:$fuelVersion")
implementation("com.github.kittinunf.fuel:fuel-gson:$fuelVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
}
Lưu ý: Phiên bản kotlinx-coroutines-core được điều chỉnh thành 1.4.3 (bỏ hậu tố -native-mt vì không dùng trong môi trường JVM thuần túy).
Mô hình dữ liệu Post
Định nghĩa lớp dữ liệu Article tương ứng với cấu trúc JSON từ API:
data class Article(
val userId: Int,
val id: Int,
val title: String,
val content: String
) {
override fun toString(): String =
"Article(userId=$userId, id=$id, title=\"$title\", content=\"${content.replace("\n", "\\n")}\")"
}
Tên trường body trong JSON được ánh xạ thành content trong Kotlin nhằm tăng tính biểu đạt và tránh xung đột với các khái niệm khác trong hệ sinh thái.
Bộ chuyển đổi phản hồi (Deserializer)
Tạo hai lớp tùy chỉnh để xử lý phản hồi JSON thành đối tượng Kotlin:
class ArticleDeserializer : ResponseDeserializable<Article> {
override fun deserialize(content: String): Article =
Gson().fromJson(content, Article::class.java)
}
class ArticleListDeserializer : ResponseDeserializable<List<Article>> {
override fun deserialize(content: String): List<Article> {
val type = object : TypeToken<List<Article>>() {}.type
return Gson().fromJson(content, type)
}
}
Hàm gọi API không chặn (suspend)
Các hàm dưới đây đều mang từ khóa suspend, cho phép gọi bất đồng bộ mà không làm tắc luồng chính:
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
suspend fun fetchSingleArticleRaw(): String =
Fuel.get("$BASE_URL/posts/1").awaitString()
suspend fun fetchSingleArticle(): Article =
Fuel.get("$BASE_URL/posts/1").awaitObject(ArticleDeserializer())
suspend fun fetchTopArticles(limit: Int): List<Article> =
Fuel.get("$BASE_URL/posts").awaitObject(ArticleListDeserializer()).take(limit)
suspend fun submitNewArticle(): Article =
Fuel.post("$BASE_URL/posts", parameters = listOf(
"userId" to "101",
"title" to "Demo Entry",
"content" to "This is a sample post created via Fuel + Coroutines."
)).awaitObject(ArticleDeserializer())
suspend fun modifyArticle(id: Int): Article =
Fuel.put("$BASE_URL/posts/$id", parameters = listOf(
"userId" to "99",
"id" to "$id",
"title" to "Updated Title",
"content" to "Content revised using PUT request."
)).awaitObject(ArticleDeserializer())
suspend fun removeArticle(id: Int): String =
Fuel.delete("$BASE_URL/posts/$id").awaitString()
Các hàm trên minh họa rõ ràng từng loại thao tác HTTP, kèm theo việc truyền tham số dạng danh sách cặp key-value — đảm bảo tính linh hoạt và dễ bảo trì.
Chạy thử nghiệm trong hàm main
Sử dụng runBlocking để chạy khối coroutine đồng bộ trong môi trường console:
fun main() = runBlocking {
println("[RAW] ${fetchSingleArticleRaw()}")
println("[OBJ] ${fetchSingleArticle()}")
println("[LIST] ${fetchTopArticles(2)}")
println("[CREATE] ${submitNewArticle()}")
println("[UPDATE] ${modifyArticle(1)}")
println("[DELETE] ${removeArticle(1)}")
}
Kết quả đầu ra sẽ bao gồm cả chuỗi JSON thô và các thể hiện của lớp Article, giúp kiểm tra tính nhất quán giữa dữ liệu mạng và mô hình miền.