Flow 기본 개념
1.필요성
우리가 개발을 하면서 네트워크 요청 또는 데이터베이스 실시간 업데이트와 같이 비동기적으로 진행해야할 작업을 수행할 때가 많다.
이를 위해 우리는 종종 Kotlin의 Coroutine을 활용, suspend 함수를 통해 작업 결과를 비동기적으로 받아올 수 있다.
그런데 우리가 일련의 여러 값을 비동기적으로 반환하려면 어떻게 해야할까?
suspend fun getAllListValue() : List<Int> {...} // <- List<Int>라는 값을 반환하면 끝. (단일)
suspend 함수의 경우 이처럼 단일 값 밖에 반환하지 못하기 때문에 사용하기 어렵다.
이 때 Flow를 이용하여 여러 값을 순차적으로 비동기적으로 반환할 수 있다.
2.특징
Flow는 Coroutine을 기반으로 빌드하여 여러 값을 비동기적으로 제공할 수 있다.
즉, Flow는 '비동기식'으로 계산할 수 있는 "데이터 스트림"의 개념이다.
여기서 "데이터 스트림" 이란?
- 생산자 : 일반적으로 Android에서 Repositery의 layer 계층이 생산자의 역할
- 소비자 : UI layer 계층은 소비자의 역할
- 중개자 : 생산자와 소비자 사이의 layer는 중개자 역할
Flow는 값의 Sequence를 생성하는 일종의 Iterator와 유사하지만, Suspend 함수를 사용하여 값을 비동기적으로 생성하고 사용한다.
따라서 Main Thread를 차지하지 않고, 다음 값 (여러 값)을 생성할 네트워크 요청 업무를 안전하게 수행한다.
Flow 생성
Flow를 만드려면 Flow Builder를 사용해야 한다.
Flow Builder는 emit() 함수를 사용하여 '새로운 값'을 데이터 스트림에 내보낼 수 있는 flow를 만든다.
다음은 고정된 간격(5초)으로 최신 뉴스 정보를 자동으로 가져오는 예시이다.
suspend 함수는 연속적으로 값을 가져올 수 없다. 따라서 Flow를 만들어 연속적인 값을 반환하도록 한다.
class NewsRemoteDataSource(
private val newsApi : NewsApi,
private val refreshIntervalMs:Long = 5000
) {
val latestNews : Flow<List<ArticleHeadline>> = flow {
while(true) {
val latestNews = newsApi.fetchLatestNews()
emit(latestNews)
delay(refreshIntervalMs)
}
}
}
interface NewsApi {
suspend fun fetchLatestNews(): List<ArticleHeadline>
}
해당 dataSource class 는 데이터 스트림의 생산자가 된다.
- flow{} : Flow Builder() -> Coroutine Context (?)
- while(true) 문 내에 지속적으로 요청할 네트워크 작업을 만든다. val latestNews = newsApi.fetchLatestNews()
- emit(latestNews) : latestNews 값을 데이터 스트림으로 내보낸다. flow 생성.
- delay(refreshIntervalMs) : 5초간 반복.
flow{}는 코루틴 내에서 실행된다. 따라서 동일한 비동기 API 이점을 활용할 수 다. 하지만 몇가지 제안사항이 존재.
- flow는 순차적이다. 생산자가 Coroutine에 있으므로 suspend 함수가 호출되면 생산자는 suspend 함수가 반환할 때 까지 정지 상태에 머무른다.
- flow{} 에서는 생산자가 다른 Coroutine Context의 값을 emit() 할 수 없다.(=데이터 스트림으로 값을 내보낼 수 없다.)
즉, 새로운 Coroutine을 만들거나 withContext() block을 활용하여 다른 CorotineContext에서 emit을 호출하지 않는다.
*기본적으로 Flow 빌더의 생산자는 수집하는 측의 CoroutineContext에서 실행된다.
Stream 수정
중개자(Intermediatory)는 '중간 연산자'를 사용하여 값을 소비하지(수집하지) 않고 데이터 스트림을 수정할 수 있다.
'중간 연산자'는 데이터 스트림에 적용되는 경우, 값이 향후에 사용될 때 까지 (수집될 때 까지) 실행 되지 않는 작업에 일종의 Chain을 설정하는 함수이다.
다음은 repositery layer에서 중간 연산자 'map'을 활용하여 data가 view에 표시되기 전 가공한다.
class NewsRepositery(
private val newsRemoteDataSource : NewsRemoteDataSource,
private val userData : UserData
) {
val favoriteLatestNews : Flow<List<ArticleHeadline>> =
newsRemoteDataSource.latestNews
.map{
news-> news.filter{userData.isFavoriteTopic(it)}
}
.onEach{
news->savedInCache(news)
}
}
중간연산자는 시차를 두고 차례로 적용할 수가 있다. 따라서 항목을 흐름에 내보낼 때 느리게 실행되는 작업 체인을 구성한다.
하지만 Flow에 중간 연산자를 설정하는 것이 스트림을 수집하는 것이 아니다. 중간 연산자는 flow를 수정하는 역할일 뿐.
Stream 수집
'터미널 연산자'를 사용하여 '값 수신 대기'를 시작하는 flow를 트리거한다.
스트림으로 내보내진(=emit())의 모든 값을 가져오려면 collect() 터미널 연산자를 사용한다.
collect()는 suspend 함수이므로 Coroutine 내에서 사용해야 한다. 그리고 collect의 매개변수로 람다를 사용한다.
ex. (value:Int) -> Unit
collect()는 suspend 함수이기 때문에 해당 coroutine은 flow가 종료될 때 까지 정지 될 수 있다.
class LatestNewsViewModel(
private val newsRepositery : NewsRepositery
) : ViewModel() {
init {
viewModel.scope{
newsRepositery.favoriteLatestNews.collect {
favoriteNews -> //update view.
}
}
}
}
해당 예시에서는 flow를 수집하면 고정된 간격으로 최신 뉴스를 새로 고침하며 네트워크 요쳥 결과를 내보내는 생산자가 trigger된다.
생산자는 while(true)루프로 항상 활성 상태가 유지되고 있다.
이는 viewModel 삭제되어 viewModelScope가 취소되면 데이터 스트림 또한 종료된다.
다음과 같은 이유로 스트림 '수집'이 종료될 수 있다.
- 코루틴이 취소된 경우, 생산자도 종료된다.
- 생산자가 '항목 방출'을 완료한 경우, 데이터 스트림이 종료되고, collect를 호출한 코루틴이 실행을 다시 시작한다.
다른 '중간 연산자'를 통해 지정되지 않은 경우, flow의 상태는 cold 및 delay 상태.
cold 및 delay 상태는 flow에서 '터미널 연산자'가 호출될 때 마다 생산자 코드가 다시 실행.
만약 'Flow 수집기'가 여러개 있다면 dataSource는 서로 다른 간격으로 최신 뉴스를 여러번 가져올 것.
따라서 여러 소비자가 동시에 수집할 때 Flow를 최적화 및 공유하고 싶다면 shardIn 연산자를 사용할 수 있다.
참고
https://developer.android.com/kotlin/flow
Android의 Kotlin 흐름 | Android 개발자 | Android Developers
Android의 Kotlin 흐름 코루틴에서 흐름은 단일 값만 반환하는 정지 함수와 달리 여러 값을 순차적으로 내보낼 수 있는 유형입니다. 예를 들면 흐름을 사용하여 데이터베이스에서 실시간 업데이트
developer.android.com
'안드로이드 > 개념' 카테고리의 다른 글
[Android/Kotlin] 브로드캐스트 리시버(Broadcast Receiver) (1) 개념 , 리시버 생성, 리시버 선언 (0) | 2022.11.01 |
---|---|
[Android/Kotlin] Service (1) Service 개념, 종류, 수명 주기 (0) | 2022.10.17 |
[Android/Kotlin] Context (1) 일단 간단하게 이해해보기 (0) | 2022.06.23 |
[Android/Kotlin] Flow 흐름 (2) 예외 파악, 다른 CoroutinContext 실행, Callback 기반 API를 Flow로 변환하기. (0) | 2022.06.09 |