티스토리 뷰
Ktorfit이란?
Ktorfit은 안드로이드의 네트워크 라이브러리인 Retrofit과 ktor를 합한 라이브러리이다. 현재 KMP로 기존 안드로이드 프로젝트에서 확장중이다. Retrofit은 내부적으로 OkHttp를 사용하는 안드로이드 전용 라이브러리이기 때문에 Retrofit을 멀티플랫폼 환경에서 사용 불가하다. 따라서 Retrofit과 같은 방식을 사용하되, 멀티 플랫폼에서 사용가능한 Ktorfit을 사용했다.
의존성 추가
build.gradle(app 단위)
plugins {
id("com.google.devtools.ksp") version "CURRENT_KSP_VERSION"
id("de.jensklingenberg.ktorfit") version "2.7.2"
}
sourceSets {
commonMain.dependencies {
implementation("de.jensklingenberg.ktorfit:ktorfit-lib:$ktorfitVersion")
implementation("de.jensklingenberg.ktorfit:ktorfit-converters-response:$CONVERTER_VERSION")
}
}
build.gradle(project 단위)
plugins {
alias(libs.plugins.ksp) apply false
}
다음과 같이 어노테이션 프로세서인 KSP와 Ktorfit의 의존성을 추가해준다. 나는 응답을 Response객체로 받기 위해 ResponseConverter를 만들어주기 위한 의존성도 추가했다.
https://foso.github.io/Ktorfit/converters/converters/
위 코드에는 없지만 추가로 직렬화를 위해 kotlinx-serialization의 의존성도 추가해주었다.
https://github.com/Kotlin/kotlinx.serialization
요청 방법
Retrofit
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
interface DeviceService {
@POST("devices")
suspend fun registerDevice(
@Body deviceRegisterRequest: DeviceRegisterRequest,
): Response<DeviceRegisterResponse>
}
Ktorfit
import de.jensklingenberg.ktorfit.Response
import de.jensklingenberg.ktorfit.http.Body
import de.jensklingenberg.ktorfit.http.POST
interface DeviceService {
@POST("devices")
suspend fun registerDevice(
@Body deviceRegisterRequest: DeviceRegisterRequest,
): Response<DeviceRegisterResponse>
}
코드를 보면 알 수 있듯이 import만 다르고 요청 방법이 같다.
Interceptor
Retrofit
@ContributesBinding(AppScope::class)
@Inject
class FestaBookAuthInterceptor(
private val festivalLocalDataSource: FestivalLocalDataSource,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val festivalId = festivalLocalDataSource.getFestivalId()
val requestBuilder = originalRequest.newBuilder()
if (festivalId != null) {
Timber.d("festivalId : $festivalId")
requestBuilder.addHeader("festival", festivalId.toString())
}
val requestWithHeader = requestBuilder.build()
return chain.proceed(requestWithHeader)
}
}
- chain을 통해 Retrofit이 생성한 현재 Request객체를 가져온다.(Request 객체는 불변이다.)
- 로컬 데이터를 datasource에서 받아 헤더에 실는다.
- 헤더에 실은 후 새로운 Request 객체를 생성하여 반환한다.
Ktorfit
@Inject
class FestaBookAuthPlugin(
private val festivalLocalDataSource: FestivalLocalDataSource,
) {
val plugin =
createClientPlugin("FestaBookAuthPlugin", ::FestaBookAuthPluginConfig) {
onRequest { request, _ ->
val festivalId = festivalLocalDataSource.getFestivalId()
if (festivalId != null) {
Timber.d("festivalId : $festivalId")
request.headers["festival"] = festivalId.toString()
}
}
}
}
- Ktorfit이 Request객체를 생성하기 전에 HttpRequestBuilder에 접근한다.
- 로컬데이터를 dataSource에서 받아온다.
- 받아온 데이터를 헤더에 추가한다.
"FestaBookAuthPlugin"은 Ktorfit이 plugin을 설치할 때 식별자로 사용한다. 중복 이름의 여러 plugin이 생기면 예외가 발생한다.
Ktorfit Client, Service 객체 생성
@BindingContainer
@ContributesTo(AppScope::class)
object NetworkBindings {
@Provides
fun provideJson(): Json = Json { ignoreUnknownKeys = true }
@Provides
fun provideResponseConverterFactory(): ResponseConverterFactory = ResponseConverterFactory()
@Provides
fun provideAppConfig(): AppConfig = createAppConfig()
@Provides
fun provideKtorfit(
appConfig: AppConfig,
responseConverterFactory: ResponseConverterFactory,
authPlugin: FestaBookAuthPlugin,
json: Json,
): Ktorfit =
ktorfit {
baseUrl(appConfig.baseUrl)
converterFactories(responseConverterFactory)
httpClient {
HttpClient {
install(ContentNegotiation) {
json(json)
}
install(authPlugin.plugin)
}
}
}
@Provides
fun provideScheduleService(ktorfit: Ktorfit): ScheduleService = ktorfit.createScheduleService()
@Provides
fun provideNoticeService(ktorfit: Ktorfit): NoticeService = ktorfit.createNoticeService()
@Provides
fun providePlaceService(ktorfit: Ktorfit): PlaceService = ktorfit.createPlaceService()
@Provides
fun provideDeviceService(ktorfit: Ktorfit): DeviceService = ktorfit.createDeviceService()
@Provides
fun provideFestivalNotificationService(ktorfit: Ktorfit): FestivalNotificationService = ktorfit.createFestivalNotificationService()
@Provides
fun provideFAQService(ktorfit: Ktorfit): FAQService = ktorfit.createFAQService()
@Provides
fun provideFestivalService(ktorfit: Ktorfit): FestivalService = ktorfit.createFestivalService()
@Provides
fun provideLostItemService(ktorfit: Ktorfit): LostItemService = ktorfit.createLostItemService()
@Provides
fun provideFestivalLineupService(ktorfit: Ktorfit): FestivalLineupService = ktorfit.createFestivalLineupService()
}
다음 코드는 Metro 자동 DI 라이브러리를 통해 Ktorfit Client 와 Service 객체를 생성하는 코드이다.
요청 받은 값을 Response 타입으로 받기 위해 ResponseConverterFactory를 사용했다. Retrofit은 자동으로 별다른 추가 설정 없이 자동으로 Response 타입을 명시하면 가능하지만 Ktorfit은 ConverterFactory를 통해 구현해주어야한다.
Retrofit은 메서드 체이닝을 통해 Retrfit 객체를 Build해주었지만, Ktorfit은 Kotlin DSL방식을 지원하기 때문에 지저분하게 체이닝하지 않고 Ktorfit{} 을 통해 Client 생성이 가능하다.
Retrofit은 create()를 통해 Service 객체를 생성했지만 Ktorfit은 Service 인터페이스에서 HTTP 메서드 관련 어노테이션을 달아준 후, 빌드를 한 번하면 KSP가 자동으로 createScheduleService같은 Service 객체를 생성해주는 확장함수를 만들어준다.
'CMP' 카테고리의 다른 글
| SharedPreferences에서 DataStore로 마이그레이션하기 (1) | 2026.02.01 |
|---|