๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/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์„ ์•”์‹œํ™”ํ•œ๋‹ค.