Trang web JSONPlaceholder cung cấp một bộ API REST miễn phí, chuyên dùng cho mục đích kiểm thử và học tập. Bài viết này trình bày cách sử dụng Retrofit 2 kết hợp với Kotlin Coroutines để thực hiện các thao tác cơ bản trên tài nguyên /posts: lấy dữ liệu (GET), tạo mới (POST), cập nhật (PUT) và xóa (DELETE).
Cấu trúc dữ liệu mẫu
Các endpoint trả về JSON tuân thủ lược đồ sau:
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto"
}
Cài đặt dự án
Khởi tạo một project Kotlin JVM bằng Gradle trong IntelliJ IDEA với cấu hình JDK 11 trở lên. Cập nhật build.gradle.kts như sau:
plugins {
kotlin("jvm") version "1.9.20"
application
}
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib"))
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
testImplementation(kotlin("test"))
}
application {
mainClass.set("MainKt")
}
Lớp dữ liệu Post
Định nghĩa lớp dữ liệu tương ứng với cấu trúc JSON, sử dụng data class để tự động sinh equals(), hashCode() và toString():
data class Article(
val userId: Int,
val id: Int,
val title: String,
val content: String
) {
override fun toString(): String = buildString {
append("Article(userId=$userId, id=$id, title=\"$title\", ")
append("content=\"${content.replace("\n", "\\n")}\")")
}
}
Lưu ý: Tên trường khớp trực tiếp với key trong JSON nên không cần thêm @SerializedName. Nếu cần ánh xạ tên khác, có thể dùng:
@SerializedName("body") val content: String
Giao diện API với Retrofit
Tạo interface mô tả các endpoint dưới dạng hàm suspend:
interface ArticleService {
@GET("posts/{id}")
suspend fun fetchSingle(@Path("id") postId: Int): Article
@GET("posts")
suspend fun fetchAll(): List<Article>
@GET
suspend fun fetchRaw(@Url url: String): String
@FormUrlEncoded
@POST("posts")
suspend fun insert(
@Field("userId") authorId: Int,
@Field("title") headline: String,
@Field("body") bodyText: String
): Article
@FormUrlEncoded
@PUT("posts/{id}")
suspend fun modify(
@Path("id") postId: Int,
@Field("userId") authorId: Int,
@Field("title") headline: String,
@Field("body") bodyText: String
): Article
@DELETE("posts/{id}")
suspend fun remove(@Path("id") postId: Int): Response<ResponseBody>
}
Khởi tạo Retrofit client
Sử dụng hai instance riêng biệt để xử lý cả phản hồi dạng JSON và chuỗi thuần túy:
val jsonClient = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val stringClient = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
Hàm gọi API
Các hàm tiện ích bao bọc việc tạo service và gọi phương thức:
suspend fun retrieveFirstPost(): Article =
jsonClient.create(ArticleService::class.java).fetchSingle(1)
suspend fun retrieveList(limit: Int): List<Article> =
jsonClient.create(ArticleService::class.java).fetchAll().take(limit)
suspend fun retrieveRawPost(): String =
stringClient.create(ArticleService::class.java).fetchRaw("posts/1")
suspend fun createNewEntry(): Article =
jsonClient.create(ArticleService::class.java).insert(101, "Demo Title", "Demo Content")
suspend fun updateExisting(): Article =
jsonClient.create(ArticleService::class.java).modify(1, 101, "Updated Title", "Updated Content")
suspend fun deleteById(): Boolean {
val response = stringClient.create(ArticleService::class.java).remove(1)
return response.isSuccessful
}
Chạy thử nghiệm
Thực thi trong phạm vi runBlocking để kiểm tra đầu ra:
fun main() = runBlocking {
println(retrieveRawPost())
println(retrieveFirstPost())
println(retrieveList(2))
println(createNewEntry())
println(updateExisting())
println("Deletion succeeded: ${deleteById()}")
}