Flow

sumi Yoo·2022년 11월 17일
0

Flow

데이터 스트림이며, 코루틴 상에서 리액티브 프로그래밍 지원을 하기 위한 구성요소이다.

Cold Stream

  • 하나의 소비자에게 값을 보낸다.
  • 생성된 이후에 누군가 소비하기 시작하면 데이터를 발행한다.
  • 예) 상태가 변하지 않는 값을 읽을 때(DB를 읽거나 URL을 통해 서버 값을 읽는 경우)

Hot Stream

  • 하나 이상의 소비자(Consumer)에게 값을 보낸다
  • 데이터 발행이 시작된 이후 부터 모든 소비자에게 같은 데이터를 발행하고 구독자가 없는 경우에도 데이터를 발행한다
  • 예) 상태가 변하는 값을 읽을 때(라디오에서 방출하는 방송을 청취할 때)

리액티브 프로그래밍?

데이터가 변경 될 때 이벤트를 발생시켜서 데이터를 계속해서 전달하도록 하는 프로그래밍 방식

기존 명령형 프로그래밍의 단점

데이터가 필요할 때마다 결과값을 매번 요청해야한다는 점에서 매우 비효율적이다.

Coroutine Flow를 이용한 리액티브 프로그래밍

Coroutine Flow는 코루틴 상에서 리액티브 프로그래밍을 지원하기 위해 만들어진 구현체이다. 코루틴에서 데이터 스트림을 구현하기 위해서는 Flow를 사용해야 한다.

데이터 스트림은 아래 세 가지로 구성된다.

  • Producer(생산자)
  • Intermediary(중간 연산자)
  • Consumer(소비자)

Producer(생산자)

역할: 데이터 발행
flow{} 블록 내부에서의 emit()을 통해 데이터를 생성한다.

Intermediary(중간 연산자)

역할: 생성된 데이터 수정
map, filter, onEach 등의 중간 연산자가 있다.

Consumer(소비자)

역할: collect를 이용해 데이터 수신

Flow의 한계

LiveData를 Flow로 대체할 수는 없다.
1. Flow 는 상태가 없음 (.value 와 같은 형태로 값을 얻을 수 없음).
2. Flow 는 선언적임 (콜드 - 연속해서 계속 들어오는 데이터를 처리할 수 없음): flow builder 는 Flow 가 무엇인지 설명하고 있는데, collect 되었을 때만 "생성되고 값을 반환"(materialized)합니다. 하지만, 새로운 Flow가 성공적으로 실행되면 (materialized) 값을 받는 쪽 (collector) 한 개 마다 하나씩 데이터를 호출합니다. 그 말은 업스트림 (비싼 네트워크 호출 등) 데이터베이스 접근 등의 요청이 collector 마다 한번 씩, 결국 모두 합치면 여러 번 호출된다는 뜻입니다.
3. Flow 스스로는 안드로이드 라이브사이클에 대해 알지 못함. 안드로이드 라이프사이클에 따라 중지되거나 재개되지 못합니다.

(1) - 현재 상태에 접근하기 (2) - 여러 개의 (N >=1) collector가 있더라도 리소스 요청은 한 번만 하기, 그리고 collector가 없다면 리소스를 요청하지 않기를 어떻게 할 수 있을까?

sharedIn() 와 stateIn()

  1. stateIn() 은 replay 값을 설정 할 수 없습니다. StateFlow는 SharedFlow 에서 replay 값이 1로 고정된 것입니다. 이 말은 새로운 subscriber는 즉시 현재 상태 값을 받게 된다는 의미입니다.

  2. stateIn()은 초기 값이 필요합니다. 이 말은 만약 당신이 처음에 설정해 줄 값이 없다면, StateFlow<T> 에서 T 에 들어갈 타입을 null 일 수 있는 타입으로 설정하거나, sealed class를 설정해서 "비어있는 초기 값"을 정의해 줘야 한다는 의미입니다.

SharedFlow 를 사용하기에 적합한 곳은 StateFlow 에서 추가 버퍼가 필요하거나, 여러 개의 최근 값을 반환해야 하거나, 초기값을 무시해야 하는 곳입니다.

하지만, SharedFlow를 선택할 때 명백하게 타협해야 할 부분이 하나 있는데, 그것은 바로 StateFlow<\T>.value를 잃게 된다는 것입니다.

StateFlow

데이터 홀더(저장소) 역할을 하면서 Flow의 데이터 스트림 역할까지 한다.
Flow를 UI에서 사용되기 위해 StateFlow로 변환되어야 한다.
그리고 UI에서는 이 StateFlow를 구독하여 항상 최신 데이터를 발행받는다.

StateFlow가 항상 Flow를 구독하고 있으면 메모리 누수가 생기므로 이 StateFlow가 살아있어야 하는 CoroutineScope를 명시할 수 있어야 한다. => 이를 stateln 함수를 통해 할 수 있다.

StateFlowsms Hot Stream이다. 마지막 홀딩하고 있는 데이터를 구독하는 구독자에게 전달할 뿐, 구독자가 구독할 때 발행을 위한 로직을 Trigger 하지는 않는다.

stateln 사용하여 Flow를 StateFlow로 변환하기

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은 세 가지 변수를 받는다.

  • scope: StateFlow가 Flow로부터 데이터를 구독받을 CoroutineScope를 명시한다.
  • started: Flow로부터 언제까지 구독을 할지 명시할 수 있다.
  • initialValue: StateFlow에 저장될 초기값을 설정한다.
flow.stateIn(
        initialValue = 0,
        started = SharingStarted.WhileSubscribed(5000),
        scope = viewModelScope
    )

초기 저장값은 0이고, 구독 후 5초 후에 처음 발행 받고, ViewModel의 생명주기만큼만 구독받는 행동을 하는 StateFlow가 만들어진다.

https://stanleykou.tistory.com/m/entry/httpsproandroiddevcomshould-we-choose-kotlins-stateflow-or-sharedflow-to-substitute-for-android-s-livedata-2d69f2bd6fa5

Android UI에서 flow를 안전하게 수집하기

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

Flow.onStart()

이 흐름이 수집되기 시작하기 전에 지정된 작업을 호출하는 흐름을 반환합니다.

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

FilterNot

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

distinctUntilChanged

직전에 발행된 값과 중복된 값만 필터링하여 발행하지 않는다.

https://velog.io/@mraz3068/Android-Coroutine-Flow-debounce-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B2%80%EC%83%89-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84

0개의 댓글