오늘도 더 나은 코드를 작성하였습니까?

flow (예외 작업 / 다른 CoroutineContext에서 실행시키기) 본문

Coroutine/coroutineFlow

flow (예외 작업 / 다른 CoroutineContext에서 실행시키기)

hik14 2021. 5. 13. 16:06

Catching unexpected exceptions

생산자는 구현은 다른 라이브러리에서 가져올 수 있습니다.

이는 예기치 않은 예외가 발생할 수 있음을 의미합니다. 이러한 예외를 처리하려면 catch 중간 연산자를 사용.

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // catch 중간연산자 예외가 던저진다면,
                // 처리하고 UI를 처리한다. 
                .catch { exception -> notifyError(exception) }
                .collect { favoriteNews ->
                   
                }
        }
    }
}

예외가 발생하면 새 데이터가 수신되지 않았기 때문에 collect lambda가 호출되지 않는다.

catch는 데이터를 flow에 내보낼 수 있습니다. repository layer는 대신 이전에 캐시 된 값을 내보낼 수 있습니다.

class NewsRepository(...) {
    val favoriteLatestNews: Flow<List<ArticleHeadline>> =
        newsRemoteDataSource.latestNews
            .map { news -> news.filter { userData.isFavoriteTopic(it) } }
            .onEach { news -> saveInCache(news) }
            // 만약 에러가 발생한다면 최신 캐쉬된 값을 내보낸다. 
            .catch { exception -> emit(lastCachedNews()) }
}

 

위에서 예외가 발생하면 예외로 인해 캐쉬되었던 새로운 data가 stream으로 내보내 졌으므로 collect lambda가 호출됩니다.

Executing in a different CoroutineContext

기본적으로 flow builder 의 생산자는 collecting하는 coroutine의 CoroutineContext에서 실행된다.

 flow builder에서는 다른 CoroutineContext에서 값을 내보낼 수 없다.

 

이러한 제한 경우에 따라 불편할 수 있다.

 

Repository layer는 viewModelScope에서 사용하는 Dispatchers.Main에서 작업을 수행하지 않아야합니다

flow의 CoroutineContext를 변경하려면 중간 연산자 flowOn을 사용합니다.

 

flowOn은 upStream flow의 CoroutineContext를 변경합니다.

 

upStream flow 는 이전 (또는 그 이상)에 적용된 생산자와 중간 연산자를 의미한다.

downStream flow는 소비자와 함께 flowOn 이후의 중간 연산자를 의미한다. 

 

Producer(생산자) intermediate operator flowOn intermediate operator Consumer(소비자)
upStream flow

downStream flow 
   I/O Thread / Default Thread      

 MainThread

 

downStream flow 은 영향을 받지 않으며 flow에서 collecting하는 데 사용되는 CoroutineContext에서 실행됩니다.

flowOn 연산자가 여러개 일때,  각 연산자는 현재 위치에서 upStreamdownStream으로 변경합니다.

 

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource,
    private val userData: UserData,
    private val defaultDispatcher: CoroutineDispatcher
) {
    val favoriteLatestNews: Flow<List<ArticleHeadline>> =
        newsRemoteDataSource.latestNews
            .map { news -> // filter 중간연산자 DefaultThread에서 실행됨. 
                news.filter { userData.isFavoriteTopic(it) }
            }
            .onEach { news -> // saveInCache 중간연산자 DefaultThread에서 실행됨. 
                saveInCache(news)
            }
            // flowOn은 위의 the upstream flow ↑에 영향을 준다. 
            .flowOn(defaultDispatcher)
            // the downstream flow ↓ 은 영향받지 않는다. 
            .catch { exception ->  // MainThread에서 실행된다. 
                emit(lastCachedNews())
            }
}

 

onEach 및 map 연산자는 defaultDispatcher를 사용하는 반면 catch 연산자와 소비자는 viewModelScope에서 사용하는 Dispatchers.Main에서 실행된다.

 

데이터 소스 계층이 I / O 작업을 수행하므로 I / O 작업에 최적화 된 디스패처를 사용해야합니다.

class NewsRemoteDataSource(
    ...,
    private val ioDispatcher: CoroutineDispatcher
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        // Executes on the IO dispatcher
        ...
    }
        .flowOn(ioDispatcher)
}

'Coroutine > coroutineFlow' 카테고리의 다른 글

flow의 실행 방법  (0) 2021.05.31
stateFlow sharedFlow  (0) 2021.05.27
Convert callback-based APIs to flows  (0) 2021.05.13
android JetPack Libraries에서 사용해보기.  (0) 2021.05.13
Kotlin flow 기초  (0) 2021.05.13