Tích hợp Retrofit 2 và Kotlin Coroutines để Gọi Các Dịch Vụ REST

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

Thẻ: Kotlin retrofit2 Coroutines rest-api gson

Đăng vào ngày 26 tháng 5 lúc 05:00