Aplikasi Android jarang berdiri sendiri — hampir selalu ada REST API yang menjadi sumber data utama. Retrofit dari Square adalah library HTTP client paling populer di Android. Library ini mengubah interface Kotlin menjadi implementasi yang melakukan request HTTP, mengkonversi response ke objek Kotlin, dan menyatukan error handling. Bab ini menjelaskan setup Retrofit, anotasi @GET/@POST, integrasi converter (Moshi/Gson), serta OkHttp interceptor untuk autentikasi dan logging.
Setup Retrofit #
Dua dependency utama: retrofit (core) dan converter-moshi (atau converter-gson). Tambahkan juga okhttp untuk interceptor dan logging-interceptor untuk debug. Pada build.gradle.kts:
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
}Setelah dependency tersedia, kita membuat instance Retrofit dengan base URL, converter factory, dan OkHttpClient yang sudah memasang interceptor.
Anotasi @GET, @POST, dan Path Parameter #
Interface Retrofit memakai anotasi untuk mendefinisikan endpoint. Setiap method harus suspend agar dapat dipanggil dari coroutine.
interface PenggunaApi {
@GET("pengguna/{id}")
suspend fun fetchPengguna(@Path("id") id: Int): Pengguna
@POST("pengguna")
suspend fun buatPengguna(@Body payload: PenggunaBaru): Pengguna
@GET("pengguna")
suspend fun listPengguna(@Query("page") page: Int = 1): List<Pengguna>
}| Anotasi | Tujuan |
|---|---|
@GET, @POST, @PUT, @DELETE | Method HTTP |
@Path("name") | Substitusi segmen path |
@Query("name") | Query string (?page=1) |
@Body | Body request sebagai objek yang dikonversi |
@Header("name") | Header dinamis per request |
@Field("name") | Field form-encoded (butuh @FormUrlEncoded) |
Moshi dan Gson Converter #
Converter bertugas mengubah JSON ke objek Kotlin dan sebaliknya. Moshi (dari Square) adalah pengganti modern Gson — lebih cepat, lebih kecil, dan native Kotlin. Konfigurasi Moshi membutuhkan KotlinJsonAdapterFactory agar dapat membaca class Kotlin.
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(OkHttpClient.Builder().build())
.build()
val api = retrofit.create(PenggunaApi::class.java)OkHttp Interceptor #
Interceptor adalah lapisan yang dapat memodifikasi request sebelum dikirim dan response sebelum dikembalikan. Use case paling umum: menambah header Authorization, logging request/response, dan retry otomatis.
val authInterceptor = Interceptor { chain ->
val req = chain.request().newBuilder()
.addHeader("Authorization", "Bearer YOUR_API_TOKEN")
.build()
chain.proceed(req)
}
val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
val client = OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.addInterceptor(logging)
.build()Urutan interceptor penting: addInterceptor menambahkan pada urutan aplikasi, addNetworkInterceptor menambahkan pada urutan network (setelah redirect). Untuk autentikasi, gunakan addInterceptor; untuk logging body, gunakan addInterceptor level BODY.
Simulasi Retrofit dan Interceptor #
Cuplikan berikut memodelkan Retrofit, interface service, dan interceptor sebagai struktur data ringkas. Mock server menangani request dan mengembalikan response yang sudah ditentukan:
// networking_retrofit.kt
// Mendemonstrasikan setup Retrofit, anotasi @GET/@POST, dan OkHttp
// interceptor sebagai struktur data ringkas. Tidak benar-benar melakukan
// HTTP — hanya memetakan kontrak service dan interceptor.
// Mock HTTP request/response
data class Request(val method: String, val path: String, val headers: Map<String, String> = emptyMap(), val body: String? = null)
data class Response(val code: Int, val body: String, val headers: Map<String, String> = emptyMap())
// Mensimulasikan OkHttp interceptor
fun interface Interceptor {
fun intercept(chain: Chain): Response
class Chain(val request: Request, val proceed: (Request) -> Response)
}
// Mensimulasikan Retrofit service interface
class PenggunaApi {
// @GET("pengguna/{id}")
fun fetchPengguna(id: Int): Request = Request("GET", "/pengguna/$id")
// @POST("pengguna")
fun buatPengguna(nama: String, email: String): Request =
Request("POST", "/pengguna", body = """{"nama":"$nama","email":"$email"}""")
}
// Retrofit builder yang memasang interceptor dan converter
class RetrofitBuilder {
private val interceptors = mutableListOf<Interceptor>()
private var converter: ((String) -> Any?)? = null
fun addInterceptor(i: Interceptor) { interceptors.add(i) }
fun converter(c: (String) -> Any?) { converter = c }
fun build(apiClass: () -> PenggunaApi): Retrofit {
return Retrofit(apiClass(), interceptors.toList(), converter)
}
}
class Retrofit(
val api: PenggunaApi,
private val interceptors: List<Interceptor>,
private val converter: ((String) -> Any?)?
) {
fun execute(request: Request): Response {
// Mock server
val server: (Request) -> Response = { req ->
when (req.method) {
"GET" -> {
val id = req.path.substringAfterLast("/").toInt()
Response(200, """{"id":$id,"nama":"Budi","email":"[email protected]"}""")
}
"POST" -> Response(201, req.body ?: "")
else -> Response(404, "")
}
}
val chain = Interceptor.Chain(request, server)
var response = server(request)
interceptors.forEach { response = it.intercept(chain) }
return response
}
@Suppress("UNCHECKED_CAST")
fun <T> parse(response: Response, parser: (String) -> T): T = parser(response.body)
}
// Mensimulasikan Moshi/Gson converter
object JsonParser {
fun parsePengguna(json: String): Map<String, Any> {
// Parser sangat sederhana untuk demo
val hasil = mutableMapOf<String, Any>()
json.trim().removeSurrounding("{", "}").split(",").forEach { pair ->
val (k, v) = pair.split(":", limit = 2).map { it.trim().trim('"') }
val parsed: Any = v.toIntOrNull() ?: v
hasil[k] = parsed
}
return hasil
}
}
fun main() {
val builder = RetrofitBuilder()
builder.addInterceptor { chain ->
val req = chain.request
println(" [Interceptor] ${req.method} ${req.path} body=${req.body ?: '-'}")
chain.proceed(req)
}
val retrofit = builder.build { PenggunaApi() }
println("=== @GET /pengguna/42 ===")
val reqGet = retrofit.api.fetchPengguna(42)
val resGet = retrofit.execute(reqGet)
println(" Response code=${resGet.code} body=${resGet.body}")
val parsed = retrofit.parse(resGet) { JsonParser.parsePengguna(it) }
println(" Parsed: $parsed")
println("\n=== @POST /pengguna ===")
val reqPost = retrofit.api.buatPengguna("Siti", "[email protected]")
val resPost = retrofit.execute(reqPost)
println(" Response code=${resPost.code} body=${resPost.body}")
}Output yang Dihasilkan:
=== @GET /pengguna/42 ===
[Interceptor] GET /pengguna/42 body=-
Response code=200 body={"id":42,"nama":"Budi","email":"[email protected]"}
Parsed: {id=42, nama=Budi, [email protected]}
=== @POST /pengguna ===
[Interceptor] POST /pengguna body={"nama":"Siti","email":"[email protected]"}
Response code=201 body={"nama":"Siti","email":"[email protected]"}Perhatikan bahwa interceptor dipanggil sebelum server memproses request — setiap log request muncul di output. Response yang dihasilkan kemudian diparse oleh JsonParser.parsePengguna menjadi Map<String, Any>.
Referensi Lanjutan #
- Retrofit — dokumentasi resmi Retrofit untuk klien HTTP bertipe yang umum dipakai di aplikasi Android Kotlin
Selanjutnya #
Retrofit mengkonversi JSON lewat Moshi, namun Moshi memerlukan reflection atau codegen. Bab Serialization dan JSON akan membahas alternatif kotlinx.serialization yang kompatibel dengan multiplatform.