flow와 flowOf 차이? 모르면 밤샘 야근할 수 있다.

SSY·2023년 4월 23일
1

Flow

목록 보기
4/5
post-thumbnail

0. 시작하며

아래의 두 코드의 차이가 있다고 생각하시나요?

override fun searchResultModels(): Flow<List<SearchResultItem>>
    = flow { emit(fetchSearchResultModels()) }
    
override val searchResultModels: Flow<List<SearchResultItem>>
    = flowOf(fetchSearchResultModels())

flow나 flowOf나 모두 Flow스트림을 반환하는 함수입니다. 더군다나 flowOf의 내부를 보면 아래와 같은데요.

/**
 * Creates a flow that produces the given [value].
 */
public fun <T> flowOf(value: T): Flow<T> = flow {
    /*
     * Implementation note: this is just an "optimized" overload of flowOf(vararg)
     * which significantly reduces the footprint of widespread single-value flows.
     */
    emit(value)
}

코드를 보면 아시다시피, flow빌더와 차이가 아예 없습니다. 하지만 위 두 코드에선 엄연히 다른 결과가 발생했다는 것이 문제인 것이죠.

1. 생명주기가 onResume일때

우린 보통 onResume일때 소비자 컴포넌트가 데이터를 수집하도록 설정하곤 합니다. 따라서 콜드 플로우 컴포넌트인 flow는 onResume일때, 소비자가 데이터를 수집함과 동시에 데이터를 생산해내야만 합니다. 하지만 결과는?

override fun searchResultModels(): Flow<List<SearchResultItem>>
    = flow { emit(fetchSearchResultModels()) }

위의 코드일땐 생산자 컴포넌트가 동적으로 데이터를 잘 생산하는걸 볼 수 있습니다. 하지만

override val searchResultModels: Flow<List<SearchResultItem>>
    = flowOf(fetchSearchResultModels())

위의 코드일땐 생산자 컴포넌트가 데이터를 동적으로 변경사항을 반영하여 데이터를 생산하지 않습니다. 오직, 가장 처음 소비자 컴포넌트가 데이터를 수집했던것과 동일한 데이터만을 생산할 뿐이죠.

참고로 말씀드리자면 ViewModel은 아래의 형태로 콜드 플로우를 핫 플로우로 변형해주고 있습니다.

val uiState = searchResultRepository.localSearchResultModels
    .map { PagingData.from(it) }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(),
        initialValue = PagingData.empty()
    )

3. 원인은?

결론부터 말씀드리자면, flowOf는 지정된 초기값으로 정적인 데이터를 생산해내야할 때 사용하며, flow빌더는 동적으로 여러번 값을 생산해내야할 때 사용합니다.

[강조]
flowOf의 경우는 처음 설정된 초기값만 동일하게 생산한다. 동적으로 데이터가 변경된다 하더라도 다른 값이 방출되지 않는다.

아래의 추가 예시를 들어보겠습니다.

fun getListFlow(): Flow<List<Int>> {
    var myList = listOf(1, 2, 3)
    return flowOf(myList).onEach {
        delay(1000)
        myList = myList.map { it + 1 }
    }
}

fun main() {
    CoroutineScope(Dispatchers.IO).launch {
        repeat(10) {
            getListFlow().collectLatest {
                Log.i("flowOfTest", it.toString())
            }
            delay(1000)
        }
    }
}

위의 콜드 플로우를 반환하고 있는 함수의 데이터를 동적으로 여러번 수집하면 어떤 일이 일어날까요? 정답은 동일한 데이터만 수집된다는 점입니다. 위의 코드를 보시면 알겠지만, [1,2,3]이란 데이터를 처음 생산해준 후, onEach를 통하여 myList를 갱신해주고 있습니다. 그리고 repeat를 사용하여 1초의 딜레이 시간을 주며 반복을 10번 돌며 데이터를 계속 수집하고 있습니다.

그러면 상식적으로 아래와 같이 나와야 한다고 생각할 수 있습니다.

[1,2,3]
[1,2,3,4]
[1,2,3,4,5]
[1,2,3,4,5,6]
...

하지만 실상은 아래와 같이 나옵니다.

[1,2,3]
[1,2,3]
[1,2,3]
[1,2,3]
...

그 이유는 아까도 말씀드렸듯, flowOf의 경우는 가장 처음 생산하는 데이터가 정해지면 그 데이터만 생산하기 때문입니다. 따라서 만약 동적으로 데이터를 생산해야하는 로직인데 flowOf를 사용하여 데이터를 생산하게 하면 계속 동일한 데이터만 나올 수밖에 없으며 이는 잘못된 선택인 것이죠.

4. 마치며

어쩌면 놓치기 쉬운 flowOf와 flow빌더를 사용한 콜드 스트림을 생성하는 플로우 함수에 대해 알아보았습니다. flowOf내부 구현부를 보면 flow빌더를 사용할때와 쓰임새가 아예 똑같이 때문에 당연히 헷갈릴 수 있는 부분입니다. 그만큼 조심해야 하는 부분이고요. 이 글을 읽으신 분들은 좋은 지식 얻어가셨으면 좋겠습니다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글