코틀린 Flow 정리

2025-05-30T05:40:24
# Android Kotlin Flow 완벽 가이드 🌊 ## 들어가며 Android 개발을 하다 보면 **실시간으로 변화하는 데이터**를 다뤄야 할 때가 많습니다. 예를 들어: - 채팅 메시지가 실시간으로 업데이트되는 화면 - 위치 정보가 계속 변경되는 지도 앱 - 메모 목록이 자동으로 갱신되는 노트 앱 이런 상황에서 **Kotlin Flow**가 빛을 발합니다. Flow를 제대로 이해하고 사용하면 더 반응적이고 효율적인 앱을 만들 수 있어요. ## Flow란 무엇인가? 🤔 **Flow = 시간에 따라 연속적으로 데이터를 방출하는 스트림** 일반적인 함수는 한 번 호출하면 하나의 값을 반환하고 끝나지만, Flow는 여러 개의 값을 시간차를 두고 연속적으로 방출할 수 있습니다. ```kotlin // 일반 함수 (한 번만) fun getNumber(): Int { return 42 // 한 번 반환하고 끝 } // Flow (계속해서) fun getNumberFlow(): Flow<Int> = flow { emit(1) // 첫 번째 값 delay(1000) emit(2) // 두 번째 값 delay(1000) emit(3) // 세 번째 값 } ``` ## Flow vs 일반 함수 비교 📊 ### 일반 함수의 한계 ```kotlin fun getUserList(): List<User> { return database.getAllUsers() // 현재 시점의 데이터만 반환 } // 사용할 때 val users = getUserList() // 이 시점 이후 데이터 변경되면 모름 ``` ### Flow의 장점 ```kotlin fun getUserListFlow(): Flow<List<User>> { return database.getAllUsers() // 데이터 변경 시마다 새로운 값 방출 } // 사용할 때 userListFlow.collect { users -> println("업데이트된 사용자 목록: $users") // 데이터베이스가 변경될 때마다 자동으로 호출됨 } ``` ## Flow의 생명주기 🔄 Flow는 **Cold Stream** 방식으로 동작합니다: ### 1. 생성 단계 (Cold) ```kotlin val flow = repository.getAllBlocks() // 아직 아무것도 실행되지 않음 ``` ### 2. 구독 시작 (Hot) ```kotlin flow.collect { data -> // 여기서 실제 작업 시작 println("받은 데이터: $data") } ``` ### 3. 데이터 방출 ```kotlin // 데이터 소스에서 변경이 발생할 때마다 emit(newData) // Room 등에서 자동으로 처리 ``` ### 4. 구독 취소 ```kotlin // Coroutine이 취소되거나 수집기가 종료되면 자동으로 정리 ``` ## 실제 사용 예시: 메모 앱 📝 메모 앱을 예로 Flow가 어떻게 활용되는지 살펴보겠습니다. ### Repository 계층 ```kotlin interface BlockRepository { fun getAllBlocks(): Flow<List<Block>> // Flow 반환 suspend fun insertBlock(block: Block): Long suspend fun deleteBlock(id: Long) } class BlockRepositoryImpl @Inject constructor( private val blockDao: BlockDao ) : BlockRepository { override fun getAllBlocks(): Flow<List<Block>> { return blockDao.getAllBlocks() .map { entities -> entities.map { it.toDomain() } } } } ``` ### Room DAO ```kotlin @Dao interface BlockDao { @Query("SELECT * FROM blocks ORDER BY updatedAt DESC") fun getAllBlocks(): Flow<List<BlockEntity>> // Room이 Flow 지원 @Insert suspend fun insertBlock(block: BlockEntity): Long } ``` ### ViewModel ```kotlin @HiltViewModel class BlockListViewModel @Inject constructor( private val repository: BlockRepository ) : ViewModel() { // Flow를 StateFlow로 변환하여 UI에서 사용 val blocks: StateFlow<List<Block>> = repository.getAllBlocks() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun addBlock(content: String) { viewModelScope.launch { val newBlock = Block(content = content) repository.insertBlock(newBlock) // Flow 덕분에 UI가 자동으로 업데이트됨! } } } ``` ### Compose UI ```kotlin @Composable fun BlockListScreen( viewModel: BlockListViewModel = hiltViewModel() ) { val blocks by viewModel.blocks.collectAsState() LazyColumn { items(blocks) { block -> BlockItem( block = block, onDelete = { viewModel.deleteBlock(block.id) } ) } } // blocks가 변경될 때마다 자동으로 리컴포지션 발생 } ``` ## 실시간 업데이트의 마법 ✨ 위 코드에서 주목할 점은 **데이터 변경 시 UI가 자동으로 업데이트**된다는 것입니다: 1. 사용자가 새 메모 추가 버튼 클릭 2. `addBlock()` 함수가 데이터베이스에 새 메모 저장 3. Room이 변경을 감지하고 새로운 데이터를 Flow에 방출 4. Compose UI가 자동으로 리컴포지션되어 새 메모 표시 **개발자가 수동으로 UI를 업데이트할 필요가 없습니다!** ## Flow 연산자 활용하기 🛠️ Flow는 다양한 연산자를 제공하여 데이터를 변환하고 조작할 수 있습니다. ### map - 데이터 변환 ```kotlin repository.getAllBlocks() .map { blocks -> blocks.filter { !it.content.isBlank() } // 빈 메모 제거 } .collect { filteredBlocks -> // 필터링된 결과 사용 } ``` ### combine - 여러 Flow 결합 ```kotlin class SearchViewModel @Inject constructor( private val repository: BlockRepository ) : ViewModel() { private val _searchQuery = MutableStateFlow("") val searchQuery: StateFlow<String> = _searchQuery.asStateFlow() // 메모 목록과 검색어를 결합하여 검색 결과 생성 val searchResults = combine( repository.getAllBlocks(), searchQuery ) { blocks, query -> if (query.isEmpty()) { blocks } else { blocks.filter { it.content.contains(query, ignoreCase = true) } } }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = emptyList() ) fun updateSearchQuery(query: String) { _searchQuery.value = query } } ``` ### debounce - 입력 지연 처리 ```kotlin // 사용자가 타이핑을 멈춘 후 300ms 후에 검색 실행 searchQuery .debounce(300) .distinctUntilChanged() .flatMapLatest { query -> repository.searchBlocks(query) } .collect { results -> // 검색 결과 처리 } ``` ## StateFlow vs Flow 🔄 ### Flow - Cold Stream: 구독할 때만 활성화 - 상태를 유지하지 않음 - 구독자마다 독립적인 스트림 ### StateFlow - Hot Stream: 항상 활성화 상태 - 현재 상태를 유지 - 마지막 값을 즉시 방출 - Compose와 완벽한 호환성 ```kotlin // Flow 사용 val dataFlow: Flow<List<Item>> = repository.getData() // StateFlow 사용 (UI에 권장) val dataState: StateFlow<List<Item>> = repository.getData() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = emptyList() ) ``` ## 메모리 관리와 성능 최적화 ⚡ ### 자동 구독 취소 ```kotlin @Composable fun MyScreen() { val data by viewModel.data.collectAsState() // Composable이 사라지면 자동으로 구독 취소 // 메모리 누수 방지 } ``` ### SharingStarted 전략 ```kotlin val data = repository.getData() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed( stopTimeoutMillis = 5000, // 5초 후 구독 정지 replayExpirationMillis = 0 // 재구독 시 마지막 값만 재생 ), initialValue = emptyList() ) ``` ### conflate - 백프레셔 처리 ```kotlin repository.getFrequentUpdates() .conflate() // 빠른 업데이트 시 최신 값만 유지 .collect { latestValue -> // 최신 값만 처리 } ``` ## 에러 처리 🚨 ```kotlin repository.getAllBlocks() .catch { exception -> // 에러 발생 시 처리 emit(emptyList()) // 기본값 방출 logError(exception) } .collect { blocks -> // 정상 데이터 또는 기본값 받음 } ``` ## 실무에서 주의할 점 ⚠️ ### 1. Flow vs suspend 함수 구분 ```kotlin // ✅ 올바른 사용 fun getAllBlocks(): Flow<List<Block>> // 실시간 업데이트 필요 suspend fun getBlockById(id: Long): Block? // 한 번만 조회 // ❌ 잘못된 사용 suspend fun getAllBlocks(): Flow<List<Block>> // suspend 불필요 ``` ### 2. Context Switching 주의 ```kotlin repository.getAllBlocks() .flowOn(Dispatchers.IO) // IO 작업은 IO 디스패처에서 .collect { blocks -> // Main 스레드에서 UI 업데이트 } ``` ### 3. 무한 재시도 방지 ```kotlin repository.getData() .retry(3) { exception -> exception is NetworkException // 특정 예외만 재시도 } .collect { data -> // 최대 3번 재시도 후 데이터 받음 } ``` ## 마무리 🎯 Flow는 Android 앱에서 **반응형 프로그래밍**을 구현하는 핵심 도구입니다. **Flow의 핵심 장점:** - ✅ 실시간 데이터 업데이트 - ✅ 메모리 효율적 관리 - ✅ 자동 구독 취소 - ✅ 풍부한 연산자 지원 - ✅ Compose와 완벽한 호환성 **언제 Flow를 사용해야 할까요?** - 데이터베이스 변경 사항을 실시간으로 반영해야 할 때 - 사용자 입력에 반응하는 검색 기능 - 위치, 센서 데이터 등 지속적으로 변경되는 데이터 - 네트워크 상태나 연결 상태 모니터링 Flow를 제대로 활용하면 더 반응적이고 사용자 경험이 뛰어난 Android 앱을 만들 수 있습니다. 다음 프로젝트에서 Flow를 도입해보세요! 🚀 --- ## 참고 자료 📚 - [Kotlin Flow 공식 문서](https://kotlinlang.org/docs/flow.html) - [Android 개발자 가이드 - Flow](https://developer.android.com/kotlin/flow) - [Jetpack Compose State 가이드](https://developer.android.com/jetpack/compose/state)
목록으로 돌아가기

게시글 삭제

정말로 이 게시글을 삭제하시겠습니까?
삭제된 게시글은 복구할 수 없습니다.