๐ก๊ตฌ์ฑ ๋ณ๊ฒฝ ์ ํ ๋ฒ์ ๋ฐ์ดํฐ ๋ณํ๋ฅผ ๊ด์ฐฐํ๊ธฐ ์ํ UI Event ํ์ฉ๋ฒ์ ๋ํ์ฌ ๊ธฐ๋กํ์์ต๋๋ค.
Event
- LiveData์ ๊ฐ์ Observable ํ์ ์ ํ์ฉํ๋ฉด ๋ณ๊ฒฝ ์ฌํญ์ ๊ตฌ๋ ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ง์์ ์ผ๋ก ๋ ธ์ถํ๊ณ ๊ด์ฐฐํ ์ ์์ต๋๋ค.
- ํ์ง๋ง ์ค๋ต๋ฐ, ํ ์คํธ, navigation ์ ํ ๋ฑ์ ์ผํ์ฑ ์ด๋ฒคํธ๋ ํ ๋ฒ์ ๋ฐ์ดํฐ ๋ณํ๋ง์ ํ์๋ก ํฉ๋๋ค.
- ์ด ๋ ์ฌ๋ฌ ๋ฒ ๋ ธ์ถ๋์ง ์๋๋ก ๋ณ๋์ Event wrapper class๋ฅผ ํ์ฉํ๊ฑฐ๋ SingleLiveData ํ์ ๋ฑ์ ํ์ฉํ ์ ์์ต๋๋ค.
์๋๋ก์ด๋์์ ํ์ฉํ ์ ์๋ ๋ฐฉ์์ SingleLiveData์ Event ํจํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค. LiveData๋ฅผ ์ด๋ฌํ ํ๊ฒฝ์ ํ์ฉํ ๊ฒฝ์ฐ ํ์ฑํ๋ ๋๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ๋ค์ ์ ๋ฌํ๊ฒ ๋๋ฉฐ, ๋นํจ์จ์ ์ธ ๋ฆฌ์์ค๋ฅผ ๋ญ๋นํ ์ ์์ต๋๋ค.
SingleLiveData
- SingleLiveData ํจํด์ ํ์ฉํ๋ฉด LiveData๋ฅผ ํ์ฅํ์ฌ ํ ๋ฒ๋ง ๋ฐ์ดํฐ๊ฐ ์๋น๋๋๋ก ํ ์ ์์ต๋๋ค.
- ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๊ฐ ๋ ํ์๋ ๋ ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ์ง ์๋๋ก ์ค๊ณํฉ๋๋ค.
MutableLiveData
- SingleLiveData๋ฅผ ๊ตฌํํ ๋ MutableLiveData๋ฅผ ์์๋ฐ์ ๊ธฐ๋ฅ์ ํ์ฅํฉ๋๋ค.
- AtomicBoolean์ ํ์ฉํ์ฌ ์ค๋ ๋์์ ์์ ํ๊ฒ boolean ๊ฐ์ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค.
- ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ ์ค์ธ์ง ์ฌ๋ถ๋ฅผ ์ถ์ ํ์ฌ true๋ก ์ ํํ๊ณ , ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ์๋ฃํ๋ฉด false๋ก ์ ํํฉ๋๋ค.
- ์ด๋ฅผ ํตํด true์ธ ์ํ์์๋ง ์ด๋ฒคํธ๋ฅผ ๋ฐ์ํ์ฌ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
class SingleLiveData<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
// Observe the internal MutableLiveData
super.observe(owner, { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(value: T?) {
pending.set(true)
super.setValue(value)
}
// ์ด๋ฒคํธ ํธ๋ฆฌ๊ฑฐ
fun call() {
value = null
}
}
ViewModel์์ ํ์ฉ
- ViewModel์์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๊ณ , ์ด๋ฅผ ๊ด์ฐฐํ๋ Activity ๋๋ Fragment์์ ์ด๋ฒคํธ ๋ฐ์์ ๋ฐ๋ฅธ UI ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
class MyViewModel : ViewModel() {
val singleLiveEvent = SingleLiveData<String>()
fun triggerEvent() {
singleLiveEvent.value = "ํ ๋ฒ๋ง ์คํ๋ ์ด๋ฒคํธ"
}
}
// in Activity or Fragment
viewModel.singleLiveEvent.observe(this, Observer { event ->
showToast(event)
})
Event Wrapper
- Event Wrapper ํจํด์ ๋ฐ์ดํฐ๊ฐ ์ด๋ฏธ ์ฒ๋ฆฌ๋์๋์ง ์ฌ๋ถ๋ฅผ ์ถ์ ํ์ฌ, ๋์ผํ ์ด๋ฒคํธ๊ฐ ๋ค์ ์ ๋ฌ๋์ง ์๋๋ก ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
- Event๋ผ๋ ๋ํผ ํด๋์ค๋ฅผ ๋์ ํด, ํ ๋ฒ๋ง ์๋น๋ ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
class Event<out T>(private val content: T) {
private var hasBeenHandled = false
/**
* ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋์ง ์์๋ค๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๊ณ , ์ฒ๋ฆฌ๋ ๊ฒฝ์ฐ์๋ null์ ๋ฐํ
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* ์ด๋ฒคํธ๊ฐ ์ด๋ฏธ ์ฒ๋ฆฌ๋์๋์ง ์ฌ๋ถ์ ๊ด๊ณ์์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํ
*/
fun peekContent(): T = content
}
ViewModel์์ ํ์ฉ
- SingleLiveData์ ๋ง์ฐฌ๊ฐ์ง๋ก ViewModel์์ ํ์ฉํ ์ ์์ต๋๋ค.
class MyViewModel : ViewModel() {
private val _event = MutableLiveData<Event<String>>()
val event: LiveData<Event<String>> get() = _event
fun triggerEvent() {
_event.value = Event("ํ ๋ฒ๋ง ์ฒ๋ฆฌ๋ ์ด๋ฒคํธ")
}
}
viewModel.event.observe(this, Observer { event ->
event.getContentIfNotHandled()?.let {
// ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋์ง ์์ ๊ฒฝ์ฐ์๋ง ์ฒ๋ฆฌ
showToast(it)
}
})
์ด๋ค ๋ฐฉ์์ด ์ข์๊น?
- SingleLiveData๋ ํ ๋ฒ๋ง ์ ๋ฌ๋๋ ๊ฐ๋จํ ์ด๋ฒคํธ์ ์ ํฉํฉ๋๋ค.
- ์ด๋ ํ ์คํธ ๋ฉ์์ง๋ ๋ค๋น๊ฒ์ด์ ๊ณผ ๊ฐ์ด ํ ๋ฒ ๋ฐ์ํ ํ ๋ค์ ๋ฐ์ํ ํ์๊ฐ ์๋ ๊ฒฝ์ฐ ์ ์ฉํฉ๋๋ค.
- Event Wrapper ํจํด์ ๋ณต์กํ UI ์ํ ๊ด๋ฆฌ์ ๋ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- UI ์ํ๋ฅผ ๋ช ์์ ์ผ๋ก ๊ด๋ฆฌํ ๋ ์ ์ฉํฉ๋๋ค.
- ์๋๋ก์ด๋์์ ๋ ํจํด์ UI ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ๋ ์ค๋ณต ํธ์ถ์ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ์ด๋ฉฐ, ์ด ํจํด๋ค์ ํ์ฉํ๋ฉด UI ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ํ ๋ฒ๋ง ์๋น๋๋ ์ด๋ฒคํธ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.