Glacier's Daily Log

'Flow' in Android 본문

Coding/Android

'Flow' in Android

h__glacier_ 2024. 1. 24. 19:29
반응형
  • Flow?
    • Coroutine 비동기 작업에서 Suspend 함수를 사용하여 작업이 모두 완료된 후에 값을 리턴받을 수 있다.
    • 하지만 중간에 진행되는 작업 중에 갱신되는 값들도 계속 받을 수 있다면 업데이트를 조금 더 다이나믹하게 진행할 수 있지 않을까?
    • Flow를 사용해서 연속적인 값을 방출하게 구현할 수 있다.

 

  • LiveData vs Flow
    • 기존에 사용하던 LiveData와 Flow의 차이점?
      • LiveData- 생명주기를 가진 데이터 홀더
        - 메인스레드에서 동작. 별도 쓰레드에서 동작해야 하는 상황 (Data Layer에서의 구현 등) 에서는 사용하기 어려움
        - 안드로이드 의존성이 밀접하게 결합되어 있어 테스트가 힘들다.- 마찬가지로 안드로이드 의존성이 있기 때문에 코드 분리시에 조심해서 사용해야함

      • Flow- Coroutine 스코프 내에서 동작. lifecycleScope/viewmodelScope와 사용하면 생명주기에 맞춰서 적절한 동작 구현 가능
        - 코틀린 기능이라 안드로이드 의존성이 없다 → 테스트 용이성, 코드 분리시 적절하게 활용 가능
        - Cold Stream → collect시에 값이 방출된다.
        - LiveData와 다르게 상태가 없다.

 

  • 데이터 스트림 처리
    • Producer
      • flow {} 블럭 내에서 emit()을 통해서 데이터 생성
      • Local/Remote DataSource 등에서 데이터를 생성
    • Intermediary
      • 생성된 데이터를 원하는 형태에 맞게 가공
      • 대표적으로 방대한 데이터에서 필요한 데이터만 뽑아 객체로 만드는 등의 작업이 있음.
      • map, filter, onEach 등의 메소드를 주로 사용.
    • Consumer
      • collect를 통해서 데이터 스트림을 소비
      • viewmodel에서 collect하여 View에 뿌려주는 등의 작업이 진행될 수 있음.

 

  • Flow 지원 메소드
    • collect {} → 데이터를 수집
    • collectIndexed → 데이터를 index와 함께 수집 (param : index, value)
    • collectLatest → 가장 최근의 데이터를 수집
    • .flowOn(Dispatchers.IO).collect {} → 기존 코루틴은 withContext(Dispatchers.IO) 와 같이 디스패쳐를 변경했지만
      Flow에서는 .flowOn(DISPATCHER) 로 변경할 것을 권장.


  • StateFlow?
    • Why StateFlow instead of LiveData?
      • LiveData도 충분히 사용하기 좋은 데이터 홀더이지만 아키텍처 관점에서 다소 단점이 있음.
      • LiveData는 Android 플랫폼에 의존성이 있다. 따라서 UI가 없는 곳에서 LiveData를 사용할 수 없음.
      • 클린아키텍처를 따르려 Domain Layer/Data Layer 분리시에 LiveData를 사용하는데 한계가 있음.
        또한 코드 계층을 분리할 때 LiveData가 있는 코드로는 안드로이드 의존성을 떼어내기가 매우 어려움.
      • 심지어 LiveData는 UI와 밀접하게 연관되어 있어 오직 메인스레드 에서만 읽고 쓸 수 있다. 비동기 스트림 불가.
      • Coroutine이 발전하면서 Flow가 등장하게되었고 LiveData를 대체할 수 있는 StateFlow와 SharedFlow라는 대안이 생김.

    • StateFlow/SharedFlow는 Hot Stream이다.
      • 하나 이상의 소비자들이 구독할 수 있고, 기본값을 가지고 모든 구독자에게 같은 데이터를 발행하며, 구독자가 없는 경우에도 데이터를 발행함.
      • 생성하자마자 바로 활성화되며, 값이 업데이트 된 경우에만 반환.

class MyViewModel {
	private val _myUiState = MutableStateFlow<Result<UiState>>(Result.Loading)
	val myUiState: StateFlow<Result<UiState>> = _myUiState

	// Load data from a suspend fun and mutate state
	init {
		viewModelScope.launch {
			val result = ...
			_myUiState.value = result
			}
		}
	}

 

 

    • stateIn Flow extension 활용하여 더 간단하게 사용하기
val userFlow: StateFlow<UiState> = userRepository
    .getUsers()
    .asResult()
    .map { result ->
      when (result) {
        is Result.Loading -> UiState.Loading
        is Result.Success -> UiState.Success(result.data)
        is Result.Error -> UiState.Error(result.exception)
      }
    }
    .stateIn(
      scope = viewModelScope,
      initialValue = UiState.Loading,
      started = SharingStarted.WhileSubscribed(DEFAULT_TIMEOUT)
    )
  • - 참고 : 
 

StateFlow & SharedFlow에 대한 고찰

StateFlow와 SharedFlow에 관한 잘 정리된 문서가 존재하지만, 해당 포스트에서는 적절한 hot flow를 어떻게 선택할 지에 대한 몇가지 아이디어를 제시하고자 한다.

velog.io

 

  • repeatOnLifecycle(Lifecycle.State.STARTED)로 리소스 낭비 방지하기
    • repeatOnLifecycle(Lifecycle.State.STARTED) {} 블록 내의 코드는 라이프사이클이 STARTED와 STOPPED내에 있을 때 Flow를 수집.
    • 따라서 원치않는 생명주기에 리소스 낭비를 방지할 수 있음.

 

 

반응형
Comments