[Kotlin] Coroutine Flow를 사용하여 Network 연결

김민주·2022년 8월 3일
1

1. Flow란?

  • 여러 값을 순차적으로 내보냄
  • Coroutine(코루틴) 기반으로 빌드되어 여러 값 제공 가능
    -> 코루틴 내에서 실행해야함
  • 비동기적으로 생성하고 사용
    -> main thread 차단하지 않고 다음값 생성할 네트워크 요청 가능

2. 데이터 스트림

  • 생산자(Producer)
    • stream에 추가되는 데이터 생산하는 역할 (비동기 가능)
    • 대표적으로 DataSource가 UI data Producer
    • emit등을 이용해 flow builder 생성
    • default로 수집하는 코루틴의 CoroutineContext에서 실행
    • CoroutineContext flowOn modifier 사용하여 upstream flow 변경 가능
  • 중개자(Intermediary)
    • stream에서 내보내는 값 수정하거나 stream 자체 생산하는 역할
    • 대표적으로 Repository가 Intermediary
    • 중간 연산자 map, filter 등 사용
    • (선택사항)
    • CoroutineContext flowOn modifier 사용하여 upstream flow 변경 가능
  • 소비자(Consumer)
    • stream의 값 사용하는 역할
    • 대표적으로 UI가 최종적으로 데이터를 표시하는 Consumer
    • 주로 ViewModel에서 collect 연산자 사용하여 데이터 수집

3. Flow를 활용한 네트워크 연결 코드

- Api

interface NewsApi {
	@GET("/api/news")
    suspend fun fetchLatestNews(): List<ArticleHeadline>
}

- DataSource

interface NewsRemoteDataSource {
	suspend fun fetchLatestNews(): Flow<List<ArticleHeadline>>
}

- DataSourceImpl

class NewsRemoteDataSourceImpl(
    private val newsApi: NewsApi,
    private val refreshIntervalMs: Long = 5000,
    private val ioDispatcher: CoroutineDispatcher
) : NewsRemoteDataSource {
override fun fetchLatestNews(): Flow<List<ArticleHeadline>> = flow {
        while(true) {
            val latestNews = newsApi.fetchLatestNews()
            emit(latestNews) // 결과 flow로 emit
            delay(refreshIntervalMs) // 반복 요청 주기
        }
    }
    // data layer에서 I/O 작업 수행하므로
    .flowOn(ioDispatcher)
}

- Repository

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource,
    private val userData: UserData,
    private val defaultDispatcher: CoroutineDispatcher
) {
    val favoriteLatestNews: Flow<List<ArticleHeadline>> =
        newsRemoteDataSource.fetchLatestNews()
            .map { news -> 
                news.filter { userData.isFavoriteTopic(it) }
            }
            .onEach { news -> 
                saveInCache(news)
            }
            // flowOn 위쪽 stream은 defaultDispatcher에서 실행
            .flowOn(defaultDispatcher)
            // flowOn 아래쪽 stream은 consumer's context에서 실행
            .catch { exception -> // Executes in the consumer's context
                emit(lastCachedNews())
            }
}

- ViewModel

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

    init {
        viewModelScope.launch {
        // 데이터 수집 시작
            newsRepository.favoriteLatestNews.collect { favoriteNews -> 
                // 이제 view를 위한 작업 진행
            }
        }
    }
}

참고자료

https://developer.android.com/kotlin/flow
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/

profile
즐거운 개발자 김민주입니다🙂

0개의 댓글