[androidx] Flow 2

sundays·2022년 12월 22일
0

coroutine

목록 보기
3/7

StateFlow

  • 현재 상태와 새로운 상태 업데이트를 수집기에 내보내는 관찰 가능한 상태 홀더 흐름
  • value 속성으로 현재 상태 값을 읽을 수 있습니다
  • 상태를 업데이트 하고 흐름에 전송하려면 MutableStateFlow 클래스의 value 속성에 새 값을 할당합니다
  • 관찰 가능한 변경 가능 상태를 유지해야할때 적합합니다
  • View 가 UI 상태 업데이트를 Listen 하고 구성 변경에도 기본적으로 화면 상태가 지속되도록 LatestNewsViewModel 에서 StateFlow를 노출할 수 있습니다
class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update View with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(val exception: Throwable): LatestNewsUiState()
}
  • MutableStateFlow 업데이트를 담당하는 클래스가 생산자, StateFlow에서 수집되는 모든 클래스가 소비자
  • flow 빌더와는 달리 StateFlow는 생산자 코드가 트리거 되지 않습니다
  • StateFLow는 항상 활성 상태이고 메모리 내에 있으며 가비지 컬렉션 루트에서 달리 참조가 없는 경우에만 가비지 컬렉션에 사용할 수 있습니다
  • 새로운 소비자가 흐름에서 수집을 시작하면 스트림의 마지막 상태와 후속 상태가 수신됩니다
  • LiveData 같이 관찰 가능한 다른 클래스에서 이 동작을 찾을 수 있습니다.
class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Start a coroutine in the lifecycle scope
        lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // Note that this happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

UI 업데이트 주의

  • launch 또는 launchIn 확장 함수로 UI에서 직접 흐름을 수집하면 안됩니다
    • 이런경우에는 뷰가 표시되지 않는 경우에도 이벤트를 처리하게 되어 앱이 다운될 수 있습니다
    • 이것을 방지하기 위해서는 repeatOnLifeCycle API를 사용합니다

StateFlow, Flow, LivData 의 차이점

  • StateFlow의 경우 초기 상태를 생성자에게 전달하지만 LiveData는 전달하지 않습니다
  • 뷰가 Stopped 상태일때
    • LiveData.observe()는 소비자를 자동으로 등록 취소
    • StateFlow 는 다른 흐름에서 수집하는 경우엔 자동으로 수집을 중지하지 않습니다. 만약 LiveData 처럼 실행되게 하려면 Lifecycle.repeatOnLifecycle 블록에서 흐름을 수집해야 합니다

shareIn

  • StateFlow 는 흐름이 수집되는 동안 가비지 컬렉션 루트에서 다른 참조가 있는 경우 메모리에 남아있게 합니다
  • callbackFlow를 사용하여 firestore에서 가져온 데이터를 shasreIn 을 통해 collect간에 공유할 수 있습니다. 전달되는 종류는 다음과 같습니다.
    • externalScope CoroutineScope 공유 흐름을 필요한 만큼 유지하기 위해 이 범위는 소비자 보다 오래 지속되어야 합니다
    • replay = 1 각 새 수집기로 재생할 항목의 수
    • started = { } 시작 동작 정책
class NewsRemoteDataSource(...,
    private val externalScope: CoroutineScope,
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}
  • latestNews 는 마지막으로 내보낸 항목을 새 수집기로 재생하여 externalScope가 활성 상태이고 활성 수집기가 있는 한 활성 상태로 유지됩니다
  • sharingStarted.WhileSubscribed() 는 subscriber가 있는 동안 업스트림 생산자를 활성 상태로 유지합니다.
    • sharingStarted.Eagerly 생산자를 즉시 시작
    • sharingStarted.Lazyly 첫번째 subscriber를 표시한 후 공유를 시작하고 흐름을 영구적으로 활성 상태로 유지

SharedFlow

  • 모든 콘텐츠가 주기적으로 동시에 새로고침 되도록 나머지 부분에 tick을 전송할 수 있습니다
  • 최신 뉴스 외 좋아하는 주제 컬렉션으로 사용자 정보 섹션을 새로고침 할 수 있습니다
// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Event<String>> = _tickFlow

    init {
        externalScope.launch {
            while(true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    ...,
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { ... }
    ...
}
  • reply 를 사용하면 이전에 내보낸 여러 값을 새 구독자를 위해 다시 보낼 수 있습니다
  • onBufferOverflow 를 사용하여 버퍼가 전송할 항목으로 가득 찬 경우 적용할 정책을 지정할 수 있습니다
    • 기본값으로 BufferedOverflow.SUSPEND 으로 호출자를 정지 합니다
    • DROP_LATEST
    • DROP_OLDEST
  • MutableSharedFlow 에서 활성 수집기의 수가 포함된 subscriptionCount 속성이 있어서 비즈니스 로직을 적절하게 최적화
  • MutablesharedFLow 에는 흐름에 전송된 최신 정보를 재생하지 않으려는 경우를 위한 resetReplayCache 를 사용

Reference

profile
develop life

0개의 댓글