Android Retrofit HTTP client
在現代 Android 開發中,Retrofit 是由 Square 公司開發的最主流、最強大的網路請求函式庫。它本質上是一個 Type-safe 的 HTTP Client,能將 RESTful API 轉化為 Kotlin 介面 (Interface),並與 Coroutines 完美整合。
環境配置與依賴
除了 Retrofit 核心庫,我們通常還需要一個轉換器 (Converter) 來處理 JSON 解析,以及一個攔截器來輸出日誌。
在 build.gradle.kts 加入依賴:
dependencies {
val retrofitVersion = "2.11.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
// 推薦使用 Kotlin Serialization 作為 JSON 轉換器
implementation("com.squareup.retrofit2:converter-kotlinx-serialization:$retrofitVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
// OkHttp 攔截器 (用於 Log)
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}
確保 AndroidManifest.xml 已加入網路權限:
<uses-permission android:name="android.permission.INTERNET" />
核心註解與請求方式
Retrofit 透過註解來描述 HTTP 請求的各個部分。
路徑與查詢參數 (@Path, @Query)
interface ApiService {
// 取得單一使用者:/users/1
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
// 搜尋使用者:/users?sort=desc&page=1
@GET("users")
suspend fun searchUsers(
@Query("sort") sortOrder: String,
@Query("page") page: Int
): List<User>
}
Header 與 Body (@Header, @Body)
interface ApiService {
// 動態傳入 Header
@GET("profile")
suspend fun getProfile(@Header("Authorization") token: String): Profile
// 傳送 JSON Body
@POST("users")
suspend fun createUser(@Body user: User): User
}
表單與檔案上傳 (@FormUrlEncoded, @Multipart)
interface ApiService {
// 模擬 HTML 表單提交
@FormUrlEncoded
@POST("login")
suspend fun login(
@Field("username") user: String,
@Field("password") pass: String
): LoginResponse
// 上傳檔案 (如圖片)
@Multipart
@POST("upload")
suspend fun uploadAvatar(
@Part avatar: MultipartBody.Part,
@Part("description") desc: RequestBody
): UploadResponse
}
自定義 OkHttpClient 與攔截器
Retrofit 底層使用 OkHttp。透過配置 OkHttpClient,我們可以實現日誌輸出、超時設定以及自動注入內容 (如 Token)。
身份驗證攔截器 (Auth Interceptor)
class AuthInterceptor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
return chain.proceed(request)
}
}
// 建立 Client
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor(AuthInterceptor("your_token_here"))
.build()
建立與 Hilt 整合
在 Hilt 中提供單一的 Retrofit 實例是最佳實踐。
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
val json = Json { ignoreUnknownKeys = true } // 忽視未知欄位
val contentType = "application/json".toMediaType()
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory(contentType))
.build()
}
@Provides
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
優雅的錯誤處理
單純的 try-catch 很難區分「網路斷線」與「伺服器回傳 404」。我們可以使用自定義包裝類別:
sealed class NetworkResult<out T> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val code: Int, val message: String?) : NetworkResult<Nothing>()
data class Exception(val e: Throwable) : NetworkResult<Nothing>()
}
// 在 Repository 中使用
suspend fun safeApiCall(): NetworkResult<List<User>> {
return try {
val response = apiService.getUsers()
// Retrofit 配合協程會直接回傳內容,若需狀態碼可將回傳改為 Response<T>
NetworkResult.success(response)
} catch (e: HttpException) {
NetworkResult.Error(code = e.code(), message = e.message())
} catch (e: Exception) {
NetworkResult.Exception(e)
}
}
如果需要精確的狀態碼,建議在 API Interface 定義時回傳
Response<T>,例如 suspend fun getUsers(): Response<List<User>>。這樣你就能手動判斷 response.isSuccessful 並讀取 response.code()。透過以上配置,你的網路層將不僅強大,而且具備良好的擴展性與維護性。