버튼 수집상

[안드로이드] Retrofit 대신 Ktor로 Api 호출해보기 - 1 본문

TIL - 안드로이드

[안드로이드] Retrofit 대신 Ktor로 Api 호출해보기 - 1

cocokaribou 2023. 12. 11. 16:35

배경

KMM (Kotlin Multiplatform Mobile) 이 여기저기서 많이 언급되고 있다.

줄곧 XML을 쓰다가 마침 Compose 프로젝트를 할 기회가 생겼다.

이 기회에 Ktor도 써보면 어떨까 생각이 들어서 간단하게 써보았다.

 

Philipp Lackner 의 아래 영상을 참고했다.

 

그런데 예제에서 쓰는 HttpClient.get() 함수의 시그니처가 달랐다.

 

예제에선 io.ktor:ktor-client-core-jvm:1.6.3에서 아래 함수 호출.

/**
 * Executes a [HttpClient] GET request, with the specified [scheme], [host], [port], [path] and [body].
 * And allows to further configure the request, using a [block] receiving an [HttpRequestBuilder].
 *
 * Tries to receive a specific type [T], if fails, an exception is thrown.
 */
public suspend inline fun <reified T> HttpClient.get(
    scheme: String = "http",
    host: String = "localhost",
    port: Int = DEFAULT_PORT,
    path: String = "/",
    body: Any = EmptyContent,
    block: HttpRequestBuilder.() -> Unit = {}
): T = request {
    url(scheme, host, port, path)
    method = HttpMethod.Get
    this.body = body
    apply(block)
}

 

타입을 리턴하기 때문에 아래와 같이 호출부를 정의할 수 있다.

override suspend fun getPosts(): List<PostResponse> {
    return client.get { url(HttpRoutes.POSTS) }
}

 

그런데 io.ktor:ktor-client-core-jvm:2.3.6 버전의 HttpClient.get()은 HttpResponse를 리턴한다.

/**
 * Executes an [HttpClient]'s GET request with the parameters configured in [builder].
 *
 * Learn more from [Making requests](https://ktor.io/docs/request.html).
 */
public suspend inline fun HttpClient.get(builder: HttpRequestBuilder): HttpResponse {
    builder.method = HttpMethod.Get
    return request(builder)
}

 

그래서 받아온 HttpResponse의 body()를 호출해서 json으로 파싱해줘야한다.

아래 코드를 참고했다.

https://github.com/ktorio/ktor-documentation/tree/2.3.7/codeSnippets/snippets/client-json-kotlinx

 

 

 

gradle.properties

ktor_version=2.3.6

 

build.gradle.kts(:app)

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.serialization")
}

//...

dependencies {
    // 다른 라이브러리 생략

    // ktor
    val ktor_version: String by project
    implementation("io.ktor:ktor-client-core:$ktor_version")
    implementation("io.ktor:ktor-client-android:$ktor_version")
    implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
    implementation("io.ktor:ktor-client-logging:$ktor_version")
    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
}

플러그인 선언 잊지 말 것.

대부분의 예제에서 버전을 스네이크 케이스로 적던데 kotlin 린트 때문에 노란줄이 뜬다..

 

PostApiService (HttpClient 정의)

interface PostApiService {
    suspend fun getPosts(): List<PostResponse>

    companion object {
        fun create(): PostApiServiceImpl {
            return PostApiServiceImpl(
                client = HttpClient(Android) {
                    expectSuccess = true
                    
                    // plugins
                    install(ContentNegotiation) {
                        json()
                    }
                    install(ResponseObserver) {
                        onResponse { response ->
                            Log.i("logger", "${response.status.value}")
                        }
                    }
                    install(DefaultRequest) {
                        header(HttpHeaders.ContentType, ContentType.Application.Json)
                    }
                }
            )
        }
    }
}

 

 

PostResponse

import kotlinx.serialization.Serializable

@Serializable
data class PostResponse(
    val id: Int,
    val userId: Int,
    val title: String,
    val body: String
)

 

PostApiServiceImpl

class PostApiServiceImpl(
    private val client: HttpClient,
) : PostApiService {
    override suspend fun getPosts(): List<PostResponse> {
        return client.get {
            url(HttpRoutes.POSTS)
        }.body()
    }
}

 

MainActivity (Compose)

class MainActivity : ComponentActivity() {
    private val service = PostApiService.create()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val posts = produceState<List<PostResponse>>(
                initialValue = emptyList(),
                producer = {
                    value = service.getPosts()
                }
            )
            CommunityTheme {
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    LazyColumn {
                        items(posts.value) {
                            Column(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(16.dp)
                            ) {
                                Text(text = it.title ?: "", fontSize = 20.sp)
                                Spacer(modifier = Modifier.height(4.dp))
                                Text(text = it.body ?: "", fontSize = 15.sp)
                            }
                        }
                    }
                }
            }
        }
    }
}

 

이러면 영상 속 예제대로 api를 호출할 수 있다.

 

추신

ktor-server 라이브러리도 제공되는데, API를 안드로이드 앱으로 만드는 이유가 있나?

멀티플랫폼 지원이니까 웹에서 띄운다는 느낌이겠지?

728x90