retrofit

app下的build.gradle

1
2
3
// Retrofit
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

建立Retrofit

1
2
3
  private val retrofit = Retrofit.Builder()
    .baseUrl("https://www.httpbin.org/")
    .build()

Interface 產生介面的實作物件

Prerequisites:

反射相關的Class了解之後,才能往下閱讀。

Interface

1
2
3
4
interface ApiService {
  @GET("get")
  suspend fun get(@Query("name") name: String) : Response<ResponseBody>
}

產生介面的實作物件

1
val service = retrofit.create(ApiService::class.java)

ApiService::class 是 Kotlin 的 KClass 類別
ApiService::class.java 把 Kotlin 的 KClass 轉成 Java 的 Class

Retrofit 是一個 Java 寫的函式庫,它的 create() 方法長這樣:

public <T> T create(Class<T> clazz) {
  return clazz.newInstance();
}

create()的參數是 Java Class,在 Kotlin 世界裡,KClass.java 才能轉換成Java Class。

Kotlin 寫法 Java 寫法 說明
ApiService::class ApiService.class Kotlin 專用的 KClass
ApiService::class.java ApiService.class Retrofit 要的 Java Class

使用方式:

val userApi = retrofit.create(UserApi::class.java)
val orderApi = retrofit.create(OrderApi::class.java)

Annotations

@GET

Api

1
2
3
4
interface ApiService {
  @GET("get")
  suspend fun get(@Query("name") name: String) : Response<ResponseBody>
}

TestCase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import kotlinx.coroutines.runBlocking
import org.junit.Test
import retrofit2.Retrofit

class RetrofitTest {
  private val retrofit = Retrofit.Builder()
    .baseUrl("https://www.httpbin.org/")
    .build()

  @Test
  fun testGet() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.get("Alice")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
}
{
  "args": {
    "name": "Alice"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-691133af-3ccdfc272a50f4927d7d3aa2"
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/get?name=Alice"
}

@Post

@Post @FormUrlEncoded

@FormUrlEncoded 是用來告訴 Retrofit:

這是一個 表單格式 (application/x-www-form-urlencoded) 的 POST 請求。

也就是說,它是用來搭配 @Field 一起使用的。 它會自動幫你把欄位轉成像這樣的 body:

user=alice&password=1234

與 @Body 的差別

標註 傳輸格式 用於
@FormUrlEncoded + @Field application/x-www-form-urlencoded 傳送普通表單欄位(文字)
@Body 取決於你傳的 RequestBody 傳送 JSON、FormBody、Multipart… 等任何格式

Retrofit 幫你送出的 HTTP 請求會是這樣:

POST /post HTTP/1.1
Content-Type: application/x-www-form-urlencoded

注意:必須同時加上 @FormUrlEncoded

如果你寫了 @Field 卻沒加 @FormUrlEncoded, Retrofit 會報錯:

@Field parameters can only be used with form encoding. (parameter #1)
1
2
3
4
5
interface ApiService {
  @POST("post")
  @FormUrlEncoded
  suspend fun post(@Field("name") name: String) : Response<ResponseBody>
}
1
2
3
4
5
6
7
8
  @Test
  fun testPost() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.post("Alice")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "Alice"
  }, 
  "headers": {
    "Content-Length": "10", 
    "Content-Type": "application/x-www-form-urlencoded", 
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Post @Body FormBody

@Body 傳 RequestBody 任意格式(JSON、FormBody)

POST /post HTTP/1.1
Content-Type: application/x-www-form-urlencoded

類似okhttp的FormBody,以下是okHttp的FormBody。

1
2
3
4
    val formbody = FormBody.Builder()
      .add("user", "alice")
      .add("password","1234")
      .build()

ApiService

1
2
  @POST("post")
  suspend fun postbody(@Body body: RequestBody) : Response<ResponseBody>
1
2
3
4
5
6
7
8
9
10
11
12
13
  @Test
  fun formBody() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val formbody = FormBody.Builder()
      .add("user", "alice")
      .add("password","1234")
      .build()

    val response = service.postbody(formbody)
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "password": "1234", 
    "user": "alice"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "24", 
    "Content-Type": "application/x-www-form-urlencoded", 
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Post @Body json

POST /post HTTP/1.1
Content-Type: application/json
1
2
  @POST("post")
  suspend fun postjson(@Body body: RequestBody) : Response<ResponseBody>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  @Test
  fun json() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val json = """
      {
      "a": 1,
      "b": 2
      }
    """.trimIndent()
    val jsonbody = RequestBody.create(MediaType.parse("application/json"), json)
    val response = service.postjson(jsonbody)
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "{\n\"a\": 1,\n\"b\": 2\n}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "18", 
    "Content-Type": "application/json; charset=utf-8", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-6911376b-1f3523996a6ee09c63a456e8"
  }, 
  "json": {
    "a": 1, 
    "b": 2
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Post @Body 物件轉json

build.gradle(:app)

    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

加入Gson轉換工廠,可以把物件轉成json

addConverterFactory(GsonConverterFactory.create())
1
2
3
4
val retrofitGson = Retrofit.Builder()
  .baseUrl("https://www.httpbin.org/")
  .addConverterFactory(GsonConverterFactory.create())
  .build()

物件

1
2
3
data class UserInfo(
  val name: String, val password: String
)

Api Interface

1
2
  @POST("post")
  suspend fun postObj(@Body body: UserInfo): Response<ResponseBody>

執行結果Content-Type是application/json,有json欄位。

1
2
3
4
5
6
7
8
9
10
11
12
13
  @Test
  fun postObj() = runBlocking<Unit> {
    val retrofitGson = Retrofit.Builder()
      .baseUrl("https://www.httpbin.org/")
      .addConverterFactory(GsonConverterFactory.create())
      .build()
    val service = retrofitGson.create(ApiService::class.java)
    val userInfo = UserInfo("Mary", "1234")
    val response = service.postObj(userInfo)
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "{\"name\":\"Mary\",\"password\":\"1234\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "33", 
    "Content-Type": "application/json; charset=UTF-8", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-69114062-43c94ace06011c254a13d201"
  }, 
  "json": {
    "name": "Mary", 
    "password": "1234"
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Post @Multipart 上傳檔案

@Part要有欄位名。

@Part("欄位名")
@Part("desc")
1
2
3
4
5
6
  @Multipart
  @POST("post")
  suspend fun postMultipart(
    @Part file: okhttp3.MultipartBody.Part, 
    @Part("desc") body: RequestBody
  ): Response<ResponseBody>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  @Test
  fun postMultipart() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val file = File("/Users/cici/Desktop/data")
    val filebody = RequestBody.create(MediaType.parse("text/plain"), file)
    // 傳送檔案
    val part1 = MultipartBody.Part.createFormData("file1", file.name, filebody)
    // 傳送文字
    val text = RequestBody.create(MediaType.parse("text/plain"), "text content")
    val response = service.postMultipart(part1, text)
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "", 
  "files": {
    "file1": "test"
  }, 
  "form": {
    "desc": "text content"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "395", 
    "Content-Type": "multipart/form-data; boundary=8d40d716-b986-40ff-972c-dd585d7be6ae", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-69113da4-02eb34df1288b0754d9ee2e6"
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Http

@Http get

自訂method,hasBody是指「是否傳送Body」,path是路徑。

1
2
  @HTTP(method = "GET", path = "get", hasBody = false)
  suspend fun httpGet(@Query("name") name: String) : Response<ResponseBody>

hasBody為false,執行結果沒有json欄位。

1
2
3
4
5
6
7
8
  @Test
  fun httpGet() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.httpGet("Alice")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {
    "name": "Alice"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-691134f5-273506e65ab818ff6fd29a98"
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/get?name=Alice"
}

@Http post

自訂method,hasBody是指「是否傳送Body」,path是路徑。

1
2
  @HTTP(method = "POST", path = "post", hasBody = true)
  suspend fun httpPost(@Query("name") name: String) : Response<ResponseBody>

hasBody為true,執行結果有json欄位。

1
2
3
4
5
6
7
8
  @Test
  fun httpPost() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.httpPost("Alice")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {
    "name": "Alice"
  }, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "0", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-691134b8-3005b6a511d732a3647311dd"
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post?name=Alice"
}

@QueryMap

可以把map作為參數。

1
2
  @GET("get")
  suspend fun queryMap(@QueryMap map: Map<String, String>) : Response<ResponseBody>
1
2
3
4
5
6
7
8
9
10
11
12
13
  @Test
  fun queryMap() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.queryMap(
      mapOf(
        "name" to "Alice",
        "address" to "Taiwan"
      )
    )
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {
    "address": "Taiwan", 
    "name": "Alice"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-6911353c-7c4f426a393b6e1933b42f71"
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/get?name=Alice&address=Taiwan"
}

@Path

@Path通常搭配頁碼。

https://你的baseUrl/page/{pageNumber}
https://你的baseUrl/page/1

由於我是使用使用httpbin,目前沒看到有page相關API,使用action參數代表要做的動作是post。

1
2
3
4
5
6
  @POST("{action}")
  @FormUrlEncoded
  suspend fun pathTest(
    @Path("action") action: String,
    @Field("name") name: String
  ): Response<ResponseBody>
1
2
3
4
5
6
7
8
  @Test
  fun path() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.pathTest("post", "Mary")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "Mary"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "9", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-6911493c-20910e7754dfc70b1b5857b6"
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

Api

1
2
  @POST("post")
  suspend fun postHeader(@Body body: RequestBody, @Header("version") version: String): Response<ResponseBody>
1
2
3
4
5
6
7
8
9
10
11
12
  @Test
  fun testHeader() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val formbody = FormBody.Builder()
      .add("user", "alice")
      .add("password", "1234")
      .build()
    val response = service.postHeader(formbody, "version1.0")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "password": "1234", 
    "user": "alice"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "24", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "Version": "version1.0", 
    "X-Amzn-Trace-Id": "Root=1-69116368-4a2f0b0b79c617d04044d7c3"
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Headers

注意!後面有s

Api

1
2
3
  @Headers("os:android","version:1.0")
  @POST("post")
  suspend fun postHeaderS(@Body body: RequestBody): Response<ResponseBody>
1
2
3
4
5
6
7
8
9
10
11
12
  @Test
  fun testHeaderS() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val formbody = FormBody.Builder()
      .add("user", "alice")
      .add("password", "1234")
      .build()
    val response = service.postHeaderS(formbody)
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "password": "1234", 
    "user": "alice"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "24", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "Os": "android", 
    "User-Agent": "okhttp/3.14.9", 
    "Version": "1.0", 
    "X-Amzn-Trace-Id": "Root=1-69116545-7fac0a3e68612ea7151b8451"
  }, 
  "json": null, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

@Url

自己定義url,會覆蓋baseURL。

注意!下方的@Get後面是沒有東西,沒有圓括號()。

1
2
  @GET
  suspend fun getUrl(@Url url: String): Response<ResponseBody>
1
2
3
4
5
6
7
8
  @Test
  fun testUrl() = runBlocking<Unit> {
    val service = retrofit.create(ApiService::class.java)
    val response = service.getUrl("https://www.httpbin.org/get?name=Mary")
    if (response.isSuccessful) {
      println(response.body()?.string())
    }
  }
{
  "args": {
    "name": "Mary"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-6911872f-7c5e2e00125e9a7574732956"
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/get?name=Mary"
}

回傳物件

以下是回傳的Response,要把json欄位的內容轉成UserInfo的物件回傳。

{
  "args": {}, 
  "data": "{\"name\":\"Mary\",\"password\":\"1234\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "33", 
    "Content-Type": "application/json; charset=UTF-8", 
    "Host": "www.httpbin.org", 
    "User-Agent": "okhttp/3.14.9", 
    "X-Amzn-Trace-Id": "Root=1-69115c37-615e97ce087bac434ab53385"
  }, 
  "json": {
    "name": "Mary", 
    "password": "1234"
  }, 
  "origin": "42.73.166.13", 
  "url": "https://www.httpbin.org/post"
}

Response<物件>

尖括號包著的是回傳的物件,使用Response作為回值類型,是可以使用isSuccessful屬性,檢查回傳值是200。

if (response.isSuccessful) { ... }

GsonConvert

build.gradle(:app)

    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

寫一個ResponseWrapper1,欄位名json一定要對映Response中的json欄位名,json的類型為 UserInfo

1
2
3
data class ResponseWrapper1(
  val json: UserInfo
)

UserInfo類別

1
2
3
data class UserInfo(
  val name: String, val password: String
)

Api

1
2
  @POST("post")
  suspend fun rtnObj1(@Body body: UserInfo): Response<ResponseWrapper1>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  @Test
  fun postObjRtn1() = runBlocking<Unit> {
    val retrofitGson = Retrofit.Builder()
      .baseUrl("https://www.httpbin.org/")
      .addConverterFactory(GsonConverterFactory.create())
      .build()
    val service = retrofitGson.create(ApiService::class.java)
    val userInfo = UserInfo("Mary", "1234")
    val response = service.rtnObj1(userInfo)
    if (response.isSuccessful) {
      val wrapper = response.body()
      val usr = wrapper?.json
      println("name = ${usr?.name} password = ${usr?.password}")
    }
  }
name = Mary password = 1234

@SerializedName

前一個範例遇到的問題,只能使用json欄位名,使用@SerializedName處理這個問題。
告訴gson,把json欄位,轉成userInfo的物件。

1
2
3
4
5
6
import com.google.gson.annotations.SerializedName

data class ResponseWrapper1(
  @SerializedName("json")
  val userInfo: UserInfo
)

其它內容都一樣,只是wrapper?.userInfo有改變。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  @Test
  fun postObjRtn1() = runBlocking<Unit> {
    val retrofitGson = Retrofit.Builder()
      .baseUrl("https://www.httpbin.org/")
      .addConverterFactory(GsonConverterFactory.create())
      .build()
    val service = retrofitGson.create(ApiService::class.java)
    val userInfo = UserInfo("Mary", "1234")
    val response = service.rtnObj1(userInfo)
    if (response.isSuccessful) {
      val wrapper = response.body()
      val usr = wrapper?.userInfo
      println("name = ${usr?.name} password = ${usr?.password}")
    }
  }
name = Mary password = 1234

moshi

除了使用@SerializedName(“json”),也可以使用moshi處理這個問題。

build.gradle(:app)

    implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
    def moshi_version = "1.13.0"
    implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"

Response的欄位為json,把它轉成userInfo的欄位名,類型為UserInfo。

1
2
3
4
5
6
7
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ResponseWrapper2(
  @Json(name = "json")
  val userInfo: UserInfo
)

Api

1
2
  @POST("post")
  suspend fun rtnObj2(@Body body: UserInfo): Response<ResponseWrapper2>

使用Moshi轉換工廠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  @Test
  fun postObjRt2() = runBlocking<Unit> {
    val retrofitGson = Retrofit.Builder()
      .baseUrl("https://www.httpbin.org/")
      .addConverterFactory(
        //建立Moshi工廠
        MoshiConverterFactory.create(
          Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()
        )
      )
      .build()
    val service = retrofitGson.create(ApiService::class.java)
    val userInfo = UserInfo("Mary", "1234")
    val response = service.rtnObj2(userInfo)
    if (response.isSuccessful) {
      val wrapper = response.body()
      val usr = wrapper?.userInfo
      println("name = ${usr?.name} password = ${usr?.password}")
    }
  }
name = Mary password = 1234

原理:
MoshiConverterFactory 是什麼?

MoshiConverterFactory 是 Retrofit 的 Converter Factory,用來把 HTTP 回傳的 JSON 自動轉成 Kotlin/Java 物件,或把 Kotlin/Java 物件轉成 JSON 傳給伺服器。

它背後使用 Moshi 這個 JSON 解析庫(由 Square 團隊開發,和 Retrofit 同家族)。

作用就像你之前用的 GsonConverterFactory,只是底層是 Moshi 而不是 Gson。

Moshi 原生支援 Kotlin 的 data class

你用了:

@JsonClass(generateAdapter = true)

這會自動生成 Adapter,幫 Moshi 處理 Kotlin 的 constructor、nullable、default value 等特性。

Gson 有時在處理 nested JSON 或 字串包 JSON 的情況下會比較麻煩,需要手動 type adapter 或額外解析。

@Json(name=”json”)

這行會告訴 Moshi,把 JSON 回傳裡的 “json” 欄位對應到 userInfo 屬性。

Moshi 會自動解析 “json” 內的物件成 UserInfo,你不需要手動呼叫 Gson().fromJson()。

你呼叫:

val response = service.rtnObj(userInfo)

Retrofit 發出 POST 請求,把 UserInfo 轉成 JSON(Moshi 轉換)。

伺服器回傳:

{
  "args": {},
  "data": "{\"name\":\"Mary\",\"password\":\"1234\"}",
  "json": {
      "name": "Mary",
      "password": "1234"
  }
}

MoshiConverterFactory 看到你定義:

@JsonClass(generateAdapter = true)
data class ResponseWrapper(
  @Json(name = "json") val userInfo: UserInfo
)

就自動把 json 裡的物件解析成 UserInfo,然後包在 ResponseWrapper 裡回傳給你。

RetrofitUtil

以下內容知識量很多,必須要擁有以下知識,才可以繼續。

Prerequisites:

參數是Java Class(Java類別描述檔),因為Retrofit是java寫的,所以create()方法只能用java的class傳入。

使用泛型方法定義的參數T。

1
2
3
  fun <T> createService(clazz: Class<T>):T {
    return retrofit.create(clazz)
  }

retrofit是靜態變數,使用by委托給lazy,只執行一次by lazy{}的內容,之後就不會再執行,用到retrofit的屬性的時候才會執行。

ArticleApi::class.java,把KClass轉成Java Class,把Java Class作為參數傳給方法。

1
2
3
4
5
6
7
8
9
interface ArticleApi {
  @GET("article/list")
  suspend fun articleList(): ArticleList
  companion object {
    val retrofit: ArticleApi by lazy {
      RetrofitUtil.createService(ArticleApi::class.java)
    }
  }
}

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.coroutine.api
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitUtil {
  private const val BASE_URL =
    "https://mock.apipost.cn/app/mock/project/ced69cf2-9206-4a42-895e-dd7442a888df/"
  private val retrofit = Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl(BASE_URL)
    .build()

  fun <T> createService(clazz: Class<T>):T {
    return retrofit.create(clazz)
  }
}

Data class

data class自動有setter與getter功能,不用自己寫。

1
data class ArticleList(val data: List<ArticleItem>, val code: Int = -1, val message: String)
1
data class ArticleItem(val title: String, val source: String, val time: String)

ServiceApi

articleList()傳回值是ArticleList,ArticleList又包含ArticleItem。

注意以下article前面無右斜線。

@GET("article/list")
1
2
3
4
5
6
7
8
9
10
11
12
package com.example.coroutine.api
import retrofit2.http.GET

interface ArticleApi {
  @GET("article/list")
  suspend fun articleList(): ArticleList
  companion object {
    val retrofit: ArticleApi by lazy {
      RetrofitUtil.createService(ArticleApi::class.java)
    }
  }
}

使用ArticleApi.retrofit

呼叫ArticleApi.retrofit.articleList(),就可以執行retrofit的網路請求。
retrofit會把interface轉成物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MainActivity07 : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val submit = findViewById<Button>(R.id.button)
    val textv = findViewById<TextView>(R.id.textView)
    submit.setOnClickListener {
      GlobalScope.launch(Dispatchers.Main) {
        val listResult = withContext(Dispatchers.IO) {
          ArticleApi.retrofit.articleList()
        }
        textv.text = listResult.toString()
      }
    }
  }
}

results matching ""

    No results matching ""