
# 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ํ๊ณ , ์ธ๋ถ์์๋ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํ๋๋ก ๋ง๋๋ ๋ฐฉ๋ฒ์ด๋ค. ์ด๋ ๊ฒ ์ฌ์ฉํ๋ ์ด์ ๋ ํฌ๊ฒ ๋ ๊ฐ์ง๋ก ๋๋ ๋ณผ ์ ์์ ๊ฒ ๊ฐ๋ค.
- ์บก์ํ
- ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ 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๊ฐ ํด๋น ๊ฐ์ฒด๋ฅผ ๋ณผ ์ ์๊ฒ๋๋ค.
- ์ฑ์์ ์์คํ
์๋น์ค๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ์ฑ๊ธํค ํจํด์ ์ฌ์ฉํ๋ LiveData๊ฐ์ฒด๋ฅผ ํ์ฅํ์ฌ ์์คํ
์๋น์ค๋ฅผ ๋ํํ ์ ์๋ค. LiveData๊ฐ์ฒด๊ฐ ์์คํ
์๋น์ค์ ํ ๋ฒ ์ฐ๊ฒฐ๋๋ฉด ๋ฆฌ์์ค๊ฐ ํ์ํ ๋ชจ๋ Observer๊ฐ ํด๋น ๊ฐ์ฒด๋ฅผ ๋ณผ ์ ์๊ฒ๋๋ค.
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
๋์์ด ๋๋ค๋ฉด ๋๊ธ์ด๋ ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์ธ์! ๋ก๊ทธ์ธ ์ํด๋ ๋ฉ๋๋ค ^_^
'Android ๐ฅ๏ธ > ์ฝ์งโ๏ธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Firebase CloudFunction ์ฝ์ง + FCM(1) (0) | 2024.04.21 |
---|---|
Firebase Cloud Messaging(FCM) ์ฝ์งํ๊ธฐ (0) | 2024.04.16 |
์ง๋ ฌํ - Parcelize, Parcelable, Serializable (0) | 2024.04.05 |
์๋๋ก์ด๋ ๋ฌดํ์คํฌ๋กค ๊ธฐ์ด - Infinite Scroll, Endless Scroll (0) | 2024.04.03 |
ListAdapter - DiffUtil ์ฝ์ง (1) | 2024.04.03 |