-
Kotlin Flow 연산자 distinctUntilChanged 분석Android & Kotlin 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 -> /* 비교 로직 */ } )'Android & Kotlin' 카테고리의 다른 글
AndroidStudio/IntelliJ IDE 에서 변수 추적이 안되는 케이스 분석 (1) 2025.08.08 Serialization 라이브러리 마이그레이션 과정(Gson -> Kotlinx-Serialization) (1) 2023.12.23 직렬화 라이브러리 마이그레이션 이유(Gson 에서 Kotlinx-Serialization) (1) 2023.12.22 Hilt 관련 리펙토링 기록 (@binds @Provides 차이 , UsecaseModule) (1) 2023.01.08 DialogFragment 오류 정리 (Radius , 크기 지정) (0) 2022.11.28