๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Android

[Kotlin] Coroutines - LiveData Builder

interfacer_han 2024. 2. 22. 15:23

๋ณธ ๊ฒŒ์‹œ๊ธ€์˜ Coroutine ๊ฐœ๋…์€ Android ๋‚ด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์„ ์ „์ œ๋กœ ์ž‘์„ฑ๋˜์—ˆ๋‹ค.
 

#1 Coroutine์ด ์“ฐ์ธ LiveData ์˜ˆ์ œ - ์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•

#1์€ LiveData ๊ฐ’ ํ• ๋‹น์— Coroutines๊ฐ€ ์“ฐ์ธ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ๋‹ค.
 

#1-1 build.gradle.kts (Module)

plugins {
    ...
}

android {
    ...
}

dependencies {
    ...

    //  Coroutines
    val coroutinesVersion = "1.7.3"
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")

    val lifecycleVersion = "2.6.2"

    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
    
    // LiveData
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
}

Coroutines, ViewModel, LiveData ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
 

#1-2 User.kt ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค

// package com.example.livedatabuilder.model

data class User(val id: Int, val name: String)

2๊ฐœ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„ data class๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.
 

#1-3 UserRepository.kt

// package com.example.livedatabuilder.model

import kotlinx.coroutines.delay

class UserRepository {

    suspend fun getUsers(): List<User> {
        delay(8000)
        val users: List<User> = listOf(
            User(1, "Park Bom"),
            User(2, "Park Sandara"),
            User(3, "CL"),
            User(4, "Gong Minji")
        )
        return users
    }
}

Repository ์—ญํ• ์„ ํ•  ํด๋ž˜์Šค๋„ ๋งŒ๋“ ๋‹ค. ์ด ํด๋ž˜์Šค์˜ getUsers() ๋ฉ”์†Œ๋“œ๋Š” ๋ฐฉ๊ธˆ ๋งŒ๋“ค์—ˆ๋˜ data class ์ธ์Šคํ„ด์Šค์˜ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. Repository์—์„œ ๊ฐ’์„ ๊บผ๋‚ด์˜ค๋Š” ๋ฐ์— ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์„ delay() ํ•จ์ˆ˜๋กœ ํ‘œํ˜„ํ–ˆ๋‹ค.
 

#1-4 MainActivityViewModel.kt

// package com.example.livedatabuilder

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.livedatabuilder.model.User
import com.example.livedatabuilder.model.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MainActivityViewModel : ViewModel() {

    private var usersRepository = UserRepository()
    var users: MutableLiveData<List<User>?> = MutableLiveData()

    fun getUsers() {
        viewModelScope.launch {
            var result: List<User>? = null
            withContext(Dispatchers.IO) {
                result = usersRepository.getUsers()
            }
            users.value = result
        }
    }
}

ViewModel๊ณผ ๊ทธ ํ”„๋กœํผํ‹ฐ๋กœ LiveData๋ฅผ ๋งŒ๋“ ๋‹ค. LiveData์— ๊ฐ’์„ ํ• ๋‹นํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ, viewModelScope์—์„œ Repository.getUsers()๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. Dispatchers.IO ์Šค๋ ˆ๋“œ(์ด ๋งํฌ์˜ #3-2 ์ฐธ์กฐ)๋Š” ์ฃผ๋กœ ์ž…/์ถœ๋ ฅ์„ ๋‹ด๋‹นํ•˜๋Š” ์Šค๋ ˆ๋“œ๋‹ค. Repository์˜ ์—ญํ•  ๋˜ํ•œ ๋ฐ์ดํ„ฐ ์ž…/์ถœ๋ ฅ์ด๋ฏ€๋กœ withContext() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด์„œ ์Šค๋ ˆ๋“œ๋ฅผ ์ž„์‹œ ์ „ํ™˜ํ•ด getUsers()๋ฅผ ์ˆ˜ํ–‰์‹œ์ผฐ๋‹ค.
 

#1-5 MainActivity.kt

// package com.example.livedatabuilder

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {
    private lateinit var mainActivityViewModel: MainActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

        mainActivityViewModel.getUsers()
        mainActivityViewModel.users.observe(this, Observer { myUsers ->
            myUsers?.forEach {
                Log.i("interfacer_han", "name is ${it.name}")
            }
        })
    }
}

/* Log ์ถœ๋ ฅ ๊ฒฐ๊ณผ
name is Park Bom
name is Park Sandara
name is CL
name is Gong Minji
*/

Activity์—์„œ LiveData์— ๊ฐ’์„ ํ• ๋‹นํ•˜๋Š” ์ž‘์—… ํ›„ ํ•ด๋‹น LiveData๋ฅผ observeํ•œ๋‹ค. ์ด์ œ 8์ดˆ ํ›„์— Coroutines ์ฝ”๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด Log ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฐ ๊ฒƒ์ด๋‹ค.
 

#2 Coroutine์ด ์“ฐ์ธ LiveData ์˜ˆ์ œ - LiveData Builder ์ด์šฉ

#2-1 ๊ฐœ์š”

 

์ˆ˜๋ช… ์ฃผ๊ธฐ ์ธ์‹ ๊ตฌ์„ฑ์š”์†Œ๋กœ Kotlin ์ฝ”๋ฃจํ‹ด ์‚ฌ์šฉ  |  Android ๊ฐœ๋ฐœ์ž  |  Android Developers

์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜๋ช… ์ฃผ๊ธฐ ์ธ์‹ ๊ตฌ์„ฑ์š”์†Œ๋กœ Kotlin ์ฝ”๋ฃจํ‹ด ์‚ฌ์šฉ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. Kotlin ์ฝ”

developer.android.com

#1์˜ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์™€ ์ฝ”๋“œ๋Ÿ‰ ๋‘˜ ๋‹ค ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, LiveData Builder๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. lifecycle-livedata-ktx๊ฐ€ 2.4.0 ๋ฒ„์ „ ์ด์ƒ์ด๋ฉด LiveData Builder๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. #1์—์„œ ์ด๋ฏธ ํ•ด๋‹น ๋ฒ„์ „ ์ด์ƒ์˜ lifecycle-livedata-ktx๋ฅผ ๋ชจ๋“ˆ ์ˆ˜์ค€ build.gradle.kts์—์„œ ์ถ”๊ฐ€ํ–ˆ์œผ๋ฏ€๋กœ build.gradle.kts๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
 

#2-2 MainActivityViewModel.kt์—์„œ LiveData์— LiveDataBuilder ์‚ฌ์šฉ

// package com.example.livedatabuilder

import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import com.example.livedatabuilder.model.UserRepository
import kotlinx.coroutines.Dispatchers

class MainActivityViewModel : ViewModel() {

    private var usersRepository = UserRepository()
    
    var users = liveData(Dispatchers.IO) {
        val result = usersRepository.getUsers()
        emit(result) // LiveData์— ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋ฐœํ–‰ํ•˜๊ณ  ์ด๋ฅผ ๊ตฌ๋…(LiveData.observe())ํ•˜๊ณ  ์žˆ๋Š” ๊ด€์ฐฐ์ž(Observer)๋“ค์—๊ฒŒ ์•Œ๋ฆผ
    }

/*
    var users: MutableLiveData<List<User>?> = MutableLiveData()

    fun getUsers() {
        viewModelScope.launch {
            var result: List<User>? = null
            withContext(Dispatchers.IO) {
                result = usersRepository.getUsers()
            }
            users.value = result
        }
    }
*/
}

LiveData๋Š” LiveData๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๋Š” LifecycleOwner ์ธํ„ฐํŽ˜์ด์Šค์— ์˜ํ–ฅ์„ ๋ฐ›์œผ๋ฉฐ ์ž‘๋™ํ•œ๋‹ค (์ด ๋งํฌ์˜ #2-3 ์ฐธ์กฐ). LifecycleOwner์˜ ์ƒํƒœ์— ๋”ฐ๋ผ LiveData ๊ฐ’์˜ '๊ฐฑ์‹  ํ›„ ํ›„์† ์ž‘์—…'์„ ํ•  ์ง€ ์•ˆํ•  ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ, #1-4์—์„œ LiveData๋Š” viewModelScope ์ฆ‰, ViewModel์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ฅด๋Š” ์ฝ”๋ฃจํ‹ด ์˜์—ญ์— ์˜ํ•ด ์‹คํ–‰๋œ๋‹ค. ๋ฐ˜๋ฉด, LiveData Builder๋Š” LiveData๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ์‹คํ–‰์„ ์‹œ์ž‘ํ•˜๊ณ , ๋น„ํ™œ์„ฑํ™”๋˜๋ฉด ์ž๋™์œผ๋กœ ์ทจ์†Œ๋˜๋Š” ์ฝ”๋ฃจํ‹ด ์˜์—ญ์„ ๋งŒ๋“ ๋‹ค. ๋”ฐ๋ผ์„œ LiveData๊ฐ’์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋ฐ์— ๊ด€๋ จ๋œ Coroutines์˜ ๋™์ž‘/์ทจ์†Œ๋ฅผ ViewModel์ด ์•„๋‹Œ, LiveData๊ฐ€ ์ฃผ์ฒด์ ์œผ๋กœ ํŒ๋‹จํ•˜๊ฒŒ ๋œ๋‹ค. ์ด๋Š” ์˜ค๋™์ž‘์„ ์˜ˆ๋ฐฉํ•˜๋ฉด์„œ ๋™์‹œ์— ์ปดํ“จํŒ… ์ž์› ์ ˆ์•ฝ์œผ๋กœ๋„ ์ด์–ด์ง„๋‹ค.
 
์ถ”๊ฐ€๋กœ, ์ฝ”๋“œ ๋˜ํ•œ ๋” ๊ฐ„๊ฒฐํ•ด์กŒ๋‹ค. ์ด ์žฅ์ ์€ ViewModel์—์„œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ Activity์—์„œ๋„ ๊ทธ๋ ‡๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.
 

#2-3 MainActivity.kt์—์„œ getUsers() ์‚ญ์ œ

// package com.example.livedatabuilder

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {

    private lateinit var mainActivityViewModel: MainActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

        // mainActivityViewModel.getUsers()
        mainActivityViewModel.users.observe(this, Observer { myUsers ->
            myUsers?.forEach {
                Log.i("interfacer_han", "name is ${it.name}")
            }
        })
    }
}

/* Log ์ถœ๋ ฅ ๊ฒฐ๊ณผ
name is Park Bom
name is Park Sandara
name is CL
name is Gong Minji
*/

#1-5์—์„œ LiveData๋ฅผ observeํ•˜๊ธฐ ์ „์— ์ดˆ๊นƒ๊ฐ’์„ ํ• ๋‹นํ•˜๋Š” ์ž‘์—…์„ ํ•  ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๋‹ค. ์™œ๋ƒํ•˜๋ฉด LiveData๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด ์ฆ‰, LiveData๊ฐ€ ๋‹ด๊ธด Activity(์—ฌ๊ธฐ์„  MainActivity)์˜ LifecycleOwner์˜ ์ƒํƒœ๊ฐ€ ํ™œ์„ฑํ™” ์ƒํƒœ๋ฉด, LiveData Builder๊ฐ€ ์•”์‹œ์ ์œผ๋กœ ์ˆ˜ํ–‰๋  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

#3 ์ฃผ์˜์ 

"๊ทธ๋ ‡๋‹ค๋ฉด, ViewModelScope.launch { ... }๋ฅผ liveData { ... }๋กœ ๋ชฝ๋•… ๋‹ค ๋ฐ”๊ฟ”๋ฒ„๋ฆฌ๋ฉด ์ข‹์€ ๊ฒŒ ์•„๋‹Œ๊ฐ€?"๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค๋ฉด LiveData Builder๋ฅผ ์ž˜๋ชป ์ดํ•ดํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. liveData { ... }๋ฅผ ๋” ์šฐ์›”ํ•œ, ์—…๊ทธ๋ ˆ์ด๋“œ ๋ฒ„์ „์˜ ViewModelScope๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์•ˆ ๋œ๋‹ค. ์ด ๋‘˜์€ ์„œ๋กœ ๊ทธ ์šฉ๋„๊ฐ€ ๋‹ค๋ฅด๋‹ค.

 

ViewModelScope๋Š” ์ด ๊ฒŒ์‹œ๊ธ€์—์„œ ๋ณด๋“ฏ CoroutineScope๋ฅผ ์•”์‹œ์ ์œผ๋กœ ViewModel์˜ ์ƒ๋ช…์ฃผ๊ธฐ์— ์ข…์†๋˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์— ๋ชฉ์ ์„ ๋‘”๋‹ค. ๋ฐ˜๋ฉด, LiveData Builder๋Š” ์ดˆ๊ธฐํ™”์— Coroutine์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์˜ LiveData๋ฅผ ๊ฐ„ํŽธํ•˜๊ณ  ๋ฉ”๋ชจ๋ฆฌํšจ์œจ์ ์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด๋‹ค. ๋ณธ ๊ฒŒ์‹œ๊ธ€์€ ์ดˆ๊ธฐํ™”์— Coroutine์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์˜ LiveData๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ์— ํ•œ์ •ํ•˜์—ฌ ViewModelScope ์™ธ์— ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ฃผ๋Š” ๊ธ€์ด์ง€, ViewModelScope๋ฅผ LiveData Builder๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ณด์ด๋Š” ๊ธ€์ด ์•„๋‹ˆ๋‹ค. 

 

๋ณธ ๊ฒŒ์‹œ๊ธ€์— ์“ฐ์ธ LiveData Builder๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋ทฐ๋ชจ๋ธ(#2-2)์˜ emit()์ด, ์•กํ‹ฐ๋น„ํ‹ฐ(#2-3)์˜ mainActivityViewModel.getUsers()๋ฅผ ๋Œ€์ฒดํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๊ณ , ViewModelScope๋ณด๋‹ค ๋ฉ”๋ชจ๋ฆฌ์ ์œผ๋กœ ์ด์ ์ด ์žˆ๋Š” CoroutineScope์ธ liveData { ... }๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด๊ฒŒ ์ „๋ถ€๋‹ค. ์ด ์—ญํ• ์„ ๋„˜์–ด liveData { ... } ์•ˆ์— ํ•ด๋‹น LiveData์˜ ์ดˆ๊ธฐํ™” ์™ธ ์–ด๋–ค ๋‹ค๋ฅธ ์ฝ”๋“œ๋ฅผ ๋„ฃ์œผ๋ฉด ์ปดํŒŒ์ผ์€ ๋ ์ง€ ๋ชฐ๋ผ๋„ ์—๋Ÿฌ๊ฐ€ ๋‚  ์—ฌ์ง€๊ฐ€ ์ƒ๊ธฐ๊ณ  ๋งŒ๋‹ค. ์•„๋‹ˆ, ์˜คํžˆ๋ ค ์ปดํŒŒ์ผ์€ ๋˜๋‹ˆ๊นŒ ์—๋Ÿฌ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ๋ฌธ์ œ์ ์„ ์ฐพ๊ธฐ๊ฐ€ ๋” ์–ด๋ ต๋‹ค. ์ •๋ฆฌํ•˜์ž๋ฉด, ์ดˆ๊ธฐํ™”์— Courotine์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์˜ LiveData ์™ธ์—๋Š” ์ „๋ถ€ ViewModelScope๋ฅผ ์“ด๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ž.

 

#4 ์š”์•ฝ

์ดˆ๊ธฐํ™”์— Coroutine์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์˜ LiveData๋ฅผ ๊ฐ„ํŽธํ•˜๊ณ  ๋ฉ”๋ชจ๋ฆฌํšจ์œจ์ ์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.

#5 ์™„์„ฑ๋œ ์•ฑ

 

android-practice/coroutines/LiveDataBuilder at master ยท Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com