코틀린 2.0.20버전에서 context parameters라는 기능이 추가되었는데요. 이 기능을 처음 봤던 터라, 뭔가 하고 보기 시작했습니다. 그런데 이전에 1.6.2 버전에서 추가된 context receiver라는 친구가 있더라구요. 이를 대체하는 기능이 새로 추가되었고, 기존에 있던 context receiver는 제거된다고 합니다. 찾아보니 종속성을 위한 기능 같은데, 안드로이드에서는 대부분 hilt와 같은 라이브러리로 구현을 하기 때문에 예제나 이런 부분들을 많이 찾아볼 수 없나 봅니다(구시대 프로젝트들은 코틀린 버전이 낮은 것도 한 몫 하겠군요)
하지만 꽤 쓸모있는 부분이러고 생각되어 한 번 공부해보았습니다.
Context Receiver
context parameter를 보기 전에 먼저 적용됐던 context receiver를 알아야 코드가 이해가 될 것 같아 먼저 Context Receiver라고 불리는 기능을 확인해보겠습니다.
interface UserRepository {
fun getUserName(): String
}
class UserRepositoryImpl : UserRepository {
override fun getUserName(): String = "Niaka"
}
// context(UserRepository)를 선언!
context(UserRepository)
fun greetUser(): String {
val name = getUserName() // 바로 사용 가능
return "Hello, $name!"
}
fun main() {
val repo = UserRepositoryImpl()
with(repo) {
println(greetUser()) // context 안에서 호출
}
}
위와 같이 코드를 작성하면, Context Receiver라는 것을 사용할 수 있게 됩니다. 뭔가 기묘한 코드 작성 방식이라고 생각을 하는데요. context라는 코드가 함수 위에 클래스를 가지고 있는 것 만으로 with를 통해 사용할 수 있는 부분이 기존 코드 작성방식과는 다른데, 이 부분이 묘하게 다른 언어 같기도 하네요.
하여튼 이런식으로 코드를 작성하면 The feature "context receivers" is experimental and should be enabled explicitly 이런 문구가 뜰 텐데, 그럼 아래와 같은 코드를 build.gradle에 추가해주어야 합니다.
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += "-Xcontext-receivers"
}
이는 그저 코틀린 옵션을 통해 명시해주어야 한다는 것을 의미하는데, 아무래도 이게 있고 없고의 차이에 따라 코드를 내부적으로 읽는 방식을 달리해야 해서 그렇습니다. XompilerArgs는 여러가지 옵션들을 부과할 수 있는데, 각각의 옵션별로 기능들이 달라지게 됩니다. 위 옵션은 Context Receiver를 사용하기 위한 문법을 활성화하겠다는 의미 정도로 이해하면 될 것 같습니다.
정리하자면, 이러한 Context Receiver는 특정 context(주입한 클래스)안에서 사용할 수 있는 메소드를 구현하는 기능입니다. Context(ClassName)을 통해 ClassName에 입력된 type의 클래스에서만 그 함수를 실행시키도록 강제하고, 이를 with(Class)를 통해 호출할 수 있게 제한함으로서 좀 더 정확한 맥락을 개발자에게 요구하게 됩니다. 이는 좀 더 복잡해지는 부분이 있지만 모르고 사용하는 것을 방지하는데 도움을 줄 수 있어 코드의 이해도를 높히게 됩니다.
이는 확장함수의 형태와 비슷한 모습으로 볼 수 있는데요. 차이점은 아래 Context Parameters를 다룬 이후에 소개해드리겠습니다.
Context Parameters
코틀린 블로그에서는 코틀린 2.2.0 버전에서 추가될 기능이라고 소개하는데요. 참고에 달아둔 코틀린 release 문서를 보면 2.0.20 버전에 관련 내용이 소개되어 있습니다. 그런데, 좋은 소개는 아닌게, context Receiver라는 친구를 제대로 파악하기도 전에 조만간 삭제가 될 예정이라고 친절하게 알려주네요. 물론 블로그 글에 의하면 2.2.0-beta에서도 아직 사라지지는 않은 것 같습니다.
코드는 아래와 같이 작성하면 됩니다.(이 코드를 inteliJ에서 실행시키려면 Additional command line parameters에 -Xcontext-parameters 옵션을 추가해야 합니다. 물론 context Receiver에도 xContext-receivers를 추가해야 합니다.) gradle파일을 사용하는 경우, kotlinOptions에 -Xcontext-parameters 를 추가하면 됩니다. 위의 설명에서 옵션만 변경하면 됩니다.
interface UserRepository {
fun getUserName(): String
}
class UserRepositoryImpl : UserRepository {
override fun getUserName(): String = "Niaka"
}
// 파라미터의 이름이 생김
context(repo: UserRepository)
fun greetUser(): String {
val name = repo.getUserName() // 바로 사용 가능
return "Hello, $name!"
}
fun main() {
val repo = UserRepositoryImpl()
with(repo) {
println(greetUser()) // context 안에서 호출
}
}
그런데 이 코드를 inteliJ 2024.3.1.1 버전에서 실행하는 경우, syntax error가 나는데, 정작 run은 제대로 동작합니다. android studio도 마찬가지인데, 이는 코틀린 2.2.0 버전의 xContext-parameters 옵션을 구버전 IDE가 받아들이지 못해 발생하는 문제로 보입니다.
확장함수, DI와의 차이점
사실 context-receiver를 제거하면서 코틀린 공식문서에서도 대안이 확장함수거나 명시적 DI인 걸 보면, 그에 대응되는 좀 더 발전된 코드를 만들고 싶은 것은 분명하나 비슷한 쓰임새인 것은 확실합니다. 그러나 문법이 다른 만큼 차이점은 분명히 존재하는데요.
먼저 가장 큰 차이점은 확장함수와 다르게 여러가지 클래스를 context()내부에 등록하면 한 번에 2개 이상의 클래스를 주입해서 사용할 수 있다는 점입니다. 위 예제에서는 repo 하나를 접근시켰지만, context(a: A, b: B, c: C)이런식으로 작성한 후, 내부 함수에서 a와 b, c를 모두 접근하는 복합 함수를 작성할 수 있는 것이죠.
그 다음은 DI할 때 다른 라이브러리를 사용하는 것 보다 간단한 형태의 주입이 가능하다는 점입니다. 안드로이드에서 주로 사용되는 hilt의 경우, DI를 위한 준비 과정들이 필요합니다. 먼저 화면에서는 @AndroidEntryPoint, 뷰모델에서는 @HiltViewModel 등과 같은 어노테이션이 사용되는 것부터 @Module, @InstallIn 등과 같은 여러 주석들을 통해 광범위한 DI 옵션을 세팅해야 하죠. 하지만 context-parameters는 그 기능이 한정되고 많은 뎁스(ui -> viewModel -> useCase -> repo...)를 지나면서 with가 계속 깊어진다는 단점이 있지만, 일단 구성이 간단합니다. 그리고 필요한 부분만 DI를 사용하는 형태를 취할 수도 있는 것도 차이점입니다. 물론 기존 코드들의 아키텍처가 이미 충분히 기존 라이브러리와 코드 형식에 맞춰져 있고 암묵적인 동의가 잘 되어 있어 아마 이를 이용해 DI를 하는 코드가 작성되려면 많은 시간이 걸릴 것 같긴 합니다만, 여러 방식으로 활용해볼 여지는 있을 것 같습니다.
정리
코드를 보여주고 차이점을 정의했습니다만, 기존 확장함수나 DI의 개념에서 크게 벗어나지 않은 것 같습니다. 실제로 'Context가 필요함을 정의' 하는 것에 초점을 두는 만큼, 추후 코드가 발전하는 방향도 더 많은 기능 보다는 더 편리한 적용 및 가독성을 위주로 업데이트될 것 같다는 생각이 듭니다. 물론 제 실력에서는 굳이 익숙한 기존 코드를 두고 새로운 기능을 도입해야 하는가에 대한 의문은 남습니다만, 여러 context를 선언해 하나의 함수를 만드는 방법은 현재 제가 작업하는 코드에서도 꽤나 쓰일 수 있을 것 같습니다(물론 프로젝트의 코틀린 버전을 올리는 문제부터 해결해야 하지만요).
나름 재밌어 보이는 코드라 정리해보게 되었는데, 실제로 사용을 하게 될지는 모르겠습니다. 아무래도 안드로이드는 이미 hilt로 DI를 잘 잡아두기도 했고, 너무 최신 코드라 왠만한 프로젝트에는 적용할 수 없기 때문에 많은 사람들이 모르기도 하니 코드를 이해시키는데 시간도 충분히 소모해야 할 것 같으니까요.
이런 코드가 어떤 목표를 가지고 나왔고 어떤 방식으로 쓰이는 지 명확해진다면, 코틀린의 방향성을 좀 더 가시적으로 이해해볼 수 있을 것 같습니다(그런 분석을 할 수 있는 사람이 된다면 더할 나위 없겠네요).
참고
https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters/
Update on Context Parameters | The Kotlin Blog
Context parameters will be replacing context receivers in Kotlin, with version 2.2.0 featuring them as Beta. We are committed to providing an easy migration between both features, including dedicated compiler and IntelliJ IDEA support.
blog.jetbrains.com
https://carrion.dev/en/posts/context-parameters-kotlin/
Understanding Context Parameters in Kotlin 2.2.0
Exploring Kotlin 2.2.0's new context parameters feature and how it can improve your code's readability and maintainability.
carrion.dev
What's new in Kotlin 2.0.20 | Kotlin
kotlinlang.org
'안드로이드 > 코틀린' 카테고리의 다른 글
ComposeUI의 구성 순서와 SideEffect (0) | 2025.05.22 |
---|---|
media3의 exoPlayer - ComposeUI (2) | 2025.04.16 |
Kotlin - Dispatcher (1) | 2025.04.01 |
EditText와 RecyclerView(list에서 EditText 사용시 주의점) (0) | 2024.10.24 |
Android PhotoPicker (4) | 2024.08.19 |
댓글