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

[Android] LiveData - ๊ธฐ์ดˆ

interfacer_han 2024. 1. 16. 14:34

#1 ๊ฐœ์š”

#1-1 LiveData

 

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

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

developer.android.com

LiveData. ๋ฌธ์„œ์ ์ธ ์ •์˜๋Š” Data์˜ ๋ณ€๊ฒฝ์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ํ™€๋” ํด๋ž˜์Šค๊ณ , ๋ง ๊ทธ๋Œ€๋กœ ํ•ด์„ํ•˜๋ฉด ์‚ด์•„์žˆ๋Š”(live) ๋ฐ์ดํ„ฐ(data)๋‹ค. Activity, Fragment, Service์— ์žˆ๋Š” Data, ์˜ˆ๋ฅผ ๋“ค์–ด ์–ด๋–ค ๋ณ€์ˆ˜ A๊ฐ€ ์žˆ๊ณ  ๊ทธ A๋ฅผ ์–ด๋–ค TextView์— ํ‘œ์‹œํ•ด๋†“์€ ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด ๋ณด์ž. A์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด, TextView์— ๋ณ€๊ฒฝ๋œ A๊ฐ’์„ ๋‹ค์‹œ ํ• ๋‹นํ•ด์•ผํ•œ๋‹ค. LiveData๋Š” ๊ทธ ๊ท€์ฐฎ์€ ํ• ๋‹น ๊ณผ์ •์„ ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค. LiveData๋Š” ๋ณ€๊ฒฝ๋œ A์˜ ๊ฐ’์„ ํ™”๋ฉด(View)์— ์•”๋ฌต์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ๋‹ค. ์ดˆ๊ธฐ ์„ค์ •๋งŒ ๋งˆ์ณ๋†“์œผ๋ฉด ๊ทธ ์ดํ›„๋กœ๋Š” 'binding.text = getA().toString()'์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค๋Š” ์–˜๊ธฐ๋‹ค. ์•Œ์•„์„œ ๋ณ€๊ฒฝ๋œ ์ž์‹ ์˜ ๊ฐ’์„ View์— ๊ฐฑ์‹ ํ•œ๋‹ค๋‹ˆ, ๋ง ๊ทธ๋Œ€๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ด์•„์žˆ๋‹ค๊ณ  ํ‘œํ˜„ํ•  ๋งŒํ•˜๋‹ค.
 

#1-2 LiveData๋ฅผ ์ ‘๋ชฉ์‹œ์ผœ๋ณผ ์•ฑ

 

[Android] View Model - ๊ธฐ์ดˆ

#1 View Model์˜ ํ•„์š”์„ฑ#1-1 ์˜ˆ์ œ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด TextView์˜ text๊ฐ€ 1์”ฉ ์ฆ๊ฐ€ํ•˜๋Š” ์˜ˆ์‹œ ์•ฑ์ด๋‹ค. MainActivity.kt ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. // package com.example.viewmodelbasics import androidx.appcompat.app.AppCompatActivity import andro

kenel.tistory.com

์ด์ œ, ์œ„ ๊ฒŒ์‹œ๊ธ€์˜ '#3 ์™„์„ฑ๋œ ์•ฑ'์ด LiveData๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ์ˆ˜์ •ํ•ด๋ณธ๋‹ค. ์•„๋ž˜์— ์žˆ๋Š” ๋ชจ๋“  ์ฝ”๋“œ๋Š” ์ด ์•ฑ์˜ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ˆ˜์ •ํ•œ ๊ฒƒ์ด๋‹ค.
 

#2 LiveData ์‚ฌ์šฉํ•˜๊ธฐ

#2-1 build.gradle.kts (Module :app) ์ˆ˜์ •

plugins {
    ...
}

android {
    ...
}

dependencies {
    ...

    val lifecycle_version = "2.5.1"
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version") // LiveData
}

๋จผ์ €, LiveData ์ถ”์ƒํด๋ž˜์Šค๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ๋ชจ๋“ˆ ์ˆ˜์ค€ gradle์— ์œ„์™€ ๊ฐ™์€ ๊ตฌ๋ฌธ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ํ•ด๋‹น ๊ตฌ๋ฌธ์€ ์—ฌ๊ธฐ์—์„œ ๊ฐ€์ ธ์˜จ ๊ตฌ๋ฌธ์ด๋‹ค.
 

#2-2 MainActivityViewModel.kt ์ˆ˜์ •

...

import androidx.lifecycle.MutableLiveData

...

class MainActivityViewModel : ViewModel() {
    private var count : MutableLiveData<Int> = MutableLiveData(0)

    fun getCurrentCount(): MutableLiveData<Int> {
        return count
    }

    fun updateCount(){
        count.value = (count.value)?.plus(1)
    }
}

๋ณ€์ˆ˜ count๋ฅผ Intํ˜•์ด ์•„๋‹ˆ๋ผ, MutableLiveData<Int>ํ˜•์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค. ํด๋ž˜์Šค MutableLiveData๋Š” ์ถ”์ƒํด๋ž˜์Šค์ธ LiveData์˜ subclass๋‹ค. MutableMap๊ณผ Map์˜ ๊ด€๊ณ„์™€ ๊ฐ™๋‹ค. ์ถ”์ƒํด๋ž˜์Šค๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†๊ธฐ์— MutableLiveData์˜ ๊ฐ์ฒด๋กœ ๋งŒ๋“  ๊ฒƒ์ด๋‹ค.
 
getCurrentCount()์€ ๋ฐ˜ํ™˜ํ˜•์„ ์ˆ˜์ •ํ•œ๋‹ค. getUpdatedCount()๋Š” updateCount()๋กœ ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ณ  ๋‚ด์šฉ๋„ ๋ณ€๊ฒฝํ•œ๋‹ค. count.value += 1๊ณผ ๊ฐ™์ด ์งง๊ฒŒ ์ ์„ ์ˆ˜ ์—†๋Š” ์ด์œ ๋Š” count.value๊ฐ€ null๊ฐ’์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋”ฐ๋ผ์„œ null-safe ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” plus() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. count.value๊ฐ€ null์ด๋ผ๋ฉด .plus(1)์€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.
 
MainActivity.kt๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์ „์— LiveData๊ฐ€ ์–ด๋–ป๊ฒŒ '์‚ด์•„์žˆ์„(Live) ์ˆ˜ ์žˆ๋Š”์ง€' ์•Œ์•„๋ณธ๋‹ค.
 

#2-3 LiveData.observe() ์‚ดํŽด๋ณด๊ธฐ

...

public abstract class LiveData<T> {

    ...

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

    ...

}

๋‹น์—ฐํžˆ, LiveData๊ฐ€ ์ •๋ง๋กœ ์‚ด์•„์žˆ์„๋ฆฌ๋Š” ์—†๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด LiveData๋Š” ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝ๋œ ์ž์‹ ์˜ ๊ฐ’์„ View์— ๊ฐฑ์‹ ํ•˜๋Š”๊ฐ€? ๋ฐ”๋กœ LiveData.observe() ๋ฉ”์†Œ๋“œ์— ์˜ํ•ด์„œ๋‹ค. 'observe'๋ผ๋Š” ๋‹จ์–ด ์ •์˜ ๊ทธ๋Œ€๋กœ, LiveData์˜ ๋ณ€ํ™”๋ฅผ '๊ด€์ฐฐ'ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋‹ค. ์ด ๋ฉ”์†Œ๋“œ๋Š” ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ LifecycleOwner, ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ Observer๋ฅผ ๋ฐ›๋Š”๋‹ค.
 
LifecycleOwner ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋‹จ์ผ ๋ฉ”์†Œ๋“œ ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. ์ด๋Š” Activity, Fragment, Service์™€ ๊ฐ™์ด ์ƒ๋ช…์ฃผ๊ธฐ(Lifecycle)๋ฅผ ์†Œ์œ ํ•œ(Owner) ํด๋ž˜์Šค ์ „๋ถ€๊ฐ€ ๊ตฌํ˜„(implement)ํ•˜๊ณ  ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋‹จ์ผ ๋ฉ”์†Œ๋“œ๋Š” LifecycleOwner.getLifecycle(): LifeCycle๋กœ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด ์ธ์ž์˜ ์กด์žฌ ์ด์œ ๋Š”, LivaData์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•จ์ด๋‹ค. LiveData์˜ ๋ชฉ์ ์€ ๋ณ€๊ฒฝ๋œ ์ž์‹ ์˜ ๊ฐ’์„ View์— ๊ฐฑ์‹ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ, View์— LiveData๊ฐ€ ๋…ธ์ถœ๋˜๋Š” ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, ๊ตณ์ด ๊ฐฑ์‹ ์„ ํ•˜๋ฉฐ ์ž์›๋‚ญ๋น„๋ฅผ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ๋งˆ์น˜ ์ˆจ๊ฒจ์ง„ TextView์— ๊ฐ’์„ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ํ–‰์œ„๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  View์— LiveData๊ฐ€ ๋…ธ์ถœ๋˜๊ณ ์žˆ๋Š”์ง€ ์•„๋‹Œ์ง€๋Š” ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์ฐธ์กฐ(Lifecycle.getCurrentState())ํ•ด ์•Œ ์ˆ˜ ์žˆ๋‹ค.
 
๋‘๋ฒˆ์งธ ์ธ์ž์ธ Observer ์ธํ„ฐํŽ˜์ด์Šค ๋˜ํ•œ ๋‹จ์ผ ๋ฉ”์†Œ๋“œ ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. ๊ทธ ๋‹จ์ผ ๋ฉ”์†Œ๋“œ๋Š” Observer<T>.onChanged(value: T)๋กœ, LiveData ๊ฐ์ฒด์˜ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์ž‘์—…์„ ์ •์˜ํ•œ๋‹ค.
 
์ด LiveData.observe()๋ฅผ MainActivity.kt์—์„œ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ๋‹ค.
 

#2-4 MainActivity.kt ์ˆ˜์ •

...

import androidx.lifecycle.Observer

...

class MainActivity : AppCompatActivity() {

    ...
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
        ...
        
        val countObserver = object : Observer<Int> {
            override fun onChanged(newValue: Int) {
                binding.countText.text = newValue.toString()
            }
        }
        viewModel.getCurrentCount().observe(this, countObserver)
        binding.countButton.setOnClickListener {
            viewModel.updateCount()
        }
    }
}

LiveData.observe()์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ž๋กœ LifecycleOwner๋ฅผ ์ด๋ฏธ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š” MainActivity ์ž์ฒด(this)๋ฅผ ๋„ฃ๋Š”๋‹ค. ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” Observer์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค. getUpdatedCount() ๋ฉ”์†Œ๋“œ๋Š” updateCount() ๋ฉ”์†Œ๋“œ๋กœ ์ด๋ฆ„๊ณผ ๊ธฐ๋Šฅ์„ ๋ฐ”๊ฟจ์œผ๋ฏ€๋กœ, binding.countButton์˜ setOnClickListener { ... } ๋ธ”๋ก์˜ ๋‚ด์šฉ๋„ ๊ทธ์— ๋งž์ถฐ ์ˆ˜์ •ํ•œ๋‹ค.
 
Observer ๊ตฌํ˜„ ํด๋ž˜์Šค ๋ถ€๋ถ„์˜ ๊ฐ€๋…์„ฑ์ด ์•ฝ๊ฐ„ ๊ฑฐ์Šฌ๋ฆฌ๋ฏ€๋กœ, 

/*
val countObserver = object : Observer<Int> {
    override fun onChanged(newValue: Int) {
        binding.countText.text = newValue.toString()
    }
}
viewModel.getCurrentCount().observe(this, countObserver)
*/

viewModel.getCurrentCount().observe(this, Observer {
    binding.countText.text = it.toString()
})

์™€ ๊ฐ™์ด ์ต๋ช… ํด๋ž˜์Šค(ํด๋ž˜์Šค ์ •์˜์™€ ๊ฐ์ฒดํ™”๋ฅผ ๋™์‹œ์— ํ•˜๋Š” ํด๋ž˜์Šค)๋ฅผ ์‚ฌ์šฉํ•ด ์ฝ”๋“œ๋ฅผ ์ค„์ธ๋‹ค.
 
์ด์ œ count์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์–ด๋„ ๊ทธ ๊ฐ’์„ ๋‚ด๊ฐ€ View์— ๋ช…์‹œ์ ์œผ๋กœ ์žฌํ• ๋‹นํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. LiveData.observe()๊ฐ€ ์•”์‹œ์ ์œผ๋กœ ์žฌํ• ๋‹นํ•ด์ค„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ”„๋กœ์ ํŠธ์˜ ํฌ๊ธฐ๊ฐ€ ์ž‘์€ ํ˜„์žฌ ๊ฒŒ์‹œ๊ธ€์˜ ์•ฑ์—์„œ๋Š” ํฌ๊ฒŒ ์™€๋‹ฟ์ง€ ์•Š์ง€๋งŒ, ํ”„๋กœ์ ํŠธ์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก LiveData์˜ ์ด์ ์ด ์ฒด๊ฐ๋  ๊ฒƒ์ด๋‹ค.
 

#3 ์š”์•ฝ

LiveData๋Š” onChangeListener()์˜ ์„ธ๋ จ๋œ ์‚ฌ์šฉ์ด๋‹ค.

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

 

android-practice/live-data/LiveDataBasics at master · Kanmanemone/android-practice

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

github.com