깨알 개념/Kotlin

[Kotlin] Coroutines - ViewModelScope

interfacer_han 2024. 2. 20. 12:08

본 게시글의 Coroutine 개념은 Android 내에서 사용되는 것을 전제로 작성되었다.
 

#1 ViewModel 속 전통적인 방식의 Coroutines

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

class SampleViewModel : ViewModel() {

    private val myJob = Job()
    private val myScope = CoroutineScope(Dispatchers.IO + myJob)

    fun sampleFunction() {
        myScope.launch {
            // 아무 코드
        }
    }

    override fun onCleared() {
        super.onCleared()
        myJob.cancel()
    }
}

위는 ViewModel에서 Coroutine을 사용하는 코드다. ViewModel이 종료되었음에도, ViewModel에서 생성된 Coroutine은 계속 실행될 여지가 있다. 따라서, onCleared() 콜백 함수를 Override해서 Coroutine을 안전하게 종료시켰다.
 
하지만, 이 코드는 상용구 코드(Boilerplate code)의 전형이다. Job 프로퍼티와 onCleared() 함수의 존재는 Project의 크기가 작을 땐 몰라도 커지면 커질수록, ViewModel의 갯수도 많아지면서 골칫덩어리가 된다.
 

#2 lifecycle-viewmodel-ktx 라이브러리

#2-1 개요

 

수명 주기 인식 구성요소로 Kotlin 코루틴 사용  |  Android 개발자  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 수명 주기 인식 구성요소로 Kotlin 코루틴 사용 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Kotlin 코

developer.android.com

다행히, #1의 문제를 해결할 수 있는 라이브러리가 있다. 바로, lifecycle-viewmodel-ktx다. 이 라이브러리의 ViewModelScope를 이용해서 #1 속 상용구 코드의 동작을 암시적으로 수행할 수 있다. 즉, 해당 상용구 코드를 제거해도 무방하게 만들 수 있다.
 

#2-1 androidx.lifecycle.ViewModel.kt 살펴보기 (ViewModelScope와 viewModelScope)

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.lifecycle

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import java.io.Closeable
import kotlin.coroutines.CoroutineContext

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

/**
 * [CoroutineScope] tied to this [ViewModel].
 * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

ViewModelScope는 ViewModel의 수명주기를 참조해, ViewModel이 소멸될 때 알아서 종료되는 CoroutineScope다. #3-1에서처럼 라이브러리를 불러온 순간부터 ViewModelScope는 ViewModel의 프로퍼티로서 자동 생성된다. 해당 프로퍼티의 이름은 첫 글자가 소문자인, viewModelScope다. viewModelScope는 coroutineScope와 비슷하게 사용하면 된다.
 

#3 ViewModelScope 사용하기

#3-1 build.gradle.kts (Module)에서 라이브러리 다운로드

plugins {
    ...
}

android {
    ...
}

dependencies {
    ...

    // ViewModelScope
    val lifecycle_version = "2.6.2"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
    
    ...
}

여기에 있는 구문을 복사하여 모듈 수준 build.gradle에 붙여넣는다. 이제부터 ViewModelScope(viewModelScope)를 사용할 수 있다.
 

#3-2 ViewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class sampleViewModel : ViewModel() {
    fun sampleFunction() {
        viewModelScope.launch {
            // 아무 코드
        }
    }
}

#1의 코드를 수정한다.
 

#4 요약

ViewModel에서의 CoroutineScope 생성 및 Cancellation을 암시화한다.