Android

[안드로이드] Flow 공식문서로 이해하기 - 2차 StateFlow & SharedFlow

베르_최성훈 2023. 9. 3. 16:54

StateFlow & SharedFlow 

StateFlowSharedFlow 는 다수의 소비자에게 상태 및 값을 방출하는게 가능하도록 최적화된 Flow 이다.

Flow 와 StateFlow & SharedFlow cold flow, hot flow 라는 얘기가 자주 나오는데 무슨 뜻일까?

Cold & Hot Flow

그냥 Flow 는 cold Flow 이다. 소비자가 활성화되어 있지 않으면 방출도 중지된다. 소비자와 생산자가 1 대 1 관계이다.

StateFlowSharedFlow Hot Flow 이다. 소비자랑 상관 없이 참조가 존재하는 한 메모리에 남아서 방출한다. 다수의 소비자가 가능하다.

StateFlow

state-holder 로 observable flow 이다. 현재 상태 또는 새로운 상태 업데이트를 collectors 한테 방출한다.

value property 로 현재 상태를 read 할 수 있다. 새로운 상태로 변경하기 위해서는 MutableStateFlow 의 value property 로 변경해야 한다.

이는 기존 LiveData, MutableLiveData 와 아주 비슷한 형태를 띄고 있다.

stateFlow 는 android 에서 observable mutable 상태를 유지해야하는 클래스에 아주 적합하다!

View 가 UI 상태 업데이트를 listen 하고 구성 변경에도 현재 상태를 그대로 가져가서 상태가 지속되도록 할 수 있다.

class LatestNewsViewModel(
	private val newsRepository: NewsRepository
) : ViewModel() {

	// 백킹 프로퍼티 사용
	private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
	val uiState: StateFlow<LatestNewsUiState> = _uiState

	init {
		viewModelScope.launch {
			newsRepository.favoriteLatestNews
				// flow collect 해서 값 업데이트 collect 할 때 마다 변경됨
				.collect { favoriteNews ->
					_uiState.value = LatestNewsUiState.Success(favoriteNews)
			}
		}
	}
}

sealed class LatestNewsUiState {
	data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
	data class Error(val exception: Throwable): LatestNewsUiState()
}

MutableStateFlow 를 업데이트 하는 클래스가 생산자이며 StateFlow 로 부터 값을 수집하는 모든 클래스가 소비자이다.

flow 빌더를 사용하여 빌드한 cold flow 와 다르게 hotStateFlowcollect 해도 생산자 코드가 trigger 되지 않는다. hot & cold Flow 에서 설명했듯이 StateFlow 는 메모리에 항상 활성화되어 방출될 수 있으며 아무 참조가 없을 때 GC 당한다.

새로운 소비자가 collecting 를 시작하면 마지막 stream 에서 최신 state 를 받아온다.

class LatestNewsActivity : AppCompatActivity() {
	private val latestNewsViewModel = // getViewModel()

	override fun onCreate(savedInstanceState: Bundle?) {
		...
		// Start a coroutine in the lifecycle scope
		lifecycleScope.launch {
			// LiveData 와 달리 안드로이드 생명주기에 따라 작동하지 않는다.
			// repeatOnLifecycle 을 사용
			// STOPPED 면 collect 중지 STARTED 가 되면 재개한다.
			repeatOnLifecycle(Lifecycle.State.STARTED) {
				latestNewsViewModel.uiState.collect { uiState ->
					// 새로운 상태를 collect 했을 때
					when (uiState) {
						is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
						is LatestNewsUiState.Error -> showError(uiState.exception)
					}
				}
			}
		}
	}
}

경고 : UI Update 가 필요할 때 flow 의 launch or launchIn 을 곧바로 사용해선 안된다. 이 확장 함수는 View 가 보이지 않을 때도 호출되어 app crash 로 이어질 수 있다. 따라서 RepeatOnLifeCycle 을 사용해라.

flow 를 stateFlow 로 변경하고 싶으면 stateIn 을 사용하며 된다!

LiveData vs StateFlow

  1. StateFlowLiveData 와 다르게 initial state 를 필수로 가져야 하고 따라서 생성자로 넣어줘야 한다.
  2. View 가 STOPPED 상태가 되면 LiveData.observe() 는 소비자의 등록을 자동으로 취소한다. 그러나 StateFlow 는 자동으로 중지하지 않아 repeatOnLifecycle 블록에서 flow 를 수집해야 한다.

참고: shareIn 을 사용하면 cold flow 를 hot flow 로 만들 수도 있다. shareIn 은 SharedFlow 를 반환한다.

SharedFlow

안드로이드 개발할 때 Event를 어떻게 처리할지 고민해본 경험이 있을 것이다. 필자는 Event wrapper 클래스를 사용한 커스텀 SingleLiveData 를 만들어 사용했다.

SharedFlow 를 사용하면 보다 간편하게 이벤트를 처리할 수 있다. 다음의 특징을 가지기 때문이다.

StateFlow 와 다르게 Event 는 기본값이 없고 가장 최신의 State 를 받아와서도 안된다. 또한 여러 번 같은 이벤트가 발생해도 계속 발생해야한다.

SharedFlow 특징

  1. 소비자의 구독 이후 발생한 이벤트만 전달해준다.
  2. 중복된 값이 방출되어도 모두 collect 한다.

StateFlow 는 새로운 상태가 입력된 경우에만 소비자가 수집할 수 있다. 하지만 SharedFlow 의 경우 중복된 이벤트가 발생해도 모두 collect 한다.

private val _event = MutableSharedFlow<SomeEvent>()	
val event = _event.asSharedFlow()

// 이벤트 처리

someEvent.collect { event ->
	when(event) {
		is ... -> {}
		is ... -> {}
	}
}

sealed class SomeEvent { ... }

위와 같이 간단하게 이벤트를 처리할 수 있다.

SharedFlow 는 3가지 인자를 가진다.

  • replay : 새로운 구독자에게 이전 이벤트를 몇 개까지 전달할 것인가. default 0 이다.
  • extraBufferCapacity : 버퍼를 생성해서 emit 한 데이터가 버퍼에 유지
  • onBufferOverflow : 버퍼가 가득찼을 때 어떻게 처리할 것인지

이제 LiveData 를 Stateflow & SharedFlow 로 리팩터링 해야겠다! 끗

공식문서

https://developer.android.com/kotlin/flow/stateflow-and-sharedflow

참고한 블로그

https://myungpyo.medium.com/stateflow-와-sharedflow-32fdb49f9a32

https://medium.com/prnd/mvvm의-viewmodel에서-이벤트를-처리하는-방법-6가지-31bb183a88ce