Notice
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- ar vr 차이
- 클래스
- jvm 작동 원리
- 객체
- 안드로이드 유닛 테스트 예시
- 안드로이드 라이선스
- 자바 다형성
- Rxjava Observable
- 안드로이드 레트로핏 사용법
- 안드로이드 레트로핏 crud
- 스택 자바 코드
- rxjava disposable
- 스택 큐 차이
- 플러터 설치 2022
- 멤버변수
- jvm이란
- rxjava hot observable
- 안드로이드 os 구조
- 안드로이드 라이선스 종류
- 안드로이드 유닛테스트란
- android ar 개발
- rxjava cold observable
- 서비스 vs 쓰레드
- 2022 플러터 설치
- 2022 플러터 안드로이드 스튜디오
- 큐 자바 코드
- android retrofit login
- ANR이란
- 서비스 쓰레드 차이
- 안드로이드 유닛 테스트
Archives
- Today
- Total
나만을 위한 블로그
[Android] StateFlow vs SharedFlow 본문
728x90
반응형
StateFlow와 SharedFlow를 설명하는 공식문서가 어제 날짜인 3월 13일에 업데이트됐다.
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ko
이 공식문서 내용을 기준으로 StateFlow, SharedFlow에 대해 확인하고 둘의 차이도 같이 확인한다.
StateFlow
StateFlow는 현재 상태와 새 상태 업데이트를 수집기에 내보내는 관찰 가능한 상태 홀더 흐름(Flow)이다. value 속성을 통해서도 현재 상태 값을 읽을 수 있다. 상태를 업데이트하고 Flow에 전송하려면 MutableStateFlow 클래스의 value 속성에 새 값을 할당한다. 안드로이드에서 StateFlow는 관찰 가능한 변경 상태를 유지해야 하는 클래스에 아주 적합하다. View가 UI 상태 업데이트를 listen하고 구성 변경(configuration change)에도 기본적으로 화면 상태가 지속되도록 LatestNewsViewModel에서 StateFlow를 노출할 수 있다
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// 다른 클래스의 상태 업데이트를 방지하기 위한 backing property
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
// UI는 상태 업데이트를 얻기 위해 이 StateFlow에서 수집한다
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
// 최신 인기 뉴스로 View 업데이트
// MutableStateFlow의 value 프로퍼티에 기록하여 흐름에 새 요소를 추가하고 모든 수집기를 업데이트한다
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
// LatestNews 화면의 다양한 상태를 나타낸다
sealed class LatestNewsUiState {
data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
data class Error(val exception: Throwable): LatestNewsUiState()
}
MutableStateFlow 업데이트를 담당하는 클래스가 생산자(producer)고 StateFlow에서 수집되는 모든 클래스가 소비자(consumer)다. flow 빌더를 써서 빌드된 Cold Flow와 달리 StateFlow는 Hot Flow다. Flow에서 수집해도 생산자 코드가 트리거되지 않는다. StateFlow는 항상 활성 상태고 메모리 안에 있으며 가비지 컬렉션 루트에서 이에 대한 다른 참조가 없는 경우에만 가비지 수집 대상이 된다. 새 소비자가 Flow에서 수집을 시작하면 스트림의 마지막 상태와 후속 상태가 수신된다. LiveData 같이 관찰 가능한 다른 클래스에서 이 동작을 찾을 수 있다. View는 다른 Flow와 마찬가지로 StateFlow를 listen한다
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// 생명주기 범위에서 코루틴 시작
lifecycleScope.launch {
// repeatOnLifecycle은 수명 주기가 STARTED 상태(또는 그 이상)에 있을 때마다 새 코루틴에서 블록을 시작하고 STOPPED일 때 취소한다
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Flow를 트리거하고 값 수신을 시작한다
// 생명주기가 STARTED일 때 발생하고 STOPPED일 때 수집이 중지된다
latestNewsViewModel.uiState.collect { uiState ->
// 새 값을 받음
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
}
UI를 업데이트해야 하는 경우 launch 또는 launchIn 확장 함수로 UI에서 직접 흐름을 수집하면 안 된다. 이런 함수는 뷰가 표시되지 않을 때에도 이벤트를 처리한다. 이 동작으로 앱이 다운될 수 있다. 이걸 방지하려면 repeatOnLifecycle API를 사용한다. 이 API는 androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 이상 버전에서만 쓸 수 있다
Flow를 StateFlow로 바꾸려면 stateIn 중간 연산자를 사용한다
stateIn을 모른다면 아래 공식문서를 참고한다.
Cold Flow를 주어진 coroutineScope에서 시작되는 Hot State Flow로 바꿔서 업스트림 Flow의 단일 실행 인스턴스에서 가장 최근에 방출된 값을 여러 다운스트림 구독자와 공유한다...(중략)...이 연산자는 일부 상태 값에 대한 업데이트를 제공하는 Cold Flow가 있고 생성 및 / 또는 유지 관리 비용도 많이 들지만 가장 최근 상태 값을 수집해야 하는 여러 구독자가 있을 때 유용하다. 예를 들어 비용이 많이 드는 네트워크 연결을 통해 백엔드에서 오는 상태 업데이트 Flow를 고려하면 설정하는 데 많은 시간이 걸린다...(중략)...stateIn을 쓰면 Flow의 모든 수집기 간에 단일 연결이 공유되며 연결이 필요할 때 이미 설정됐을 가능성이 있다
StateFlow, Flow, LiveData
StateFlow, LiveData는 비슷한 점이 있다. 둘 다 관찰 가능한 데이터 홀더 클래스고 앱 아키텍처에 사용할 때 비슷한 패턴을 따른다. 그러나 둘은 다르게 작동한다
- StateFlow는 초기 상태를 생성자에 전달해야 하지만 LiveData는 그렇지 않다
- 뷰가 STOPPED 상태가 되면 LiveData.observe()는 소비자를 자동으로 등록 취소하지만 StateFlow 또는 다른 Flow에서 수집하는 경우 자동으로 수집을 중지하지 않는다. 같은 동작을 실행하려면 Lifecycle.repeatOnLifecycle {}에서 Flow를 수집해야 한다
ShareIn을 써서 Cold Flow를 Hot Flow로 만들기
StateFlow는 Hot Flow로, Flow가 수집되는 동안 또는 가비지 컬렉션 루트에서 다른 참조가 있는 경우 메모리에 남아 있다. shareIn 연산자를 써서 Cold Flow를 Hot Flow로 바꿀 수 있다. 각 수집기에서 새 Flow를 만들 필요 없이 Kotlin 흐름 링크에서 예시로 생성한 callbackFlow를 쓰면 가져온 데이터를 shareIn을 통해 수집기 간에 공유할 수 있다. 다음을 전달해야 한다
- 흐름을 공유하는 데 사용되는 coroutineScope. SharedFlow를 필요한 만큼 유지하기 위해 이 scope는 소비자보다 오래 지속돼야 한다
- 각 새 수집기로 재생할 항목의 수
- 시작 동작 정책
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
위 예시에서 latestNews Flow는 마지막으로 내보낸 항목을 새 수집기로 재생하며 externalScope가 활성 상태고 활성 수집기가 있는 한 활성 상태로 유지된다. SharingStarted.WhileSubscribed() 시작 정책은 활성 구독자가 있는 동안 업스트림 생산자를 활성 상태로 유지한다. 다른 시작 정책도 쓸 수 있다. SharingStarted.Eagerly를 써서 생산자를 즉시 시작하거나 SharingStarted.Lazily를 써서 첫 번째 구독자가 표시된 후 공유를 시작하고 Flow를 영구적으로 활성 상태로 유지할 수 있다
SharedFlow
shareIn 함수는 모든 소비자에게 값을 내보내는 Hot Flow인 SharedFlow를 만든다. SharedFlow는 StateFlow의 유연한 구성 일반화다. shareIn을 쓰지 않고 SharedFlow를 만들 수도 있다. 예를 들어 SharedFlow를 쓰면 모든 컨텐츠가 주기적으로 동시에 새로고침되도록 앱의 나머지 부분에 틱을 전송할 수 있다. 최신 뉴스를 가져오는 것 외에도 좋아하는 주제 컬렉션으로 사용자 정보 섹션을 새로고침할 수도 있다. 아래 코드 스니펫에서 TickHandler는 다른 클래스가 컨텐츠를 새로고침할 시기를 알 수 있게 SharedFlow를 노출한다. StateFlow의 경우처럼 클래스에서 MutableSharedFlow 타입의 지원 속성을 써서 아이템을 Flow로 보낸다
참고) SharedFlow 인터페이스는 Flow 인터페이스를 구현하고, StateFlow 인터페이스는 SharedFlow 인터페이스를 구현한다. Flow -> SharedFlow -> StateFlow 순서로 구현하는 것이다.
// 앱의 콘텐츠를 새로 고쳐야 할 때 중앙 집중화하는 클래스
class TickHandler(
private val externalScope: CoroutineScope,
private val tickIntervalMs: Long = 5000
) {
// 다른 클래스의 흐름 배출을 피하기 위한 Backing property
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() { ... }
...
}
아래 방법으로 SharedFlow 동작을 커스텀할 수 있다
- replay를 쓰면 이전에 내보낸 여러 값을 새 구독자에게 다시 보낼 수 있다
- onBufferOverflow를 쓰면 버퍼가 전송할 아이템으로 가득 경우에 적용할 정책을 지정할 수 있다. 기본값은 호출자를 정지시키는 BufferOverflow.SUSPEND다. 다른 옵션은 DROP_LATEST, DROP_OLDEST다
또한 MutableSharedFlow에는 활성 수집기의 수가 포함된 subscriptionCount 속성이 있어서 비즈니스 로직을 적절하게 최적화할 수 있다. MutableSharedFlow에는 Flow에 전송된 최신 정보를 재생하지 않으려는 경우를 위한 resetReplayCache 함수도 있다
본문은 이걸로 끝이고 나머지는 추가로 보면 좋은 링크들이다. 처음부터 끝까지 쭉 읽어보면 프로젝트에 적용할 때 도움되는 내용들이 많다.
Flow와 MVVM, Retrofit을 섞어 쓰는 방법이 궁금하다면 아래 포스팅을 확인한다. stateIn, shareIn도 사용하지 않았고 어디까지나 예제 수준의 코드니 대충 이런 느낌이라는 걸 확인하고 다른 글들을 보면서 발전시켜 나가면 좋을 것이다.
https://onlyfor-me-blog.tistory.com/478
반응형
'Android' 카테고리의 다른 글
[Android] 단위 테스트 실행 시 Method getMainLooper in android.os.Looper not mocked 에러 해결 (0) | 2023.03.15 |
---|---|
[Android] 단위 테스트 실행 시 No tests found for given includes 에러 해결 (0) | 2023.03.15 |
[Android] Context, ContextImpl, ContextWrapper란? (0) | 2023.03.01 |
[Android] Android 13 변경사항 (0) | 2023.02.27 |
[Android] 안드로이드 스튜디오 Electric Eel 업데이트 후 앱 실행 시마다 Run 탭 올라오지 않게 하기 (0) | 2023.02.04 |
Comments