04
10

# LiveData?

LiveData๋Š” Lifecycle์„ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” Observable ํด๋ž˜์Šค๋‹ค. ์ด๋•Œ lifecycle์€ ์•ฑ ์ปดํฌ๋„ŒํŠธ์˜ ์ˆ˜๋ช…์ฃผ๊ธฐ๋ฅผ ๋งํ•œ๋‹ค. 
์‚ฌ์šฉ์„ ์œ„ํ•ด์„œ๋Š” lifecycle dependency๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•œ๋‹ค.

implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
class MyViewModel : ViewModel() {
    private val _name = MutableLiveData<String>()
    val name: LiveData<String> = _name
    
    fun setName(newName: String) {
        _name.value = newName
    }

    fun setNameWithSetValue(newName: String) {
        _name.setValue(newName)
    }

    fun setNameWithPostValue(newName: String) {
        _name.postValue(newName)
    }
}

_name์€ name์˜ backing property๋กœ ๋‚ด๋ถ€์—์„œ๋Š” Mutableํ•˜๊ณ , ์™ธ๋ถ€์—์„œ๋Š” ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋ˆ  ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

  1. ์บก์Šํ™”
  2. ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ flow

๋‚ด๋ถ€์—์„œ๋Š” ์ˆ˜์ •๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ์ง€๋งŒ ์™ธ๋ถ€์—์„œ๋Š” ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๊ณ , layer๋ฅผ ๋‚˜๋ˆ ์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด UI ๋ ˆ์ด์–ด์—์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ •์„ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„ํ‚คํ…์ฒ˜์ ์ธ ๊ด€์ ์œผ๋กœ ์ ํ•ฉํ•˜๋‹ค.
ViewModel ์•ˆ์—์„œ _name ์ˆ˜์ •ํ•˜๊ณ , ๊ทธ ์™ธ์—์„œ name์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๋ฐฉ์‹์€ ์„ธ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. ๊ทธ๋ƒฅ value์— ๋„ฃ๋Š” ๋ฒ•, setValue, postValue๋‹ค.
 

# _name.value = newName

livedata๋Š” property๋ฅผ ๊ฐ–๊ณ ์žˆ๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฑด ์ง์ ‘ ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. Observer๊ฐ€ ์ฆ‰์‹œ ๋ณ€๊ฒฝ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋ฉ”์ธ์Šค๋ ˆ๋“œ(UI ์Šค๋ ˆ๋“œ), ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ์ƒ๊ด€์—†์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์ด๋ผ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋Š” ๋ฐฉ์‹์ด๋‹ค.(thread safe ํ•˜์ง€ ์•Š๋‹ค๋Š” ์˜๋ฏธ) setValue๋ž‘ ๋™์ผํ•˜๋‹ค
 

# _name.setValue(newName)

๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์ด๋‹ค. ๋”ฐ๋ผ์„œ thread safeํ•˜๊ณ , Observer์—๊ฒŒ ๋ณ€๊ฒฝ์„ ์ฆ‰์‹œ ์•Œ๋ฆฐ๋‹ค.(๋‚ด๋ถ€์— ์žˆ๋Š” onChanged ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹) ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ setValue๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด, ์˜ˆ์™ธ(CalledFromWrongThreadException)๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. 
 

# _name.postValue(newName)

postValue๋„ thread safeํ•œ ๊ฐ’ ์—…๋ฐ์ดํŠธ ๋ฐฉ์‹์ธ๋ฐ, ๋ฉ”์ธ ์Šค๋ ˆ๋“œ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ์ƒ๊ด€์—†์ด ์•ˆ์ „ํ•œ ํ˜ธ์ถœ ๋ฐฉ์‹์ด๋‹ค. ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์˜ ๋‹ค์Œ ์ฒดํฌ ํฌ์ธํŠธ์— ์„ค์ •ํ•˜๊ณ , Observer์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค. setValue๋ž‘ ์ฐจ์ด์ ์€ ์‹ค์ œ ๊ฐ’ ์„ค์ • ํƒ€์ด๋ฐ์ด ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ์ด๊ณ , ๋ฉ”์‹œ์ง€ ํ์— ๋“ค์–ด์™€์žˆ๋Š” ๋ฉ”์‹œ์ง€๋“ค์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ด๋•Œ ๋ฉ”์‹œ์ง€ ํ์— ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ์ด ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.
setValue๋Š” ๋ฉ”์ธ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ๋˜๊ธฐ๋•Œ๋ฌธ์— ๊ฐ’์„ ์ฆ‰์‹œ ์„ค์ •ํ•ด์„œ ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์‹œ์ง€ ํ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ€๊ธฐ์‹œํ‚ค์ง€ ์•Š๋Š”๋ฐ, postValue๋Š” ๋ฉ”์ธ์Šค๋ ˆ๋“œ์˜ ๋ฉ”์‹œ์ง€ ํ์— ๋ฉ”์‹œ์ง€(์ƒˆ๋กœ๋“ค์–ด์˜จ ๊ฐ’์— ๋Œ€ํ•œ)๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ๋ฉ”์‹œ์ง€ ํ ํ™•์ธ ์‹œ์ ์— Observer์—๊ฒŒ ์—…๋ฐ์ดํŠธ๋ฅผ ์•Œ๋ฆฐ๋‹ค.
 
์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๋ฉด, ๋Œ€ํ‘œ์ ์ธ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์œผ๋กœ DB์ž‘์—…์ด๋‚˜, ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์žˆ๋‹ค. ์ด๋Ÿฐ ์ž‘์—…์€ ๋ฉ”์ธ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ํ•˜๋ฉด ์ž‘์—… ์ˆ˜ํ–‰์ค‘์— ๋ฉ”์ธ์Šค๋ ˆ๋“œ์˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฉˆ์ถ”๊ฒŒ ๋˜๋ฏ€๋กœ ์‹œ์Šคํ…œ์ด ์—๋Ÿฌ๋ฅผ ๋ฑ‰๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผํ•˜๋Š”๋ฐ ์ด๊ฑธ setValue๋กœ ํ•˜๋ฉด ์ž‘์—…์ด ์•ˆ๋๋‚ฌ์„ ๋•Œ ๋ฉ”์ธ์Šค๋ ˆ๋“œ์— ์ ‘๊ทผํ•ด์„œ null๊ฐ’์„ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋  ์ˆ˜ ์žˆ๋‹ค.
DB๋ฅผ ์„ž์–ด์„œ ๊ตฌ์„ฑํ•ด๋ณธ ์ฝ”๋“œ๋ธ”๋Ÿญ์ด๋‹ค.

class MyViewModel : ViewModel() {
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading

    fun loadData() {
        viewModelScope.launch {
            _isLoading.setValue(true) // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ setValue ์‚ฌ์šฉ
        	withContext(Dispatchers.IO){
            	val loadedData = loadFromDatabase() // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…
            	_data.postValue(loadedData) // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ postValue ์‚ฌ์šฉ
            }            
            _isLoading.postValue(false) // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ postValue ์‚ฌ์šฉ
        }
    }
}

viewModelScope๊ฐ€ ๋ฉ”์ธ์Šค๋ ˆ๋“œ์— ๋ฌถ์—ฌ์žˆ๊ธฐ ๋•Œ๋ฌธ์— withContext๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์•ผ๋œ๋‹ค.
withContext๋Š” ํ˜„์žฌ ์‹คํ–‰์ค‘์ด๋˜ ์ฝ”๋ฃจํ‹ด์„ suspend์‹œํ‚ค๊ณ  dispatcher๋ฅผ ๋ฐ”๊ฟ”์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๊ทธ๊ฒŒ ๋๋‚˜์•ผ ๋งˆ์ง€๋ง‰ ์ค„์— ์žˆ๋Š” _isLoading๊ฐ’์ด false๋กœ ๋ฐ”๋€๋‹ค.

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)
        )
    }

withContext๋กœ ์ฒ˜๋ฆฌํ•˜์ง€์•Š์œผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

์ด๊ฑธ ๋ทฐ์—์„œ ๋ฐ›์„ ๋•Œ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ํ•˜๋ฉด ๋œ๋‹ค.

viewModel.name.observe(this@MyActivity){ name->
	// name์„ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•  ์ฝ”๋“œ
}

์ด๋•Œ this@MyActivity๊ฐ€ ํ•˜๋Š” ์ผ์€ lifecycle์„ name์ด ์•Œ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. MyActivity์˜ ์ˆ˜๋ช…์ฃผ๊ธฐ ์•ˆ์—์„œ, name์— ๋Œ€ํ•œ LiveData๊ฐ’์ด ์—…๋ฐ์ดํŠธ ๋˜๋ฉด, ๋žŒ๋‹ค๋กœ ์ง€์ •ํ•ด๋‘” Observer๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋กœ ์•ž์—์„œ setValue, postValue, name.value = "" ๊ฐ€ ์ผ์–ด๋‚˜๋ฉด ์—ฌ๊ธฐ์„œ observeํ•ด์„œ ๋žŒ๋‹ค ํ•จ์ˆ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋œ๋‹ค. MyActivity๊ฐ€ ์—ฌ๊ธฐ์„œ observeํ•  ๋ผ์ดํ”„์‚ฌ์ดํด์˜ ์ฃผ์ธ์ด๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ STARTED(onStart์™€ onPause ์‚ฌ์ด), RESUMED(onResume์ด ๋ถˆ๋ฆฐ์ƒํƒœ) ์ƒํƒœ์ผ ๋•Œ๋งŒ observer(์—ฌ๊ธฐ์„œ๋Š” ๋žŒ๋‹คํ•จ์ˆ˜)์— ๊ฐ’์„ ์ „๋‹ฌํ•œ๋‹ค. DESTROYED์ผ๋•Œ๋Š” ๋‹น์—ฐํžˆ observer์—๊ฒŒ ๊ฐ’์„ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค. onPause๊ฐ€ ๋ถˆ๋ ค์žˆ๋Š” ์ƒํƒœ๋„ ํ™œ์„ฑ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— observer์—๊ฒŒ ์ ‘๊ทผํ•˜์ง€์•Š๋Š”๋‹ค.
 
์ด๋Ÿฐ ๋ฐฉ์‹์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ๋งŒ ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆํ•„์š”ํ•œ ์—…๋ฐ์ดํŠธ๋‚˜, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ๋‹ค. Fragment๋ผ๋ฉด viewLifecycleOwner๋ฅผ lifecycleOwner๋กœ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ํ™œ์„ฑ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๋‚ด๋ ค๊ฐ„๋‹ค. lifecycleOwner๊ฐ€ ์—†๋Š” observer๋ฅผ observeForever - removeObserver๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋Ÿฌ๋ฉด ์ˆ˜๋™์œผ๋กœ ์ œ๊ฑฐํ•ด์ฃผ๊ธฐ ์ „๊นŒ์ง€ ํ•ญ์ƒ ํ™œ์„ฑ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ฆฌ์— ์ฃผ์˜ํ•ด์•ผ๋œ๋‹ค.
 
๋Œ€๋‹ค์ˆ˜์˜ ๊ฒฝ์šฐ onCreate(์ปดํฌ๋„ŒํŠธ), onViewCreated(Fragment๋ผ๋ฉด)์—์„œ observer๋ฅผ ์„ค์ •ํ•ด์ฃผ๋Š”๊ฒŒ ์ข‹๋‹ค.

# ์™œ ์“ธ๊นŒ?

์ด ๋ถ€๋ถ„์€ ๊ณต์‹๋ฌธ์„œ์— ์ ํ˜€์žˆ๋Š” ๊ฑธ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์™”๋‹ค. ๊ธ€์—์„œ ์–˜๊ธฐ ์•ˆํ•œ๋ถ€๋ถ„๋งŒ ๋‚ด์šฉ์„ ๋‚จ๊ฒจ๋†จ์œผ๋‹ˆ ํ•„์š”ํ•˜๋ฉด ๋งํฌ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•˜์ž

  • UI์™€ ๋ฐ์ดํ„ฐ ์ƒํƒœ์˜ ์ผ์น˜ ๋ณด์žฅ
    • LiveData๋Š” ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ Observer๊ฐ์ฒด์— ์•Œ๋ฆฐ๋‹ค(์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๋žŒ๋‹ค๋กœ ์ •์˜ํ–ˆ๋‹ค). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ฑ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค Observer๊ฐ€ ๋Œ€์‹  UI๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฏ€๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ์—…๋ฐ์ดํŠธํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์—†์Œ
  • ์ค‘์ง€๋œ ํ™œ๋™์œผ๋กœ ์ธํ•œ ๋น„์ •์ƒ ์ข…๋ฃŒ ์—†์Œ
  • ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ๋” ์ด์ƒ ์ˆ˜๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Œ
  • ์ตœ์‹  ๋ฐ์ดํ„ฐ ์œ ์ง€
    • ์ˆ˜๋ช… ์ฃผ๊ธฐ๊ฐ€ ๋น„ํ™œ์„ฑํ™”(onPause)๋˜๋ฉด ๋‹ค์‹œ ํ™œ์„ฑํ™”(onResume)๋  ๋•Œ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์žˆ์—ˆ๋˜ ํ™œ๋™์€ ํฌ๊ทธ๋ผ์šด๋“œ๋กœ ๋Œ์•„์˜จ ์งํ›„ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค.
  • ์ ์ ˆํ•œ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ
    • ๊ธฐ๊ธฐ ํšŒ์ „๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด ํ™œ๋™ ๋˜๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑ๋˜๋ฉด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ๋ฐ›๊ฒŒ ๋œ๋‹ค.
  • ๋ฆฌ์†Œ์Šค ๊ณต์œ 
    • ์•ฑ์—์„œ ์‹œ์Šคํ…œ ์„œ๋น„์Šค๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋Š” LiveData๊ฐ์ฒด๋ฅผ ํ™•์žฅํ•˜์—ฌ ์‹œ์Šคํ…œ ์„œ๋น„์Šค๋ฅผ ๋ž˜ํ•‘ํ•  ์ˆ˜ ์žˆ๋‹ค. LiveData๊ฐ์ฒด๊ฐ€ ์‹œ์Šคํ…œ ์„œ๋น„์Šค์— ํ•œ ๋ฒˆ ์—ฐ๊ฒฐ๋˜๋ฉด ๋ฆฌ์†Œ์Šค๊ฐ€ ํ•„์š”ํ•œ ๋ชจ๋“  Observer๊ฐ€ ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค. 

LivaDataํ™•์žฅํ•˜๊ธฐ

LiveData ๊ฐœ์š”  |  Android ๊ฐœ๋ฐœ์ž  |  Android Developers

LiveData๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ์ธ์‹ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

developer.android.com

 

Why does viewModelScope.launch run on the main thread by default

While I was learning coroutines and how to properly use them in an android app I found something I was surprised about. When launching a coroutine using viewModelScope.launch { } and setting a

stackoverflow.com

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”! ๋กœ๊ทธ์ธ ์•ˆํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค ^_^
๋ฐ˜์‘ํ˜•
COMMENT