이 포스트는 안드로이드 공식 Codelab을 기반으로 작성되었습니다. [링크]
이전 포스트를 통해서 안드로이드 코루틴에 대해 공부했으니 이제 예제를 통해서 이해해보자.
얀드로이드 스튜디오에서 상단 [File] -> [New] => [Project from Version Control...] 에 들어가서 URL에 https://github.com/android/codelab-kotlin-coroutines.git 을 입력하여 프로젝트를 클로닝한다.
위 git 프로젝트를 클로닝해오면 하나의 프로젝트에 여러개의 코드랩 프로젝트가 들어있음을 알 수 있다.
우리는 이 중 coroutines-codelab 코드랩을 진행할 것이기 때문에 해당 프로젝트를 선택해서 실행해준다.
그러면 위 이미지와 같이 finished_code와 start 2개의 폴더로 나뉜 프로젝트가 실행된다.
예제를 실행해보면 위처럼 MainActivity가 실행된다.
해당 앱은 화면을 터치하면 일정 시간 딜레이 가 발생한 후 taps 횟수가 증가하고 텍스트가 새로 변경 되는 동작을 수행한다. 이때 새로운 텍스트는 서버에서 네트워크 통신을 통해 받아온다.
프로젝트의 구성은 아래와 같다.
이번 Codelab의 목표는 이 프로젝트의 비동기 처리를 Coroutine을 사용하도록 변경 하는 것이다.
프로젝트에서 코루틴을 사용하기 위해 gradle
파일에 관련 설정을 추가한다.
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
}
UI 관련 동작을 수행하는 코루틴을 구성할때는 UI 스레드(메인 스레드)에서 시작하도록 하는 게 좋다. Dispatchers.Main
으로 설정함으로써 코루틴이 메인 스레드에서 동작하게 할 수 있다.
코루틴이 메인 스레드에서 시간이 오래 걸리는 동작을 수행하더라도 메인 스레드는 멈추지(blocked)않고 중지(suspend)
되어 메인 스레드가 계속 작동하게 만든다. 또한 ViewModel
코루틴 역시 메인 스레드에서 UI를 계속 업데이트 하기 때문에 UI 관련 코루틴은 메인 스레드에서 실행하는 것이 좋다.
또한 메인 스레드에서 시작한 코루틴은 동작 이후 자유롭에 Dispatcher
를 전환할 수 있다. (ex. 코루틴에서 대용량의 JSON 데이터를 파싱할 때 메인 스레드가 아닌 다른 스레드로 전환할 수 있다.)
dependencies {
...
// replace x.x.x with latest version
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:x.x.x"
}
viewModelScope
를 활용할 예정이기 때문에 해당 dependency
를 추가한다. viewModelScope
는 Dispatchers.Main
에서 실행되면서 ViewModel
이 제거되면 같이 취소되기 때문에 유용하다.
before
/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
// TODO: Convert updateTaps to use coroutines
tapCount++
BACKGROUND.submit {
Thread.sleep(1_000)
_taps.postValue("$tapCount taps")
}
}
before 코드는 1초 대기 후 tapCount를 증가시키는 동작을 수행한다. BACKGROUND ExecutorService
를 활용해 백그라운드 스레드를 만들어 수행하고 있다.
after
/**
* Wait one second then display a snackbar.
*/
fun updateTaps() {
// launch a coroutine in viewModelScope
viewModelScope.launch {
tapCount++
// suspend this coroutine for one second
delay(1_000)
// resume in the main dispatcher
// _snackbar.value can be called directly from main thread
_taps.postValue("$tapCount taps")
}
}
after 코드에서 먼저 viewModelScope
에서 코루틴을 launch
메소드를 통해 생성한다. 이 코루틴은 viewModelScope
에서 실행되었기 때문에 메인 스레드에서 동작한다.
또한 viewModelScope
가 만약 취소(cancel)되면 (ex. 액티비티를 중지하는 등 viewModel이 제거되는 경우) viewModelScope
내의 모든 코루틴도 취소(cancel)된다.
tapCount를 증가시킨 뒤 delay
함수를 이용해 1초 딜레이를 줘 작업을 중지(suspend)시킨 뒤 변경된 값을 postValue 함으로써 마무리한다. (delay
함수는 suspend
함수이다.)
테스트 코드 작성법에 대해서는 아직 자세히 다뤄보지 않았기 때문에 다음 기회에 좀 더 제대로 알아보기로 하고 여기서는 가볍게 넘어가도록 하자.
MainViewModelTest.kt
@Test
fun whenMainClicked_updatesTaps() {
subject.onMainViewClicked()
Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("0 taps")
coroutineScope.advanceTimeBy(1000)
Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("1 taps")
}
onMainViewClicked
메소드를 호출함으로써 updateTaps
코루틴이 실행된다. 메소드 호출 직후 텍스트가 "0 taps" 이고 1초가 흐른 뒤 "1 taps"가 되면 코루틴이 정상적으로 작동하고 있는 것을 확인할 수 있다.