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를 사용하면 좋은 상황 📝
- 실시간 업데이트가 필요할 때
- 주식 가격, 날씨 정보
- 실시간 채팅
- 위치 추적
- 연속적인 데이터 처리가 필요할 때
- 검색 자동 완성
- 파일 업로드 진행률
- 센서 데이터 모니터링
- 자동 저장이 필요할 때
- 문서 자동 저장
- 폼 데이터 임시 저장
- 설정 자동 동기화
4. Flow의 장점 ✨
- 효율적인 리소스 사용
- 필요할 때만 데이터를 처리
- 메모리 사용량 최적화
- 코드 가독성
- 비동기 작업을 깔끔하게 표현
- 유지보수가 쉬움
- 안정성
- 에러 처리가 쉬움
- 메모리 누수 방지
결론 🎯
Flow API는 코틀린에서 비동기 데이터 스트림을 처리하는 강력한 도구입니다. 이 글에서 다룬 내용을 바탕으로 실제 프로젝트에서 Flow를 효과적으로 활용할 수 있습니다.
참고 자료 📚
- Kotlin 공식 문서
- Android Developers 가이드
- Kotlin Coroutines 공식 가이드
반응형
'Kotlin' 카테고리의 다른 글
코틀린 디자인 패턴과 아키텍처 (0) | 2024.11.26 |
---|---|
코틀린(Kotlin)데이터 클래스와 sealed 클래스 (0) | 2024.11.25 |
코틀린이란? (Kotlin) (1) | 2024.11.19 |
QueryDSL 란, 주요 메서드 및 장단점 기본 설명 (0) | 2024.08.13 |
테스트 코드 작성 이유 및 방법 - SpringBoot, Kotlin (0) | 2024.08.09 |