Coroutine의 Flow는 데이터 스트림이며, 코루틴 상에서 리액티브 프로그래밍 지원 하기 위한 구성요소이다. 이를 이해하기 위해서는 먼저 리액티브(반응형) 프로그래밍이 무엇인지, 그리고 Flow가 리액티브 프로그래밍에 대응하여 어떠한 역할을 하는지를 알아야 한다.
리액티브 프로그래밍이란?
리액티브 프로그래밍이란 데이터가 변경 될 때 이벤트를 발생시켜서 데이터를 계속해서 전달하도록 하는 프로그래밍 방식을 뜻한다. 이는 기존의 명령형 프로그래밍과 대응되는 개념인데 이에 대한 내용은 [RxJava] 반응형 프로그래밍 이해하기 글에 잘 설명되어 있다. 만약 리액티브 프로그래밍에 대해 잘 모른다면 위 글을 읽고 오자.
기존 명령형 프로그래밍에서는 데이터의 소비자는 데이터를 요청한 후 받은 결과값을 일회성으로 수신한다. 하지만, 이러한 방식은 데이터가 필요할 때마다 결과값을 매번 요청해야한다는 점에서 매우 비효율적이다. 리액티브 프로그래밍에서는 데이터를 발행하는 발행자가 있고 데이터의 소비자는 데이터의 발행자에 구독 요청을 한다. 그러면 데이터의 발행자는 새로운 데이터가 들어오면 데이터의 소비자에게 지속적으로 발행한다.
즉, 리액티브 프로그래밍에는 하나의 데이터를 발행하는 발행자가 있고 해당 발행자는 데이터의 소비자에게 지속적으로 데이터를 전달하는 역할을 한다. 우리는 이것을 데이터 스트림이라고 한다.
Coroutine Flow를 이용한 리액티브 프로그래밍
Coroutine Flow는 코루틴 상에서 리액티브 프로그래밍을 지원하기 위해 만들어진 구현체이다. 코루틴에서 데이터 스트림을 구현하기 위해서는 Flow를 사용해야 한다.
먼저 데이터 스트림의 구성요소를 살펴보자. 데이터 스트림은 아래 세가지로 구성되며, 이 세가지가 flow의 핵심 구성요소이다.
- Producer(생산자)
- Intermediary(중간 연산자)
- Consumer(소비자)
각 생산자, 중간 연산자, 소비자가 어떤 역할을 하는지 미세먼지 앱의 Flow를 디자인 하는 과정을 통해 알아보자.
Producer(생산자)
먼저 생산자는 데이터를 발행하는 역할을 한다. Flow에서의 Producer는 flow{ } 블록 내부에서의 emit()을 통해 데이터를 생성한다.
안드로이드 상에서 생산자가 가져오는 데이터의 대표적인 DataSource는 두가지이다.
- 첫째는 서버의 데이터로 보통 REST API(Remote Source)를 이용해 가져오는 데이터.
- 둘째는 휴대폰 상의 DB(Local DataSource)에서 가져오는 데이터.
미세먼지 앱을 만든다고 했을 때 아래와 같은 과정을 거친다.
1. 먼저 flow { } 블록을 선언한다.
2. 미세먼지 데이터을 서버로부터 받아온다.(Remote DataSource)
3. Producer가 데이터를 생성한다.(emit)
4. 2~3과정을 60초마다 반복하여 데이터를 계속해서 생성한다.
class DustRemoteDataSource(
private val dustApi: DustApi
) {
fun getDustInfoFlow() : Flow<List<DustInfo>> = flow { // 1.Flow 블록 선언
while (true) {
val dustInfos = dustApi.fetchLastedDustInfo() //2. 데이터 받아오기
emit(dustInfos) //3. Producer가 데이터 발행
delay(INTERVAL_REFRESH) //4. 60초마다 반복
}
}
companion object {
private const val INTERVAL_REFRESH: Long = 60000
}
}
Intermediary(중간 연산자)
생산자가 데이터를 생성했으면 중간 연산자는 생성된 데이터를 수정한다. 여기서의 중간 연산자는 생산자가 생성한 데이터를 수정한다. 예를 들어 생성자는 A라는 객체로 이루어진 데이터를 발행했는데 우리는 B라는 객체 데이터가 필요한 경우 Flow에서 지원하는 중간 연산자를 이용해 A객체를 B객체로 바꿀 수 있다.
대표적인 중간 연산자는 map(데이터 변형), filter(데이터 필터링), onEach(모든 데이터마다 연산 수행) 등의 중간 연산자가 있다.
다시 미세먼지 앱으로 돌아가자. View에 전달할 때는 모든 처리가 완료된 데이터가 전달되는 것이 좋다. 우리는 모든 DustInfo가 필요하지 않다. 지금 우리가 있는 지역(locale)의 미세먼지 데이터만 필요하다. 따라서 우리 지역 데이터를 제외한 모든 데이터를 filter하기 위해 기존 데이터 중 필요한 데이터로 변형하여 방출하는 map을 사용한 후 들어오는 데이터를 filtering 하여 특정 지역 locale의 데이터만을 방출한다.
class DustRepository(
private val dustRemoteDataSource: DustRemoteDataSource,
) {
fun getDustsInfoOfViewItem(locale : Locale) =
dustRemoteDataSource.getDustInfoFlow().map{ it.filter{ this.locale == locale } }
}
Consumer(소비자)
중간 연산자가 생산자가 생성한 데이터를 변환하여 소비자로 데이터를 전달한다. Flow에서는 collect를 이용해 전달된 데이터를 소비할 수 있다.
안드로이드 상에서 데이터의 소비자는 보통 UI 구성요소이다. UI는 데이터를 소비하여 데이터에 맞게 UI를 그려낸다.
우리는 받은 미세먼지 데이터를 이용해 ViewModel에서 필요한 처리를 하여 View에서 사용하면 된다.
class DustViewModel(
private val dustRepository: DustRepository
) : ViewModel() {
fun collectDustInfoOf(locale: Locale) =
viewModelScope.launch {
dustRepository.getDustInfoOf(locale).collect { dustInfos ->
//
}
}
}
}
이 장에서는 Flow에서의 각 구성요소가 어떤 역할을 하는지 기초적인 부분을 살펴보았다. 다음 장부터는 Flow의 다양한 기능을 사용해볼 것이다.
Kotlin Coroutines 공식 기술 문서 번역이 GitHub 오픈소스로 배포되었습니다. Starganizer가 되어 오픈소스를 지지해주세요.