Kotlin

Kotlin(코틀린) FLow 란?

code2772 2024. 11. 21. 07:44
728x90
반응형

1. Flow란? 🌊

Flow는 시간이 지남에 따라 여러 값을 발행할 수 있는 데이터 스트림입니다.

Flow는 코틀린에서 제공하는 비동기 스트림 처리를 위한 API입니다. RxJava의 Observable이나 LiveData와 유사하지만, 코루틴을 기반으로 하며 더 간단하고 효율적인 API를 제공합니다.

쉽게 설명하면:

  • 실시간으로 업데이트되는 주식 가격 알림
  • 타이핑할 때마다 바뀌는 검색 결과
  • SNS의 실시간 피드 업데이트 같은 것들을 처리할 때 사용합니다.

1.1 Flow의 특징

  • 비동기적으로 데이터 스트림 처리
  • Cold Stream 특성 (구독할 때만 데이터 발행)
  • 코루틴 기반의 백프레셔(Backpressure) 지원
  • 구조화된 동시성 제공

1.2 기본 사용 예제

fun simple(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)// 비동기 처리 시뮬레이션
        emit(i)// 데이터 발행
    }
}

// 사용
suspend fun main() {
    simple().collect { value ->
        println(value)
    }
}

2. Flow의 기본 구성요소 🧱

2.1 Flow 빌더

// 1. flow 빌더
val numbersFlow = flow {
    emit(1)
    emit(2)
    emit(3)
}

// 2. flowOf 빌더
val simpleFlow = flowOf(1, 2, 3)

// 3. asFlow 확장 함수
val collectionFlow = listOf(1, 2, 3).asFlow()

2.2 Flow 수집

// 1. collect
flow.collect { value ->
    println(value)
}

// 2. collectLatest (새로운 값이 오면 이전 처리를 취소)
flow.collectLatest { value ->
    delay(100)// 처리 시간이 필요한 작업
    println(value)
}

// 3. firstOrNull/lastOrNull
val first = flow.firstOrNull()
val last = flow.lastOrNull()

3. Flow 연산자 🔧

3.1 변환 연산자

val flow = flow {
    emit(1)
    emit(2)
    emit(3)
}

// map
flow.map { it * 2 }
    .collect { println(it) }// 출력: 2, 4, 6// filter
flow.filter { it > 1 }
    .collect { println(it) }// 출력: 2, 3// transform
flow.transform { value ->
    emit(value)
    emit(value * 2)
}.collect { println(it) }// 출력: 1, 2, 2, 4, 3, 6

3.2 결합 연산자

val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("a", "b", "c")

// zip
flow1.zip(flow2) { number, letter ->
    "$number$letter"
}.collect { println(it) }// 출력: 1a, 2b, 3c// combine
val numbers = flowOf(1, 2, 3).onEach { delay(100) }
val letters = flowOf("a", "b", "c").onEach { delay(150) }
numbers.combine(letters) { number, letter ->
    "$number$letter"
}.collect { println(it) }

3.3 상태 및 공유

// stateIn - StateFlow로 변환
val stateFlow = flow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = initialData
)

// shareIn - SharedFlow로 변환
val sharedFlow = flow.shareIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    replay = 1
)

4. Flow의 종류 🔄

4.1 StateFlow

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun updateUser(user: User) {
        _uiState.update { currentState ->
            currentState.copy(user = user)
        }
    }
}

// 사용
viewModel.uiState.collect { state ->
    updateUI(state)
}

4.2 SharedFlow

class EventViewModel : ViewModel() {
    private val _events = MutableSharedFlow<Event>()
    val events = _events.asSharedFlow()

    fun triggerEvent(event: Event) {
        viewModelScope.launch {
            _events.emit(event)
        }
    }
}

5. 예외 처리 🚨

5.1 기본 예외 처리

flow {
    emit(1)
    throw RuntimeException("Error!")
}.catch { e ->
    emit(-1)// 에러 발생 시 대체 값 방출
}.collect { value ->
    println(value)
}

5.2 retry 로직

flow {
    emit(fetchDataFromNetwork())
}.retry(retries = 3) { cause ->
    cause is NetworkException
}.catch { e ->
    emit(cachedData)
}.collect { data ->
    processData(data)
}

6. Context와 Concurrency 🔄

6.1 Context 전환

flow {
    emit(1)
}.flowOn(Dispatchers.IO)// 상위 Flow 연산자의 Context 변경
  .collect { value ->
// Main Context에서 수집
  }

6.2 병렬 처리

fun processData(): Flow<Result> = flow {
    val results = (1..100).asFlow()
        .flatMapMerge(concurrency = 10) { value ->
            flow {
                emit(processValue(value))
            }
        }
        .toList()

    emit(Result(results))
}

7. 실전 사용 예제 💻

7.1 채팅 메시지 처리

class ChatViewModel : ViewModel() {
    private val _messages = MutableStateFlow<List<Message>>(emptyList())
    val messages = _messages.asStateFlow()

    fun sendMessage(text: String) {
        viewModelScope.launch {
            val newMessage = Message(text, System.currentTimeMillis())
            _messages.value = _messages.value + newMessage
            chatRepository.sendMessage(newMessage)
        }
    }

    init {
        // 실시간으로 새로운 메시지 수신
        viewModelScope.launch {
            chatRepository.receiveMessages()
                .collect { newMessage ->
                    _messages.value = _messages.value + newMessage
                }
        }
    }
}

쉬운 설명:

  • 카카오톡 같은 채팅앱에서 메시지를 주고받는 것과 같은 기능입니다.
  • 새 메시지가 오면 실시간으로 화면에 표시됩니다.

7.2 실시간 검색 구현

class SearchViewModel : ViewModel() {
    // 사용자가 입력하는 검색어를 담는 Flow
    private val searchQuery = MutableStateFlow("")

    // 검색 결과를 처리하는 Flow
    val searchResults = searchQuery
        .debounce(300) // 타이핑이 멈추고 300ms 후에 검색 시작
        .distinctUntilChanged() // 이전과 같은 검색어면 무시
        .flatMapLatest { query ->
            if (query.isEmpty()) {
                flowOf(emptyList()) // 빈 검색어면 빈 결과 반환
            } else {
                searchRepository.search(query) // 실제 검색 수행
            }
        }
        .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}

// 사용 방법
searchViewModel.searchResults.collect { results ->
    // 검색 결과를 화면에 표시
    showSearchResults(results)
}

쉬운 설명:

  • 네이버 검색창에 글자를 입력할 때마다 자동으로 검색 결과가 나타나는 것과 같은 기능입니다.
  • 사용자가 타이핑을 멈춘 후 0.3초 뒤에 검색을 시작하여 서버 부하를 줄입니다.

8. 테스트 🧪

8.1 Flow 테스트

@Test
fun `test flow emissions`() = runTest {
    val flow = flow {
        emit(1)
        emit(2)
        emit(3)
    }

    val values = flow.toList()
    assertEquals(listOf(1, 2, 3), values)
}

8.2 StateFlow 테스트

kotlin
Copy
@Test
fun `test state flow updates`() = runTest {
    val viewModel = SearchViewModel()
    val values = mutableListOf<String>()

    val job = launch {
        viewModel.searchResults.collect {
            values.add(it)
        }
    }

    viewModel.updateSearchQuery("test")
    delay(500)

    assertEquals(listOf("", "test"), values)
    job.cancel()
}

Flow를 사용하면 좋은 상황 📝

  1. 실시간 업데이트가 필요할 때
    • 주식 가격, 날씨 정보
    • 실시간 채팅
    • 위치 추적
  2. 연속적인 데이터 처리가 필요할 때
    • 검색 자동 완성
    • 파일 업로드 진행률
    • 센서 데이터 모니터링
  3. 자동 저장이 필요할 때
    • 문서 자동 저장
    • 폼 데이터 임시 저장
    • 설정 자동 동기화

4. Flow의 장점 ✨

  1. 효율적인 리소스 사용
    • 필요할 때만 데이터를 처리
    • 메모리 사용량 최적화
  2. 코드 가독성
    • 비동기 작업을 깔끔하게 표현
    • 유지보수가 쉬움
  3. 안정성
    • 에러 처리가 쉬움
    • 메모리 누수 방지

결론 🎯

Flow API는 코틀린에서 비동기 데이터 스트림을 처리하는 강력한 도구입니다. 이 글에서 다룬 내용을 바탕으로 실제 프로젝트에서 Flow를 효과적으로 활용할 수 있습니다.

참고 자료 📚

  • Kotlin 공식 문서
  • Android Developers 가이드
  • Kotlin Coroutines 공식 가이드
반응형