관리 메뉴

나만을 위한 블로그

[Android] Ktor란? 본문

Android

[Android] Ktor란?

참깨빵위에참깨빵 2022. 6. 12. 19:01
728x90
반응형

최근 미디엄에서 안드로이드 관련 게시물을 보다가 간간이 Ktor라는 걸 레트로핏과 비교하는 글을 여럿 봤다.

레트로핏과 비교하는 거라면 비동기 통신 라이브러리란 건데 이게 뭔지 알아보고자 포스팅한다.

Ktor 공식 홈페이지에서 설명하는 Ktor는 아래와 같다.

 

https://ktor.io/docs/welcome.html

 

Welcome | Ktor

 

ktor.io

Ktor는 웹 앱, HTTP 서비스, 모바일 및 브라우저 앱과 같은 연결된 앱을 쉽게 구축할 수 있는 프레임워크다. 최신 연결 앱은 사용자에게 최고의 경험을 제공하기 위해 비동기 방식이어야 하며 코틀린 코루틴은 쉽고 간단하게 이를 수행할 수 있는 기능을 제공한다. Ktor의 목표는 연결된 앱을 위한 종단 간(end-to-end) 멀티플랫폼 프레임워크를 제공하는 것이다

 

공식 홈페이지에선 Ktor를 프레임워크라고 설명하고 모바일에서도 사용 가능해 보인다.

다른 Ktor의 정의를 좀 더 찾아봤다.

 

https://www.codingame.com/playgrounds/36835/ktor-as-a-backend-application

 

Ktor as a backend Application

Explore this playground and try new concepts right into your browser

www.codingame.com

Ktor는 개발자가 코틀린으로 비동기 클라이언트 및 서버 앱을 작성할 수 있게 하는 코틀린 프레임워크다. 완전히 호환되지는 않지만 JVM, 자바스크립트, 안드로이드, iOS 같은 여러 플랫폼을 대상으로 한다

 

https://medium.com/google-developer-experts/how-to-use-ktor-client-on-android-dcdeddc066b9

 

How to use Ktor client on Android

Ktor is an asynchronous open source framework for creating microservices and web applications. It was developed with Kotlin by Jetbrains…

medium.com

Ktor는 마이크로서비스 및 웹 앱을 만들기 위한 비동기식 오픈소스 프레임워크다. Jetbrains에서 코틀린으로 만들었다. 설치, 사용이 간편하며 실행 파이프라인에 단계를 추가하려는 경우 확장 가능하다. 코루틴의 비동기 속성으로 인해 여러 요청을 수신할 수 있고 멀티플랫폼이기도 하다. 즉 서버뿐 아니라 Ktor 클라이언트를 써서 안드로이드, iOS, JS에서 마이크로서비스를 사용할 수도 있다
Ktor 클라이언트를 쓰면 요청 생성, 응답 처리 및 인증, JSON 직렬화 등과 같은 기능으로 기능을 확장할 수 있다

 

https://medium.com/geekculture/what-is-ktor-and-why-should-you-learn-it-b7e5600a4d50

 

What is Ktor, and Why Should You Learn It?

Start building backends using Kotlin.

medium.com

다음은 Ktor의 가장 중요한 기능 일부다

- 라우팅
- 요청, 응답 처리
- 템플릿(Templating)
- 컨텐츠 협상(negotiation) 및 직렬화
- 인증 및 권한 부여
- ions
- HTTP
- 소켓
- 모니터링
- 관리

 

이외에 Ktor에 대해 검색해보면 아직 앱 클라이언트 관련 이야기보다는 백엔드와 연관지어 설명하는 글들이 좀 더 많다. 예제 코드 또한 백엔드 위주로 작성해서 포스팅한 사람들이 많이 있다.

국내 백엔드 생태계는 자바 스프링이 꽉 잡고 있다고 알고 있는데 조금씩 코틀린 스프링(코프링)을 도입하는 곳도 있다는데 Ktor가 어떻게 될지는 좀 더 두고봐야겠다. 안드로이드 진영에서도 Ktor vs Retrofit 같은 vs 놀이가 유행이지만 앞으로 어떻게 될지 모르니 안드로이드 개발자로서도 관심있게 지켜보면 좋을 것 같다.

 

그리고 지켜볼 뿐만 아니라 어떻게 작동하는지도 코드로 짜보는 게 좋을 것 같아서 간단하게 예제를 찾아봤다.

먼저 앱 수준 gradle에 의존성을 추가한다.

 

 // Ktor
implementation "io.ktor:ktor-client-core:1.6.3"
implementation "io.ktor:ktor-client-android:1.6.3"
implementation "io.ktor:ktor-client-serialization:1.6.3"
implementation "io.ktor:ktor-client-logging:1.6.3"
implementation "io.ktor:ktor-client-gson:1.6.3"

 

모든 의존성이 Ktor를 안드로이드에서 쓰기 위해 필요한 것은 아니니 필요한 것만 골라 쓰자. 여기선 이 의존성들을 모두 넣은 채로 진행한다.

 

그리고 요청을 보내고 응답을 받을 때 사용할 data class를 각각 정의한다.

 

import kotlinx.serialization.Serializable

@Serializable
data class PostRequest(
    val body: String,
    val title: String,
    val userId: Int
)
import kotlinx.serialization.Serializable

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

 

요청, 응답 모두 @Serializable을 쓰는 것이 눈에 띈다. 다음으로 요청을 보낼 주소를 정의한다.

 

object HttpRoutes {
    const val POSTS = "https://jsonplaceholder.typicode.com/posts"
}

 

그리고 인터페이스를 정의한다. 레트로핏과는 많이 다른 형태라 신선하다.

 

import io.ktor.client.*
import io.ktor.client.engine.android.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*

interface PostsService {
    companion object {
        fun create(): PostsService {
            return PostsServiceImpl(
                client = HttpClient(Android) {
                    install(Logging) {
                        level = LogLevel.ALL
                    }
                    install(JsonFeature) {
                        serializer = KotlinxSerializer()
                    }
                }
            )
        }
    }

    suspend fun getPosts(): List<PostResponse>

    suspend fun createPost(postRequest: PostRequest): PostResponse?
}

 

companion object로 create()를 만드는 부분이 기존 레트로핏과는 많이 다르다. 대신 좀 더 직관적으로 보이는 건 있다.

어떤 레벨의 로그까지 표시할 것인지와 JSON 직렬화를 저렇게 정의하니 직관적으로 보이는 느낌이다. 그 외에는 코루틴을 쓴다면 익숙한 키워드인 suspend를 붙인 함수 2개 뿐이다.

그리고 이 인터페이스를 구현하는 클래스를 하나 만든다.

 

import android.util.Log
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.http.*

class PostsServiceImpl(
    private val client: HttpClient
) : PostsService {
    private val TAG = this.javaClass.simpleName

    override suspend fun getPosts(): List<PostResponse> {
        return try {
            client.get {
                url(HttpRoutes.POSTS)
            }
        } catch (e: RedirectResponseException) {
            // 3xx responses
            Log.e(TAG, "Error : ${e.response.status.description}")
            emptyList()
        } catch (e: ClientRequestException) {
            // 4xx responses
            Log.e(TAG, "Error : ${e.response.status.description}")
            emptyList()
        } catch(e: ServerResponseException) {
           // 5xx responses
            Log.e(TAG, "Error : ${e.response.status.description}")
            emptyList()
         } catch(e: Exception) {
            Log.e(TAG, "Error : ${e.message}")
            emptyList()
          }
    }

    override suspend fun createPost(postRequest: PostRequest): PostResponse? {
        return try {
            client.post<PostResponse> {
                url(HttpRoutes.POSTS)
                contentType(ContentType.Application.Json)
                body = postRequest
            }
        } catch (e: RedirectResponseException) {
            // 3xx responses
            Log.e(TAG, "Error : ${e.response.status.description}")
            null
        } catch (e: ClientRequestException) {
            // 4xx responses
            Log.e(TAG, "Error : ${e.response.status.description}")
            null
        } catch(e: ServerResponseException) {
            // 5xx responses
            Log.e(TAG, "Error : ${e.response.status.description}")
            null
        } catch(e: Exception) {
            Log.e(TAG, "Error : ${e.message}")
            null
        }
    }
}

 

 

이제 지금까지 만든 것들을 액티비티에서 사용하자.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.ktorpractice.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

class ThirdActivity : AppCompatActivity(), CoroutineScope {

    private val TAG = this.javaClass.simpleName
    private lateinit var job: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.IO + job

    private val client = PostsService.create()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
        setContentView(R.layout.activity_third)

        CoroutineScope(Dispatchers.IO).launch {
            val response: List<PostResponse> = client.getPosts()
            for (i in response.indices) {
                Log.e(TAG, "response[i].id : ${response[i].id}")
                Log.e(TAG, "response[i].body : ${response[i].body}")
                Log.e(TAG, "response[i].title : ${response[i].title}")
                Log.e(TAG, "response[i].userId : ${response[i].userId}")
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

}

 

이렇게 하고 실행하면 로그캣에 아래와 같은 로그들이 출력된다.

 

 

body 밑에는 개행문자 때문에 밑으로 내려간 문장들이 있는데 잘려서 안 보인다. 정리가 안 된 코드지만 레트로핏보다 좀 더 직관적인 코드를 써서 서버와 통신하는 걸 볼 수 있다.

 

간단하게 Ktor를 안드로이드에서 쓰는 법을 확인해봤다.

이것이 레트로핏을 대체할 수 있을지는 모르겠지만 알아둬서 나쁜 개발 지식은 없기 때문에 틈날 때마다 Hilt를 써서 MVVM 클린 아키텍처에 Ktor를 적용한 미니 프로젝트를 만들어 보면 재밌을 것 같다.

반응형
Comments