데이터 스트림이며, 코루틴 상에서 리액티브 프로그래밍 지원을 하기 위한 구성요소이다.
데이터가 변경 될 때 이벤트를 발생시켜서 데이터를 계속해서 전달하도록 하는 프로그래밍 방식
데이터가 필요할 때마다 결과값을 매번 요청해야한다는 점에서 매우 비효율적이다.
Coroutine Flow는 코루틴 상에서 리액티브 프로그래밍을 지원하기 위해 만들어진 구현체이다. 코루틴에서 데이터 스트림을 구현하기 위해서는 Flow를 사용해야 한다.
데이터 스트림은 아래 세 가지로 구성된다.
역할: 데이터 발행
flow{} 블록 내부에서의 emit()을 통해 데이터를 생성한다.
역할: 생성된 데이터 수정
map, filter, onEach 등의 중간 연산자가 있다.
역할: collect를 이용해 데이터 수신
LiveData를 Flow로 대체할 수는 없다.
1. Flow 는 상태가 없음 (.value 와 같은 형태로 값을 얻을 수 없음).
2. Flow 는 선언적임 (콜드 - 연속해서 계속 들어오는 데이터를 처리할 수 없음): flow builder 는 Flow 가 무엇인지 설명하고 있는데, collect 되었을 때만 "생성되고 값을 반환"(materialized)합니다. 하지만, 새로운 Flow가 성공적으로 실행되면 (materialized) 값을 받는 쪽 (collector) 한 개 마다 하나씩 데이터를 호출합니다. 그 말은 업스트림 (비싼 네트워크 호출 등) 데이터베이스 접근 등의 요청이 collector 마다 한번 씩, 결국 모두 합치면 여러 번 호출된다는 뜻입니다.
3. Flow 스스로는 안드로이드 라이브사이클에 대해 알지 못함. 안드로이드 라이프사이클에 따라 중지되거나 재개되지 못합니다.
(1) - 현재 상태에 접근하기 (2) - 여러 개의 (N >=1) collector가 있더라도 리소스 요청은 한 번만 하기, 그리고 collector가 없다면 리소스를 요청하지 않기를 어떻게 할 수 있을까?
stateIn() 은 replay 값을 설정 할 수 없습니다. StateFlow는 SharedFlow 에서 replay 값이 1로 고정된 것입니다. 이 말은 새로운 subscriber는 즉시 현재 상태 값을 받게 된다는 의미입니다.
stateIn()은 초기 값이 필요합니다. 이 말은 만약 당신이 처음에 설정해 줄 값이 없다면, StateFlow<T> 에서 T 에 들어갈 타입을 null 일 수 있는 타입으로 설정하거나, sealed class를 설정해서 "비어있는 초기 값"을 정의해 줘야 한다는 의미입니다.
SharedFlow 를 사용하기에 적합한 곳은 StateFlow 에서 추가 버퍼가 필요하거나, 여러 개의 최근 값을 반환해야 하거나, 초기값을 무시해야 하는 곳입니다.
하지만, SharedFlow를 선택할 때 명백하게 타협해야 할 부분이 하나 있는데, 그것은 바로 StateFlow<\T>.value를 잃게 된다는 것입니다.
데이터 홀더(저장소) 역할을 하면서 Flow의 데이터 스트림 역할까지 한다.
Flow를 UI에서 사용되기 위해 StateFlow로 변환되어야 한다.
그리고 UI에서는 이 StateFlow를 구독하여 항상 최신 데이터를 발행받는다.
StateFlow가 항상 Flow를 구독하고 있으면 메모리 누수가 생기므로 이 StateFlow가 살아있어야 하는 CoroutineScope를 명시할 수 있어야 한다. => 이를 stateln 함수를 통해 할 수 있다.
StateFlowsms Hot Stream이다. 마지막 홀딩하고 있는 데이터를 구독하는 구독자에게 전달할 뿐, 구독자가 구독할 때 발행을 위한 로직을 Trigger 하지는 않는다.
public fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> {
val config = configureSharing(1)
val state = MutableStateFlow(initialValue)
val job = scope.launchSharing(config.context, config.upstream, state, started, initialValue)
return ReadonlyStateFlow(state, job)
}
stateln은 세 가지 변수를 받는다.
flow.stateIn(
initialValue = 0,
started = SharingStarted.WhileSubscribed(5000),
scope = viewModelScope
)
초기 저장값은 0이고, 구독 후 5초 후에 처음 발행 받고, ViewModel의 생명주기만큼만 구독받는 행동을 하는 StateFlow가 만들어진다.
Android 앱에서 Kotlin flows는 일반적으로 UI 레이어에서 수집되어 화면에 데이터를 업데이트합니다.
flow를 수집하여 필요 이상의 작업을 수행하지 않아야하며 리소스(CPU와 메모리 모두)를 낭비하거나 View가 백그라운드로 이동할 때 데이터가 누출되지 않도록 해야합니다.
https://jslee-tech.tistory.com/52?category=1065471
https://hongbeomi.medium.com/%EB%B2%88%EC%97%AD-livedata%EC%97%90%EC%84%9C-kotlin%EC%9D%98flow%EB%A1%9C-migrating-5b183c363f60
이 흐름이 수집되기 시작하기 전에 지정된 작업을 호출하는 흐름을 반환합니다.
fun <T> Flow<T>.onStart(action: suspend FlowCollector<T>.() -> Unit): Flow<T>
flowOf("a", "b", "c")
.onStart { emit("Begin") }
.collect { println(it) } // prints Begin, a, b, c
flowOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filterNot { it % 3 == 0 }
.collect { println(it) }
1
2
4
5
7
8
10
직전에 발행된 값과 중복된 값만 필터링하여 발행하지 않는다.