[Flow] Flow 사용 시 고려사항 #2

1 분 소요

👩🏻‍💻 android UI with flow

이전 게시글에서 말했듯이 liveData는 View의 상태에 따라 observe가 자동으로 취소된다.

하지만 flow는 아니다! 이를 잘못 사용하면 앱이 백그라운드에 있을 때 리소스가 낭비되게 된다. 따라서 이를 잘 이해하고 그에 맞는 scope에서 활용해야 한다.

flow를 안전하게 사용하는 방법은 크게 세 가지가 있다.

1. androidx.lifecycle:lifecycle-livedata-ktx
  - Flow<T>.asLiveData(): LiveData
2. androidx.lifecycle:lifecycle-runtime-ktx
  - Lifecycle.repeatOnLifecycle(state)
  - Flow<T>.flowWithLifecycle(lifecycle, state)

이렇게 사용한다면 flow가 수명주기를 인지하게 된다.

asLiveData()

class MainViewModel: ViewModel() {
  val message = repository.getMessages().asLiveData()
}

해당 방법은 liveData로 변경함으로써 수명주기가 인식되게 된다.

Lifecycle.repeatOnLifecycle() LifecycleScope 내부에서 repeatOnLifecycle을 사용하는 방식이다.

public suspend fun LifecycleOwner.repeatOnLifecycle(
  state: Lifecycle.State,
  block: suspend CoroutineScope.() -> Unit
): Unit = lifecycle.repeatOnLifecycle(state, block)

해당 함수 내부를 살펴보자. 다음과 같이 Lifecycle.State를 넘겨주고 해당 State에 도달하면 새로운 코루틴에서 block이 실행된다. 또한, lifecycle이 state 아래로 떨어지면 실행중인 코루틴을 취소한다.

repeatOnLifecycle(Lifecycle.State.STARTED) {
  viewModel.message.coolect { message ->
    listAdapter.submitList(message)
  }
}

또한, 여러 flow를 수집하는 경우 다음과 같이 여러 코루틴을 생성할 수 있다.

repeatOnLifecycle(Lifecycle.State.STARTED) {
  launch {
    AFLow.collect {...}
  }
  launch {
    BFlow.collect {...}
  }
}

Lifecycle.flowWithLifecycle

수집할 flow가 하나뿐일 경우 repeatOnLifecycle 대신 flowWithLifecycle을 사용할 수 있다.

lifecycleScopt.launch {
  viewModel.message
    .flowWithLifecycle(lifecycle, State.STARTED)
    .collect { message ->
      listAdatper.submitList(message)
    }
}


👩🏻‍💻 cold flow 재호출 방지

화면 회전 시, 액티비티가 다시 그려지지만 viewModel은 아니다. 그렇기 때문에 우리는 viewModel에 화면 관련 데이터를 저장하고 그 값을 유지하는 것이다.

하지만 다음과 같은 cold flow는 어떨까?

val result: Flow<Result<UiState>> = flow {
  emit(repository.fetchItem())
}

그렇다. 화면 회전이 일어날 때마다 계속해서 재수집 될 것이다. 따라서 이때 사용하는 것이 바로 .stateInstarted 파라미터이다.

val result: StateFlow<Result<UiState>> = someFlow
  .stateIn(
    initValue = Result.Loading,
    scope = viewModelScope,
    started = WhileSubscribed(5000),
  )

우선 .stateIn을 통해 StateFlow를 생성할 수 있다. 각 파라미터는 다음과 같은 의미를 지닌다.

  • initValue: 초기값
  • scope: stateFlow가 flow로부터 데이터를 구독받을 scope
  • started: Flow로부터 언제 구독할지

즉, started에 5000을 설정하면 stateFlow가 수집이 중단되었을 때 바로 flow를 취소하지 않고, 5초 동안 기다렸다 5초를 초과하면 취소를 진행한다.

그렇다면 어떨 때 이런 timeout이 의미가 있을까? 위에서 예시를 들었던 회전의 경우, flow가 중단되어 버리면 빠른 시간내 다시 시작해야 되므로 리소스가 낭비된다. 여기서 timeout을 설정하면 회전의 경우에는 5초가 지나지 않았기 때문에 flow가 쉽게 취소되지 않게 된다.


📃참고

댓글남기기