프로그래밍 놀이터/안드로이드, Java

[android] LiveData 를 StateFlow or SharedFlow 로 교체하기 (StateFlow 와 SharedFlow 도 이해해보기)

돼지왕 왕돼지 2021. 5. 9. 15:51
반응형

 

-

Kotlin Coroutine 은 최근 SharedFlow 와 StateFlow 라는 2가지 type 의 Flow 를 소개했다.

 

 

-

LiveData 를 Flow 형태로 바꾸기 전에 LiveData 를 Flow 형태로 바꾸어야 하는 이유를 알아보자.

1. LiveData 는 UI 에 너무 bind 되어 있다.

2. LiveData 는 Android platform 에 너무 bind 되어 있다.

 

위의 이유로 LiveData 는 Presentation Layer 에서는 사용하기 괜찮을 수 있지만, Domain Layer 로 내려가기에는 ugly 해진다.

Domain Layer 는 platform-independent 해야 하기 때문이다.

 

 

-

LiveData 를 pure Flow 로 교체할 수는 없다.

 

1. Flow 는 .value 가 없다.

2. Flow 는 선언적(Cold)이다. : flow builder 는 flow 가 무엇인지만을 정의하고, collect 가 될 때 생성이 된다. 그래서 collector 가 붙을 때마다 새로운 Flow 가 생성이 되고, 매번 Flow 의 로직이 수행된다. 예를 들어 DB 에 접근하는 작업을 한다면 collect 할 때마다 db 에 접근하는 것이다.

 

 

-

위에서 언급한 2번의 문제를 해결하는 것이 SharedFlow 이고, 1번의 문제까지 해결하는 것이 StateFlow 이다.

 

 

-

fun <T> Flow<T>.shareIn(
    scope:CoroutineScope,
    started:SharedStarted,
    reply:Int = 0
    ) : SharedFlow<T> (source)

Flow 의 extension fun "shareIn" 을 통해 SharedFlow 로 변환시킬 수 있다.

 

scope 은 Flow 를 만들 때 사용하는 scope 을 이야기하며, Data Source 쪽에는 보통 application process 의 LifecycleScope 인 LifecycleCoroutineScope 가 전달된다.

 

started 에는 SharingStarted.WhileSubscribed() 를 사용할 수 있는데, 이 경우 subscriber 가 0에서 1이 되면 Flow 가 만들어지고 share 를 시작하고, 1에서 0이 되면 share 를 멈춘다.

이는 LiveData 의 onActive, onInActive 와 비슷한 로직이라고 보면 된다.

다른 함수들을 통해 eagerly(즉각적으로 만들고 제거하지 않음), lazily(처음 collect 할 때 만들고, 제거되지 않음) 등의 option 을 선택할 수도 있다.'

 

reply 는 1을 사용할 수 있는데, 이 때는 새로운 subscriber 가 등록될 때 바로 최신의 값을 내뱉도록 한다.

 

 

-

Presentation Layer 에서 LiveData 를 계속 사용하고 싶다면, Flow<T>.asLiveData() 를 사용하면 된다.

 

 

-

launchWhenStarted { } 에서 Flow collect 를 한다면, onStart() 에서 collect 가 불리지만, onStop 에서 unsubscribe 가 자동으로 되지는 않는다.

그래서 onStop 에서 unsubscribe 를 해줘야 하는데 이는 매우 귀찮은 일이다.

그래서 coroutine 쪽에서는 observeIn(LifecycleOwner) 라는 extension 을 제공해서 그 안에서 알아서 이 작업을 해준다.

 

아래와 같이 쓰면 된다.

// Activity
viewModel.locations
    .onEach { /* new location received */ }
    .observeIn(this)

위와 같이 사용하면 LiveData 의 LifecycleOwner 의존적인 동작과 동일하게 동작한다.

 

observeIn 으로 인해 LifecycleOwner 의 Lifecycle 이 CREATED 가 되면  coroutine 이 destroy 되고, STARTED 가 되면 recreate 된다.

(Lifecycle.State 에는 INITIALIZED, CREATED, STARTED, RESUMED, DESTROYED 만 있다. PAUSED 와 STOPPED 가 없다. 그래서 onPause() 시 STARTED 로 상태로 돌아가고, onStop() 시 CREATED state 로 돌아가는 구조이다. 그래서 CRETED 가 되면 destory 되고, STARTED 면 recreate 되는 로직인 것이다.)

 

 

-

flowOn(Dispatchers.IO) 등을 통해 쉽게 subscribe 하는 thread 를 변경할 수 있다.

 

 

-

fun <T> Flow<T>.stateIn(
    scope:CoroutineScope,
    started:SharingStarted,
    initialValue:T
    ):StateFlow<T>(source)

StateFlow 는 stateIn() extension fun 을 통해 만들 수 있다.

StateFlow 는 확장성은 줄어들고, 특정 목적에 부합하도록 만들어진 SharedFlow 라고 볼 수 있다. (1개 이상의 cache control 등을 할 수 없고, 동일 값이 들어오면 무시한다.(conflate))

StateFlow 는 SharedFlow 의 replay 값이 1로 고정된 것과 같다.

그래서 subscribe 를 하는 순간 latest value 가 무조건 전달된다.

그리고 value 를 접근할 수 있다.

 

 

-

참고자료 : https://proandroiddev.com/should-we-choose-kotlins-stateflow-or-sharedflow-to-substitute-for-android-s-livedata-2d69f2bd6fa5

 

 

 

반응형