Android & Kotlin
Kotlin Flow 연산자 distinctUntilChanged 분석
쉽코기
2025. 9. 4. 11:02
기본개념
- Kotlin Flow의 연산자로 연속된 중복값을 필터링하여 새로운 Flow를 생성하여 반환함
- 이전 값과 현재 값이 같을 때(중복일 때) 현재 값을 방출하지 않고 필터링함
- 필터 조건이 True일 때는 아래로 값을 내려주지 않음 (중복으로 판단)
- 값이 변경될 때만 다운스트림으로 값을 전달함
내부 진행 과정
1. 외부 호출 (사용자 코드)
viewModel.uiState.map { it.currentTab?.isScrollable == true }
.distinctUntilChanged()
.onEach { appBarBehavior?.setScrollable(it) }
.launchInByRepeatOnStarted(this)
2. distinctUntilChanged 함수 : flow - Distinct.kt 파일
private fun <T> Flow<T>.distinctUntilChangedBy(
keySelector: (T) -> Any?,
areEquivalent: (old: Any?, new: Any?) -> Boolean
): Flow<T> = when {
this is DistinctFlowImpl<*> && this.keySelector === keySelector && this.areEquivalent === areEquivalent -> this // same
else -> DistinctFlowImpl(this, keySelector, areEquivalent)
}
- 일반적으로 keySelector (비교역할을 하는 람다) 가 같은객체일 경우는 희박하므로 else 문으로 통과
3. DistinctFlowImpl 클래스
- Collect 를 오버라이딩하여 필터링을 수행하여 새로운 Flow 를 반환함
- 값이 다른 경우에만 값을 emit 하여 방출 함
- 최종적으로 1에서 launchInByRepeatOnLifeCycle 내부의 collect 는 일반 flow 가 아닌 DistinctFlowImpl 에 의해서 오버라이딩 된 collect 가 호출 됨
private class DistinctFlowImpl<T>(
private val upstream: Flow<T>,
@JvmField val keySelector: (T) -> Any?,
@JvmField val areEquivalent: (old: Any?, new: Any?) -> Boolean
): Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
var previousKey: Any? = NULL
upstream.collect { value ->
val key = keySelector(value)
@Suppress("UNCHECKED_CAST")
if (previousKey === NULL || !areEquivalent(previousKey, key)) {
previousKey = key
collector.emit(value)
}
}
}
}
실제 사용 예시
// 값 자체를 equals()로 비교
flow.distinctUntilChanged()
// 특정 속성만 비교하여 중복 제거
flow.distinctUntilChanged { it.property }
// 두 값이 같은지 판단하는 커스텀 로직
flow.distinctUntilChanged { old, new -> /* 비교 로직 */ }
// 특정 속성을 추출한 후 커스텀 비교
flow.distinctUntilChangedBy(
keySelector = { it.property },
areEquivalent = { old, new -> /* 비교 로직 */ }
)