1. Scope 지정 확장 함수
핵심 로직과 부가 기능을 분리하자는 AOP(관점 지향 프로그래밍) 개념에서 떠올린 확장 함수입니다
기존의 View에서 Scope를 열어 사용할 때 불필요한 Depth가 생기게 됩니다.
의미만 잘 전달된다면 핵심 로직과 분리 되어도 상관 없는 구문이라 생각하여 아래와 같이 정의 해보았습니다.
fun LifecycleOwner.withInMainScope(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
action: suspend () -> Unit
) {
lifecycleScope.launch(
context = Dispatchers.Main + coroutineContext,
start = start,
) {
action()
}
}
fun LifecycleOwner.withInIOScope(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
action: suspend () -> Unit
) {
lifecycleScope.launch(
context = Dispatchers.IO + coroutineContext,
start = start,
) {
action()
}
}
AS-IS
// AS-IS
private fun testMethod() {
lifecycleScope.launch(Dispatchers.Main) {
mainTask()
}
}
private suspend fun mainTask() { }
TO-BE
아래와 같이 코드의 의미는 해치지 않으면서도 불필요한 depth를 줄일 수 있었습니다.
// TO-BE
private fun testMethod() = withInMainScope {
mainTask()
}
private suspend fun mainTask() { }
2. DP 변환 확장 함수
기존엔 Util.dpToPx() 등으로 사용하고 있었는데 해당 구문이 반복되니 가독성이 떨어지는 느낌을 많이 받았습니다.
Kotlin의 Context Receiver를 이용하여 간단하게 사용할 수 있도록 수정 해보았습니다.
context(Context)
val Float.dp get() = this * resources.displayMetrics.density
context(Context)
val Int.dp get() = this.toFloat().dp
Context Receiver 활성화하는 방법
더보기
build.gralde (app)에서 아래 구문을 추가해주어야 사용 가능합니다.
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs += "-Xcontext-receivers" // 컨텍스트 수신자 컴파일러 옵션 추가
}
사용 예시
private fun testMethod() {
binding.mapView.updateLayoutParams {
width = 100.dp
}
}
3. Flow 구독 함수
View에서 Flow를 구독할 때 사용하는 확장 함수입니다.
Flow의 확장 함수임에도 Context Receiver를 통해 LifecycleOwner에 접근할 수 있도록 구성 해보았습니다.
context(LifecycleOwner)
inline fun <T> Flow<T>.launchAndCollectIn(
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
crossinline action: suspend CoroutineScope.(T) -> Unit
) = lifecycleScope.launch {
lifecycle.repeatOnLifecycle(minActiveState) {
collect {
action(it)
}
}
}
사용 예시
viewModel.effectChannel.launchAndCollectIn { handleEffect(it) }
여러 Flow를 구독하는 경우
기존엔 Flow를 구독할 때 아래와 같은 확장 함수를 만들어서 사용하고 있었습니다.
fun LifecycleOwner.repeatOnStarted(block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, block)
}
}
여러개의 Flow를 구독하려면 아래와 같이 확장 함수를 여러번 사용하거나 Flow마다 새로운 scope를 열어서 구독하는 구문이 필요했습니다.
// View
repeatOnStarted {
viewModel.state.collect(::handleState)
}
repeatOnStarted {
viewModel.effectChannel.collect(::handleEffect)
}
launchAndCollectIn 함수를 사용하면 아래와 같이 구독 가능하므로 훨씬 유용하게 사용하고 있습니다.
with(viewModel) {
state.launchAndCollectIn { handleState(it) }
effectChannel.launchAndCollectIn { handleEffect(it) }
}