개발 공부/안드로이드

StateFlow에 대해 알아보자! (with LiveData)

yong_DD 2023. 8. 23. 18:44

데이터를 관리할 때 특히, 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 방식이기 때문에 하나 이상의 소비자가 생기면 여러번 실행되게 됩니다.

(cold Stream hot stream 차이)

 

이러한 단점을 보완해서 나온 것이 StateFlow, SharedFlow입니다.

 


StateFlow, SharedFlow

공통점

  1. Hot Stream이다.
  2. collect 될 때 데이터를 받아 올 수 있다.

차이점

  1. StateFlow는 반드시 초기값이 있어야한다.(SharedFlow는 초기값이 없어도 된다.)
  2. StateFlow는 LiveData 처럼 value를 통해 값을 가져올 수 있다.
  3. StateFlow는 공유할 수 있다.
  4. StateFlow는 중복으로 방출되는 경우 collect 하지 않는다.
  5. SharedFlow는 파라미터로 relplay, extraBufferCapacity, onBufferOverflow 등을 받을 수 있다.
  6. 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 

https://velog.io/@vov3616/StateFlow-vs-SharedFlow%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EA%B3%A0%EC%9E%88%EB%8B%88