본문 바로가기

안드로이드/Kotlin

[Kotlin] Introduction to Coroutines and Channels-5

개요

해당 게시글은 Welcome to Kotlin hands-on (kotlinlang.org)을 번역한 게시글입니다.

 

Showing Progress

일부 레포지토리에 대한 정보는 꽤 빠르게 로딩됨에 불구하고 유저는 모든 데이터가 로딩된 후에 결과 목록을 볼 수 있게 구현하려 한다. 그때까지 로딩 아이콘이 실행되어 진행상황을 보여주지만 현재 상태나 어느 contributors까지 로딩되었는지 등의 정보는 존재하지 않는다. 중간 결과를 일찍 보여주고 모든 contributors 정보는 각 레포지토리 데이터가 로딩된 후에 표시할 수 있다.

 

이런 기능을 구현하기 위해서 콜백으로 UI를 업데이트하기 위한 로직을 추가하여 각 중간 상태마다 호출되도록 해야 한다.

suspend fun loadContributorsProgress(
    service: GitHubService,
    req: RequestData,
    suspend updateResults: (List<User>, completed: Boolean) -> Unit
) {
    // loading the data
    // calling `updateResults` on intermediate states 
}

 

호출 측에서 메인 스레드에서 결과를 갱신하는 콜백을 전달해준다.

launch(Dispatchers.Default) {
    loadContributorsProgress(service, req) { users, completed ->
        withContext(Dispatchers.Main) {
            updateResults(users, startTime, completed)
        }
    }
}

updateResults 파라미터는 suspend로 선언되어있다. 상응하는 람다 인자 내부의 suspend 함수인 withContext를 호출해야 한다. updateResults 콜백은 모든 작업이 끝났는지를 확인하기 위한 Boolean 매개변수도 필요하다.

 

Task

중간 결과를 보여주는 loadContributorsProgress 함수를 구현해라. 여기서는 concurrecy는 고려하지 않는다. (concurrecy는 다음 섹션에서 추가할 예정이다.)

 

중간 contributors 리스트는 각 레포지토리에 의해 로딩된 특정 한 리스트의 유저가 아닌 집계된(aggregated) 상태로 표시해야 된다. 새로운 레포지토리가 로딩될 때마다 contributors의 수는 증가해야 한다.

 

Solution

로딩된 contributors의 리스트는 집계된 상태로 저장되어야 한다. 유저 리스트를 저장하는 allUser라는 변수를 정의할 수 있고 각 레포지토리의 contributors가 로딩될 때마다 이를 갱신한다.

suspend fun loadContributorsProgress(
    service: GitHubService,
    req: RequestData,
    updateResults: suspend (List<User>, completed: Boolean) -> Unit
) {
    val repos = service
        .getOrgRepos(req.org)
        .also { logRepos(req, it) }
        .bodyList()

    var allUsers = emptyList<User>()
    for ((index, repo) in repos.withIndex()) {
        val users = service.getRepoContributors(req.org, repo.name)
            .also { logUsers(repo, it) }
            .bodyList()

        allUsers = (allUsers + users).aggregate()
        updateResults(allUsers, index == repos.lastIndex)
    }
}

 

아래 사진과 같이 각 요청이 완료된 이후 updateResults 콜백이 호출된다.

 

아직 concurrency를 전혀 사용하지 않았고 이 코드는 직렬적이어서 동기화가 필요하지 않다.

아래 사진과 같이 요청을 동시에 보내고 각 레포지토리에 대한 응답을 받은 후 중간 결과를 갱신하려고 한다.

이 솔루션에 어떻게 concurrency를 추가할 수 있을까? Channel이 그에 대한 답이다.