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 -> /* 비교 로직 */ }
)