데이터를 관리할 때 특히, MVVM에 보통 사용이 많이 되는 것이 LiveData 입니다.
LiveData란?
- 관찰 가능한 데이터 홀더 클래스
- 생명주기(Lifecycle)를 인식 하고 있기 때문에 생명주기가 끝나면 바로 메모리상에서 스스로 해제하여 메모리 누수가 일어나지 않습니다. (따로 관리를 할 필요가 없음)
- 앱 데이터가 변경될 때마다 관찰자가 대신 UI를 업데이트하므로 개발자가 업데이트할 필요가 없어 UI와 데이터의 상태가 일치 됩니다.
이러한 장점도 있지만 단점도 있는데,
안드로이드에서 권장하는 아키텍처 & 클린 아키텍처의 Domain Layer에서는 안드로이드 플랫폼에 독립적이기 때문에 안드로이드 플랫폼에 종속적인 LiveData를 사용하기가 어렵습니다.
즉, UI가 없는 곳에서는 사용하기가 어렵습니다.😥
그럴때 생각해 볼 수 있는 것이 StateFlow입니다!
StateFlow는 Flow API로 먼저 Flow를 이해해야합니다😁
Flow
단일 값만 반환하는 정지 함수와 달리 여러 값을 순차적으로 내보낼 수 있는 유형
코루틴을 기반으로 빌드되며 flow를 사용하여 실시간 업데이트를 수신할 수 있습니다.
- 생산자는 스트림에 추가되는 데이터를 생산합니다. 코루틴 덕분에 흐름에서 비동기적으로 데이터가 생산될 수도 있습니다.
- (선택사항) 중개자는 스트림에 내보내는 각각의 값이나 스트림 자체를 수정할 수 있습니다.
- 소비자는 스트림의 값을 사용합니다.
↓자세한 것은 아래를 참고해주세요↓
Android의 Kotlin 흐름 | Android Developers
Android의 Kotlin 흐름 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 코루틴에서 흐름은 단일 값만 반환하는 정지 함수와 달리 여러 값을 순차적으로 내보낼
developer.android.com
Flow는 생명주기를 알 수 없고, Cold Stream 방식이기 때문에 하나 이상의 소비자가 생기면 여러번 실행되게 됩니다.
이러한 단점을 보완해서 나온 것이 StateFlow, SharedFlow입니다.
StateFlow, SharedFlow
공통점
- Hot Stream이다.
- collect 될 때 데이터를 받아 올 수 있다.
차이점
- StateFlow는 반드시 초기값이 있어야한다.(SharedFlow는 초기값이 없어도 된다.)
- StateFlow는 LiveData 처럼 value를 통해 값을 가져올 수 있다.
- StateFlow는 공유할 수 있다.
- StateFlow는 중복으로 방출되는 경우 collect 하지 않는다.
- SharedFlow는 파라미터로 relplay, extraBufferCapacity, onBufferOverflow 등을 받을 수 있다.
- SharedFlow는 값을 가지고 있지 않다.
둘의 차이점이 있지만 SharedFlow의 한종류로 StateFlow가 있다고 생각해주시면 됩니다.
StateFlow와 LiveData
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// LiveData
private val _uiState = MutableLiveData<LatestNesUiState>()
val uiState: LiveData<LatestNewsUiState> = _uiState
// StateFlow
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
val uiState: StateFlow<LatestNewsUiState> = _uiState
}
ViewModel에서 StateFlow가 초기값을 넣는다는 것 외에는 LiveData와 StateFlow는 큰차이가 보이지 않습니다.
// StateFlow
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
// LiveData
class NameViewModel : ViewModel() {
val currentName: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
}
class NameActivity : AppCompatActivity() {
private val model: NameViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val nameObserver = Observer<String> { newName ->
nameTextView.text = newName
}
model.currentName.observe(this, nameObserver)
button.setOnClickListener {
val anotherName = "John Doe"
model.currentName.setValue(anotherName)
}
}
}
LiveData는 ViewModel에 있는 currentName을 Activity에 Observer를 통해 값의 변동을 보고 있거나 UI의 액션을 통해 값을 재정하는 것과 달리,
StateFlow에서는 Repository의 favortieLatestNews가 Flow 형태로 되어있을 것이고 그것을 collect하여 값을 저장하는 형식을 볼 수 있습니다.
+ 추가 1
StateFlow는 LifeCycle을 모르기 때문에 뷰가 STOPPED 상태가 되어도 수집(collect)을 중지 하지 않는데,
이러한 경우에는 Lifecycle.repeatOnLifecycle 블록에서 수집을 해야합니다.
lifecyclerScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// flow...
}
}
+ 추가 2
flow를 hot stream로 만드는 방법이 있는데 바로 shareIn을 사용하는 것이다.
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
SharingStarted.WhileSubscribed : 활성 구독자가 있는 동안 업스트림 생산자를 활성 상태로 유지
SharingStarted.Eagerly : 생산자를 즉시 시작
SharingStarted.Lazily : 첫 번째 구독자가 표시된 후 공유를 시작하고 흐름을 영구적으로 활성 상태로 유지
결론
livedata의 한계점을 flow, stateFlow를 적용하면서 해결이 된다.
데이터를 가져올 때 flow를 사용하면 중간에 Domain Layer에서 가공된 데이터를 가져올 수 있다.
collect를 통해 최신의 값을 유지할 수 있다!
참고
https://developer.android.com/topic/architecture?hl=ko
https://oliveyoung.tech/blog/2022-12-14/Android-State-Flow/
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ko
'개발 공부 > 안드로이드' 카테고리의 다른 글
[Compose] 컴포즈 공부하기5 - ConstraintLayout 1 (0) | 2023.09.06 |
---|---|
[Compose] 컴포즈 공부하기4 - Image (with painter) (0) | 2023.08.30 |
Compile과 Android Build (0) | 2023.08.22 |
[Compose] 컴포즈 공부하기3 - Row, Column 그리고 정렬 (0) | 2023.08.10 |
[Compose] 컴포즈 공부하기2 - Surface, Box 어떤 차이가 있을까? (0) | 2023.08.04 |