Kotlin Coroutine에서 Flow vs Channel 선택 기준이 궁금합니다
안드로이드 개발 중인데, 데이터 스트림 처리를 위해 Kotlin Coroutine의 Flow와 Channel 중 어떤 걸 써야 할지 고민입니다.
현재는 네트워크 응답(Retrofit)을 Flow로 받아서 UI에 표시하고 있고, 사용자 액션(버튼 클릭)은 Channel로 처리하고 있습니다. 그런데 최근에 여러 데이터 소스를 합치는 combine 연산이 필요해지면서 Flow가 더 편리하다는 걸 느꼈습니다.
다만, Channel은 핫 스트림이라 메모리 관리가 까다롭다는 얘기를 들어서요. 실제 프로젝트에서 Flow와 Channel을 어떤 기준으로 구분해서 사용하시는지 경험 공유 부탁드립니다.
10 개의 답변
저도 비슷한 고민을 했었는데, 결국 Flow를 주로 쓰게 되더라고요. Channel은 특정 이벤트(버튼 클릭, Snackbar 표시 등)를 단발성으로 전달할 때만 사용하고 있어요. Flow는 combine, zip 같은 연산자 덕분에 데이터 합성이 훨씬 편리하고, 콜드 스트림이라 메모리 누수 걱정도 덜하거든요.
저는 반대로 Channel을 더 선호하는 편이에요. Flow는 collect가 호출될 때마다 새로 데이터를 생성하는데, 실시간 업데이트가 필요한 경우(채팅, 알림)에는 Channel이 더 직관적이더라고요. 다만 Channel은 close()를 꼭 해줘야 해서 lifecycle 관리가 중요해요.
combine 연산이 필요하다면 Flow가 정답에 가깝습니다. Channel로 여러 소스를 합치려면 직접 버퍼를 관리해야 해서 복잡도가 확 올라가거든요. 저는 이벤트성(버튼 클릭, 네비게이션)은 Channel, 데이터 스트림(네트워크, DB)은 Flow로 구분해서 쓰고 있어요.
혹시 SharedFlow나 StateFlow는 고려해보셨나요? 핫 스트림이 필요하지만 Channel보다 관리가 쉬운 대안이거든요. 저는 UI 상태는 StateFlow, 이벤트는 SharedFlow로 처리하고, 복잡한 데이터 파이프라인만 Flow로 쓰고 있습니다.
저도 처음에는 Channel로 시작했다가 Flow로 갈아탔어요. 이유는 테스트가 훨씬 쉽더라고요. Flow는 cold stream이라 가상 시간으로 제어하기 편하고, combine 같은 연산자로 비즈니스 로직을 선언적으로 표현할 수 있어서 코드 가독성도 좋아졌어요.
메모리 관리 측면에서 Channel은 정말 까다롭죠. 저는 한 번 Channel을 잘못 사용해서 OOM이 난 적도 있어요. 그래서 지금은 단순한 이벤트 전달 외에는 무조건 Flow를 쓰고, 필요하면 MutableStateFlow로 핫 스트림 흉내를 내고 있습니다.
네트워크 응답을 Flow로 받는다면, Retrofit 자체가 suspend 함수를 지원하니까 Flow로 처리하는 게 자연스럽죠. 사용자 액션은 Channel도 좋지만, 저는 최근에 SharedFlow로 대체했어요. replay 기능으로 버퍼링도 되고, collect 시점에 따라 데이터를 놓칠 걱정이 없더라고요.
저는 프로젝트 규모에 따라 선택을 달리해요. 작은 앱에서는 Channel로 충분하지만, 여러 모듈이 얽히는 큰 프로젝트에서는 Flow의 선언적 스트림 처리가 훨씬 유리합니다. 특히 DI와 함께 사용하면 테스트와 유지보수가 편해져요.
combine 연산이 필요하다면 Flow가 강력한 건 맞는데, Channel도 produce 빌더와 fan-out/fan-in 패턴으로 비슷한 걸 할 수 있어요. 다만 코드가 장황해져서 저는 Flow를 추천합니다. Channel은 정말 단순한 1:1 통신에만 한정적으로 쓰는 게 속편해요.
저도 비슷한 고민을 하다가 결국 Flow로 통일했어요. Channel은 버퍼 오버플로우 전략을 고민해야 하고, 취소 처리가 까다로운 반면, Flow는 lifecycle-aware하고 구조화된 동시성에 잘 맞더라고요. 단, hot stream이 필요한 경우에는 Channel이나 SharedFlow를 고려해보세요.