[Android] SharedFlow, StateFlow?

최민재·2022년 9월 20일
5

공부

목록 보기
5/6
post-thumbnail

들어가며 👋

한 프로젝트를 개발하며
Coroutine과 Flow를 활용한 리모트 통신을 구현하고 있었습니다.
그런데 똑같은 값을 다시 받으면 무시되는 현상이 발견되어 원하던 동작을 하지 않았습니다.
그래서 StateFlow를 사용하여 원하던 동작을 하도록 수정했죠.

여기서 저는 StateFlowSharedFlow가 정확이 어떤 차이가 있는지 잘 몰랐죠.
누군가에게 설명할 수 있을 정도가 아니라는 것을 깨달았습니다.
그래서 이번에 StateFlowSharedFlow에 대하여 정리해 보고자 합니다!!

LiveData와 Flow

기존에는 상태를 관리하기 위해 Observer 패턴으로 구현된 LiveData를 사용했습니다.
하지만 LiveData에는 단점이 있습니다.

위의 사진은 클린 아키텍쳐의 의존성 관계입니다.

  • Data Layer는 Remote, Local 통신을 담당하죠.

    하지만 LiveData는 비동기 데이터 처리에 적합하게 설계되지 않았습니다!
    Main Thread에서 관찰이 진행되기 때문이죠!

  • Domain Layer는 위 그림과 같이 의존성을 가지지 않는 독립적인 계층입니다.

    그러니 의존성을 가지지 않은 순수 Kotlin, Jave 코드로 이루어지죠.
    LiveData는 AAC 라이브러리로 해당하는 모듈을 추가해야하는 문제가 발생합니다.

LiveData는 이러한 단점이 존재하고 있었습니다.
그리고 Kotlin Coroutine이 뜨기 시작하며 Flow가 등장했습니다.
여기서 Flow는 코틀린에서 사용할 수 있는 비동기 스트림입니다.

그리고 "FlowLiveData를 대체할 수 있지 않을까?" 라는 기대가 생겼죠.

하지만.. 역시 문제가 있죠..

  • Flow는 콜드 스트림으로 연속적으로 들어오는 데이터를 처리할 수 없습니다.
    • 콜드 스트림 - 요청이 있는 경우에 보통 1:1로 값을 전달하기 시작
    • 핫 스트림 - 0개 이상의 상대를 향해 지속적으로 값을 전달
  • Flow는 스스로 안드로이드 생명주기를 알지 못합니다.
  • Flow는 상태가 없어 현재 값을 할당 중인지 모릅니다.

그래서 SharedFlowStateFlow가 등장하게 되었습니다!

StateFlow와 SharedFlow란? 🌊

안드로이드 공식 문서에 따른 정의를 보겠습니다.

StateFlow와 SharedFlow는 흐름에서 최적으로 상태 업데이트를 내보내고 여러 소비자에게 값을 내보낼 수 있는 Flow API입니다.

StateFlow

StateFlow는 현재 상태와 새로운 상태를 Collector에 내보내는 관찰 가능한 상태를 담는 흐름입니다.

  • Hot Stream 입니다.
    -> Flow는 마지막 값이 없는 반면 StateFlow는 마지막 값의 개념이 있습니다.
  • 항상 값을 가지고 있습니다.
  • 여러개의 collector를 지원해서 중복 리소스 요청을 방지합니다!

따라서 LiveData를 대체할 수 있습니다! 그리고 AAC DataBinding에도 StateFlow가 호환되죠!

class ViewModel() {
	val text = StateFlow<String>
}
<TextView
      android:id="@+id/textview"
      android:text="@{vm.text}"/>

StateFlow 예제

이제 예제를 통해서 StateFlow를 보도록 하죠.
대충 ViewModel에서 카드의 내용을 가져와 뷰에 갱신해야하는 상황이라고 가정한다면..

class ViewModel() {
	private val _cardState = MutableStateFlow(CardState())
    val cardState: StateFlow<CardState> = _cardState
    
    fun getCardContents() {
    	viewModelScope.launch {
        	val cardContents: CardState = 결과를 가져오는 함수
            _cardState.value = cardContents
        }
    }

}

viewModelScope 안에서 카드의 내용을 가져오는 함수를 실행시킵니다.
그 결과를 _cardState의 값에 넣죠.

그렇다면 Activity에서 이 값을 받습니다.

class Acitivty {
	
    ...
    
    fun collectCardState() {
   		lifecycleScope.launchWhenStarted {
        	viewModel.cardState.collect { state ->
        
            	if (state.result != null) {
             	   	makeCard(state.card)
            	}

				if (state.error.isNotBlank()) {
            	    showError(state.error)
            	}
            
        	}
    	}
    }
}

Activity에서 StateFlow의 값을 collect 해줍니다.

SharedFlow

먼저 관계를 보자면

Flow -> SharedFLow -> StateFlow로 상속 관계가 이루어집니다.
SharedFlowFlow를 상속 받고 StateFlowSharedFlow를 상속 받습니다.

따라서 SharedFlowStateFlow의 일반화된 버전으로 볼 수 있습니다.

  • Hot Stream 입니다.
  • 초기값을 가지지 않습니다.

그러면 바로 예제를 봅시다.

class ViewModel : ViewModel() {
	
    private val _cardState = MutableSharedFlow<CardState>(
    	replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    val cardState = _cardState.asSharedFlow()
    
    fun getCardInfo() {
    	viewModelScope.launch {
        	_cardState.emit(CardState())
        }
    }
}

위의 코드에서

replay : 새로운 구독자들에게 이전 이벤트 방출 여부 (0 = 방출X, 1 = 방출)
extraBufferCapacity : 추가 버퍼 생성 여부 (1 = 생성)
OnBufferOverflow : 버퍼 초과시 처리 여부 (DROP_OLDEST = oldest 데이터 drop)

class Acitivty {
	
    ...
    
    fun collectCardState() {
   		lifecycleScope.launchWhenStarted {
        	viewModel.cardState.collect { state ->
        
            	if (state.result != null) {
             	   	makeCard(state.card)
            	}

				if (state.error.isNotBlank()) {
            	    showError(state.error)
            	}
            
        	}
    	}
    }
}

SharedFlow와 똑같이 값을 받을 수 있습니다.

마치며 👍

오랜만에 글을 정리해 보았어요!
부족한 점이 있으면 댓글로 꼭 알려주세요!

SharedFlow, StateFlow 개념을 잘 익힌 후
상황에 맞게 자유자제로 사용할 수 있는 것이 중요한거 같아요!

저의 문제 상황은 StateFlow가 중복된 리소스를 방지하여 값이 오지 않는다는 것이었으니
SharedFlow로 변경하여 원하는 동작을 하도록 수정하였습니다!!

profile
응애 Android 개발자

0개의 댓글