요빈 2023. 5. 15. 17:01

Flow는 비동기적인 데이터 스트림이다. 따라서 코루틴의 suspend 함수와 다르게 여러 값을 순차적으로 보낼 수 있다.

예를 들어 Flow를 통해 데이터 베이스에서 실시간 업데이트를 수신할 수 있다.

데이터 스트림

  • 생산자(Consumer): 스트림에 추가되는 데이터 생선 -> 비동기적으로 데이터 생산 (ex. Repository)
  • 중개자(Intermediary, 선택사항): 스트림에 내보내는 각 값이나 스트림 수정
  • 소비자(Producer): 스트림 사용 (ex. UI)

Flow는 콜드 스트림이다. 즉, Flow가 collect 되기 전까지 실행되지 않는다는 것을 의미한다.

쉽게 말하면, Flow는 비동기로 동작하면서 여러 개의 값을 반환하는 함수를 만들 때 사용하는 콜드 스트림 방식의 코루틴 빌더이다.

Flow 만들기 in DataSource

flow { } 빌더를 통해 Flow를 만든다.

emit 함수를 사용해 새 값을 수동으로 데이터 스트림에 내보낸다.

flow 빌더는 코루틴 내에서 실행되지만, suspend로 표시되지 않음

fun getAllItem(): Flow<List<ItemEntity>>  = flow{
    val items = RetrofitApi.retrofitService.getAllItem()
    emit(items)
}
  • Flow는 순차적 -> suspend 함수를 호출하면 정지상태로 유지되기 때문에
  • 생산자가 다른 CoroutineContext에 값을 emit 할 수 없음. -> 그렇기 때문에 withContext로 다른 Context에서 emit을 호출하면 안 됨

Flow 빌더

위에서 사용한 flow { }는 가장 기본적인 Flow 빌더이다.

다른 Flow 빌더로는 .asFlow가 있다. -> 다른 Collection/Sequence들을 Flow로 변환한다.

ex. List/Map/Array -> Flow로 변환

(1..3).asFlow().collect { value -> println(value) } // 배열 -> Flow

중간 연산자 in Repository

중개자가 중간 연산자를 사용해 데이터 스트림을 수정할 수 있다.

중간 연산자 또한 suspend 함수가 아니기 때문에 새롭게 변환된 플로우를 즉시 반환한다.

중간 연산자 블록 내부에서는 suspend 함수를 사용할 수 있다.

중간 연산자 예시로는 map, filter, take, zip 등이 있다.

suspend fun performRequest(request: Int): String {
    delay(1000) // 1초 대기
    return "response $request" // 플로우 값 매핑 "request -> response $request"
}

fun main() = runBlocking<Unit> {
    (1..3).asFlow() // .asFlow() - 배열 -> Flow
        .map { request -> performRequest(request) } // 대상 플로우를 매핑해서 새로운 플로우로 반환
        .collect { response -> println(response) } // map으로 반환된 새 플로우에 대한 수집(collect)
}

변환 연산자

Flow 변환 연산자는 중간 연산자 같이 단순한 변환보다 복잡한 변환을 처리할 때 사용한다.

중간 연산자는 요소마다 1개의 변환이 가능하지만, 변환 연산자는 아래 예시처럼 emit()을 추가해 요소마다 여러 개의 변환이 가능하다. 가장 일반적으로 transfrom 연산자가 사용된다.

suspend fun performRequest(request: Int): String {
    delay(1000) // 1초 대기
    return "response $request" // 플로우 값 매핑 "request -> response $request"
}

fun main() = runBlocking<Unit> {
    (1..3).asFlow() // 배열 -> Flow 변환
    	.transform { request ->		// 변환 연산자(transform)
        	emit("Making request $request") 	// emit() - flow 방출
        	emit(performRequest(request)) 		// emit() - flow 방출
    	}.collect { response -> println(response) }	// flow 수집
}

Flow collect in ViewModel

데이터 스트림의 모든 값을 가져오려면 데이터 요청함수(ex. collect)를 사용한다.

데이터 요청함수는 suspend 함수이므로 코루틴 안에서 실행해야 한다.

즉, collect를 통해 Flow<~> 데이터를 받아오는 것이다.

getItem().collect{ 
   _alldata = it
}

Flow에게 데이터를 요청하는 함수 종류는 다음과 같다.

 

  • collect: Flow의 데이터를 순차적으로 가져와 하나씩 처리한다.
  • collectIndexed: 데이터의 value와 index까지 받아 처리한다.
  • collectLatest: 가장 최근의 데이터를 가져와 처리한다.

Flow는 collect가 호출될 때마다 새로 시작한다.

Flow 취소

Flow는 취소와 관련된 함수는 따로 제공하고 있지 않기 때문에 외부에 취소 가능한 무언가로 래핑해야 한다.

launch, async와 같은 코루틴이 적절한 예시이다.

 val job = CoroutineScope(Dispatchers.IO).launch {
    (1..3).asFlow().collect {
       Log.d("TAG", it.toString())
    }
}
job.cancel()

CoroutineContext 변경하기

기존 코루틴은 withContext를 통해 Context를 변경했지만 Flow에서는 FlowOn을 통해 변경할 것을 권장하고 있다.

(1..3).asFlow().flowOn(Dispatchers.IO).collect {
    Log.d("TAG", "$it")
}

여기까지의 Flow 실행 과정을 정리하면 다음과 같다.

 

  • flow { }  빌더 통해 Flow 생성
  • flow에서 emit 함수 통해 데이터 방출
  • flow에서 방출된 데이터들은 collect 함수를 이용해 받아옴(수집)
  • flow { } 블록 내부 코드는 collect가 호출되면 실행됨
  • flow { } 블록 내 emit 메서드는 collect 블록으로 값 전송

Reference

https://developer.android.com/kotlin/flow?hl=ko 

 

Android의 Kotlin 흐름  |  Android Developers

Android의 Kotlin 흐름 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 코루틴에서 흐름은 단일 값만 반환하는 정지 함수와 달리 여러 값을 순차적으로 내보낼

developer.android.com

https://yang-droid.tistory.com/56

 

[Kotlin Flow] 예제를 활용해 쉽게 Flow에 대한 개념 익히기 -1-

개념 Coroutine에서 Flow는 suspend func(정지 함수)와는 다르게 여러 값을 순차적으로 내보낼 수 있는 유형입니다. 이렇게 말하면 어떤말인지 잘 모를 수 있습니다.(저도 그랬거든요..ㅎㅎ) 간단한 예를

yang-droid.tistory.com

실습 참고👍 https://onlyfor-me-blog.tistory.com/478

 

[Android] Coroutine Flow란? MVVM + Flow + Retrofit 예제

최근 안드로이드 진영에서 비동기 처리에 자주 사용했던 라이브러리인 RxJava가 걷어내지고 그 자리를 코루틴의 flow라는 것이 대신한다고 들었다. 그래서 구글에서 Compose를 비롯해 여러 방식으로

onlyfor-me-blog.tistory.com

설명 참고👍https://velog.io/@ilil1/Coroutine-Flow-%EB%9E%80-1