안녕하세요 남갯입니다.
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ko
StateFlow
StateFlow는 현재상태와 새로운 상태를 업데이트 수집하는 관찰 가능한 상태 홀더 flow입니다.
상태를 업데이트를 하기 위해서는 MutableStateFlow클래스의 value 속성에 새 값을 할당합니다.
따라서 뭔가 ui상태와같이 변경 가능상태를 유지해야하는 클래스에 적합합니다.
즉 뷰가 UI 상태 업데이트를 수신 대기하고 구성변경에 화면상태가 지속되도록 하기위해 StateFlow를 노출할 수 있습니다.
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// Backing property to avoid state updates from other classes
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
// The UI collects from this StateFlow to get its state updates
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
// Update View with the latest favorite news
// Writes to the value property of MutableStateFlow,
// adding a new element to the flow and updating all
// of its collectors
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
여기서 MutableStateFlow 업데이트를 담당하는 클래스가 생산자이고, StateFlow에 수집되는 모든 클래스가 소비자입니다. flow 빌더와는 다르게 Hot Flow 입니다. 흐름에서 수집해도 생산자의 코드가 트리거되지 않습니다.
항상 활성상태고 참조가 없을경우 GC가 동작하게 할 수 있습니다.
소비자측에서 흐름을 수집하게 되면 마지막 상태와 후속 상태가 수신됩니다.
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// Note that this happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
latestNewsViewModel.uiState.collect { uiState ->
// New value received
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
LiveData와 같이 관찰 가능한 클래스에서 동작을 수신할 수 있습니다.
단 위와같이 launch 나 launchIn 과 같이 직접 흐름을 수집하지 말고 onStart 이후부터 동작하도록 변경해야한다.
기존 Flow를 StateFlow로 변환하기 위해서는 stateIn 중간 연산자를 사용해야한다.
StateFlow와 LiveData, Flow
StateFlow와 LiveData는 비슷한점이 있는데, 둘 다 관찰 가능한 데이터 홀더 클래스이고, 아키텍처 내부에 사용할 때
비슷한 패턴을 따르게 됩니다.
1. StateFlow는 무조건 초기생성자 전달을 해야하지만, LiveData의 경우는 전달하지 않습니다.
2. STOPPED 상태에 LiveData는 자동으로 Observe를 자동으로 취소하는 반면 StateFlow는 수집하는 흐름이 있는경우 자동으로 수집을 중지하지 않아서, Lifecycle.repeatOnLifecycle 블록에서 수집을 진행해야합니다.
shareIn을 사용해서 Cold Flow -> Hot Flow
stateFlow는 hot Flow로 흐름이 수집되는동안 참조가 있는경우 GC에 동작하지 않습니다. shareIn연산자를 사용해서
ColdFlow -> HotFlow로 전환이 가능합니다.
이전장에서 설명햇던 callbackFlow를 사용해서 가져온 데이터를 shareIn을 통해 공유가 가능합니다.
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
shareIn에 사용할 externalScope, 반복횟수 , 시작 정책
externalScope가 활성상태이고, 수집기가 있는동안에서는 활성상태로 유지가 됩니다.
SharingStarted.WhileSubscribed() 시작 정책은 활성 구독자가 있는 동안 업스트림 생산자를 활성 상태로 유지합니다.
SharingStarted.Eagerly 를 사용해 즉시시작하거나, SharingStarted.Lazily를 사용해서 구독자가 표시 된 후 흐름을 영구적으로 활성상태로 유지할 수 있습니다.
SharedFlow
위에서 사용한 shareIn 함수는 수집하고 있는 소비자에게 SharedFlow를 반환하게 됩니다. (HotFlow)
shareIn을 사용하지 않고 SharedFlow를 만들 수 있습니다. SharedFlow를 사용하면 콘텐츠가 주기적으로 새로고침 되도록 앱의 나머지 부분에 틱을 전송할 수 있습니다.
// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
private val externalScope: CoroutineScope,
private val tickIntervalMs: Long = 5000
) {
// Backing property to avoid flow emissions from other classes
private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
val tickFlow: SharedFlow<Event<String>> = _tickFlow
init {
externalScope.launch {
while(true) {
_tickFlow.emit(Unit)
delay(tickIntervalMs)
}
}
}
}
class NewsRepository(
...,
private val tickHandler: TickHandler,
private val externalScope: CoroutineScope
) {
init {
externalScope.launch {
// Listen for tick updates
tickHandler.tickFlow.collect {
refreshLatestNews()
}
}
}
suspend fun refreshLatestNews() { ... }
...
}
collect가 불리는 순간부터 5초마다 refreshLatestNews를 업데이트 하게 됩니다.
replay를 통해 이전에 보낸 여러개의 값을 구독자로 보낼 수 있고, onBufferOverflow를 사용하면
버퍼에 데이터가 가득 찼을경우 정책을 지정가능합니다.
MutableSharedFlow에는 subscriptionCount 속성을 통해 비즈니스 로직을 최적화 가능하게 하고,
MutableSharedFlow에는 Flow에 전송된 최신정보를 재생하지 않으려는 경우 resetReplayCahce 함수도 있음.
추가
liveData를 Flow로 마이그레이션
https://medium.com/androiddevelopers/migrating-from-livedata-to-kotlins-flow-379292f419fb
sharedIn stateIn
'IT > kotlin' 카테고리의 다른 글
[Flow] Flow combine 에러시 미동작 관련 내부 동작 (2) | 2024.10.14 |
---|---|
[flow] coroutine flow 문서 읽기 -1 (0) | 2022.05.10 |
[코틀린] 코틀린 제너릭 (0) | 2019.12.27 |
[코틀린] kapt (0) | 2019.12.27 |
[코틀린] let, with, run, also, apply 정리 (0) | 2019.12.05 |