LiveData with Coroutines and Flow — Part III: LiveData and coroutines patterns(학습 및 번역)
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow().asLiveData()
ViewModel patterns
LiveData와 Flow 사용을 비교하면서 ViewModel에서 사용할 수 있는 몇 가지 패턴을 살펴보겠습니다.
LiveData: Emit N values as LiveData
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeather: LiveData<String> = dataSource.fetchWeather()
변환을 수행하지 않으면 간단히 하나를 다른 하나에 할당할 수 있습니다.
Flow: Emit N values as LiveData
liveData 코루틴 빌더의 조합을 사용하고 Flow에서 collect할 수 있습니다(이는 각각의 방출된 값을 수신하는 터미널 연산자).
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// Don't use this
val currentWeatherFlow: LiveData<String> = liveData {
dataSource.fetchWeatherFlow().collect {
emit(it)
}
}
불필요한 상용구가 많기 때문에 한 줄에서 동일한 작업을 수행하는 Flow.asLiveData() 확장 함수를 추가했습니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow().asLiveData()
LiveData: Emit 1 initial value + N values from data source
데이터 소스가 LiveData를 노출하는 경우, emitSource를 사용하여 초기 값을 내보낸 후 업데이트를 파이프하는 데 emitSource를 사용할 수 있습니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeather: LiveData<String> = liveData {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}
Flow: Emit 1 initial value + N values from data source
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// Don't use this
val currentWeatherFlow: LiveData<String> = liveData {
emit(LOADING_STRING)
emitSource(
dataSource.fetchWeatherFlow().asLiveData()
)
}
그러나 Flow의 자체 API를 활용하면 상황이 훨씬 더 깔끔하게 보입니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> =
dataSource.fetchWeatherFlow()
.onStart { emit(LOADING_STRING) }
.asLiveData()
onStart는 초기 값을 설정하고 이 작업을 수행하면 LiveData로 한 번만 변환하면 됩니다.
LiveData: Suspend transformation
데이터 소스에서 오는 것을 변환하고 싶지만 CPU를 많이 사용하므로 suspend fun 에 있다고 가정해 보겠습니다.
데이터 소스의 LiveData에서 switchMap을 사용한 다음 liveData 빌더로 코루틴을 생성할 수 있습니다.
이제 수신된 각 결과에 대해 방출을 호출할 수 있습니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherLiveData: LiveData<String> =
dataSource.fetchWeather().switchMap {
liveData { emit(heavyTransformation(it)) }
}
Flow: Suspend transformation
Flow가 LiveData에 비해 정말 빛나는 부분입니다. 다시 Flow의 API를 사용하여 보다 우아하게 작업을 수행할 수 있습니다.
이 경우 Flow.map을 사용하여 모든 업데이트에 변환을 적용합니다.
이미 코루틴 컨텍스트에 있으므로 직접 호출할 수 있습니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val currentWeatherFlow: LiveData<String> =
dataSource.fetchWeatherFlow()
.map { heavyTransformation(it) }
.asLiveData()
Repository patterns

repository에서는 할 말이 많지 않습니다.
Flow를 사용하고 Flow를 노출하는 것처럼 Flow API를 사용하여 데이터를 변환하고 결합하기만 하면 됩니다.
val currentWeatherFlow: Flow<String> =
dataSource.fetchWeatherFlow()
.map { ... }
.filter { ... }
.dropWhile { ... }
.combine { ... }
.flowOn(Dispatchers.IO)
.onCompletion { ... }
Data source patterns
다시 한 번 원샷 작업과 Flow을 구분해 보겠습니다.
One-shot operations in the data source
Room 또는 Retrofit과 같은 suspend fun 을 지원하는 라이브러리를 사용하는 경우 suspend fun 에서 간단히 사용할 수 있습니다!
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun doOneShot(param: String) : String =
retrofitClient.doSomething(param)
그러나 일부 도구와 라이브러리는 아직 코루틴을 지원하지 않으며 콜백 기반입니다.
이런 경우에는 suspendCoroutine 또는 suspendCancellableCoroutine을 사용할 수 있습니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
suspend fun doOneShot(param: String) : Result<String> =
suspendCancellableCoroutine { continuation ->
api.addOnCompleteListener { result ->
continuation.resume(result)
}.addOnFailureListener { error ->
continuation.resumeWithException(error)
}.fetchSomething(param)
}
당신이 suspendCancellableCoroutine을 호출하면, 당신은 continuation을 얻습니다.
이 예에서 우리는 complete listener 와 failure listenr 를 설정할 수 있는 API를 사용하고 있으므로 ,
데이터나 오류를 수신할 때 콜백에서continuation.resume 또는continuation.resumeWithException을 호출합니다.
이 코루틴이 cancel되면 resume가 무시된다, Request에 시간이 지나서 콜백 중 하나가 실행될 때까지 코루틴이 활성화됩니다.
Exposing Flow in the data source
Flow builder
데이터 소스의 fake implementation 을 만들어야 하거나 간단한 것이 필요한 경우 flow 생성자를 사용하고 다음과 같이 할 수 있습니다.
override fun fetchWeatherFlow(): Flow<String> = flow {
var counter = 0
while(true) {
counter++
delay(2000)
emit(weatherConditions[counter % weatherConditions.size])
}
}
이 코드는 2초마다 기상 조건을 내보냅니다
Callback-based APIs
콜백 기반 API를 플로우로 변환하고 싶다면 callbackFlow를 사용하면 됩니다.
<!-- Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
val callback = object : Callback {
override fun onNextValue(value: T) {
offer(value)
}
override fun onApiError(cause: Throwable) {
close(cause)
}
override fun onCompleted() = close()
}
api.register(callback)
awaitClose { api.unregister(callback) }
}
보기에는 어려워 보이지만 분리해 보면 많은 의미가 있음을 알 수 있습니다.
- 새로운 value 가 있을 때 offer()을 호출합니다.
- 업데이트 전송을 중단하고 싶을 때 close(cause)를 호출합니다.
- Flow이 닫힐 때 실행해야 하는 일들을 정의하기 위해 awaitClose를 사용합니다.
이는 콜백 등록을 취소하는 것들이 적합합니다.