#1 ์ํ ์ฑ
[Android] ViewModel - View์ ๊ฐ์ฒด(ViewModel) ์ ๋ฌ
#1 ๊ฐ์ #1-1 Data Binding๊ณผ ViewModel [Android] Data Binding - View์ ๊ฐ์ฒด ์ ๋ฌ #1 ๊ฐ์ฒด ์ ๋ฌ์ ํ์์ฑ #1-1 ์ด์ ๊ธ Data Binding - ๊ธฐ์ด #1 ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ์ฌ์ฉ ์ #1-1 ์์ ์ฑ ์๊ณผ ๊ฐ์ ๊ฐ๋จํ ์ฑ์ด ์๋ค. Button์
kenel.tistory.com
์๋์ ์๋ ์ฝ๋๋ค์ ์ ๊ฒ์๊ธ์ ์์ฑ๋ ์ฑ์ ๊ธฐ๋ฐ์ผ๋ก ์์ ๋ ๊ฒ๋ค์ด๋ค.
#2 ๋ชฉํ (์์ ํ ๋ถ๋ถ)
// package com.example.eventwithview
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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.value๊ฐ 10์ด์์ด๋ฉด Activity์์ Toast ๋ฉ์์ง๋ก count.value ๊ฐ์ ํ์ํ๊ฒ ๋ง๋ค ๊ฒ์ด๋ค. ViewModel ์์ฒด์ Toast.makeText(...).show()๋ฅผ ๋ง๋ค๋ฉด ์๋์ ๊ฐ์ ์ฝ๋๊ฐ ๋๋ค.
#3 ์ฝ๋ ์์ - MVVM ํจํด ์๋ฐฐ
'๋๋ณด๊ธฐ'์ ์๋ ์ฝ๋๋ค์ ํน์๋ ํด์ ์ฒจ๋ถํ๊ธด ํ์ผ๋, ๋ฑํ ๋ณผ ํ์๋ ์๋ ์ฝ๋๋ค์ด๋ค.
#3-1 ViewModelFactory ์ถ๊ฐ
// package com.example.eventwithview
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class MainActivityViewModelFactory(private val activityContext: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainActivityViewModel::class.java)) {
return MainActivityViewModel(activityContext) as T
}
throw IllegalArgumentException("Unknown ViewModel Class")
}
}
#3-2 Activity ์์
...
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val factory = MainActivityViewModelFactory(this)
viewModel = ViewModelProvider(this, factory)[MainActivityViewModel::class.java]
binding.myViewModel = viewModel
viewModel.getCurrentCount().observe(this, Observer {
binding.countText.text = it.toString()
})
}
}
#3-3 ViewModel ์์
...
class MainActivityViewModel(private val activityContext: Context) : ViewModel() {
...
fun updateCount() {
count.value = (count.value)?.plus(1)
if (10 <= count.value!!) {
Toast.makeText(activityContext, "${count.value}", Toast.LENGTH_SHORT).show()
}
}
}
Toast ๋ฉ์์ง ํ์์ ํ์ํ Activity์ Context๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ์คํํ๋ ์ฝ๋๋ค. ํ์ง๋ง, ์ด ์ฝ๋๋ MVVM ํจํด์ ์ ๋ฉด์ผ๋ก ์๋ฐํ๊ณ ์๋ค.
#3-4 MVVM ํจํด์ ์กฐ๊ฑด
[Android] MVVM ๊ตฌ์กฐ ํ๋์ ๋ณด๊ธฐ
#1 ์๋๋ก์ด๋ ์ฑ์ '์ ํต์ ์ธ' ๋ฐฉ์ vs MVVM ํจํด #1-1 ๋์๋์ ์ข ์์ฑ ํ์ดํ๋ ํด๋์ค ๊ฐ์ ์ข ์์ฑ์ ๋ํ๋ธ๋ค. ์๋ฅผ ๋ค์ด, View๋ ViewModel์ ์ข ์๋๋ค. ์ข ์์ ์ฌ์ ์ ์๋ฏธ๋ '์์ฃผ์ฑ์ด ์์ด ์ฃผ๊ฐ
kenel.tistory.com
์๋ํ๋ฉด ๊ตฌ๊ธ์ด ๊ถ์ฅํ๋ MVVM ํจํด์์ ViewModel์ Activity๋ฅผ ์ฐธ์กฐํด์๋ ์ ๋๊ธฐ ๋๋ฌธ์ด๋ค. ViewModel์ Activity๋ฅผ ๋ชฐ๋ผ์ผ ํ๋ค.
#4 ์ฌ๋ฐ๋ฅธ ์์ ๋ฐฉํฅ (LiveData ์ด์ฉ)
#4-1 ๊ฐ์
ํ๋, Toast ๋ฉ์์ง๋ฅผ ํ์ํ๋ ค๋ฉด, Activity์ Context๊ฐ ํ์ํ๋ค. ์ด๋ด ๋ ViewModel์ Toatst ๋ฉ์์ง๋ฅผ ์คํํ ํ์ด๋ฐ๊ณผ ๊ทธ ๋ฉ์์ง์ ๋ด์ฉ๋ง์ ๊ธฐ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ง์ ์ ์ธ ์คํ์ Activity์๊ฒ ๋งก๊ธด๋ค. ํ์ด๋ฐํ๋ฉด ์๊ฐ๋๋ ๊ฒ์ LiveData๋ค. Toast ๋ฉ์์ง์ ๋ด์ฉ์ MutableLiveData<String>์ ํ ๋นํ๋ฉด ๊ทธ ์์ฒด๋ก ํ ์คํธ ๋ฉ์์ง์ ๋ด์ฉ์ ์ ๋ฌํ๋ ๊ฒ์ด๋ฉฐ ๊ทธ๋ฆฌ๊ณ Activity ์ LiveData.observe()์ ํด๋น ์ต์ ๋ฉ์์ง์ ๋ด์ฉ์ ํ์ํ ํ์ด๋ฐ๊น์ง ์๋ ค์ค ์ ์์ง ์์๊ฐ.
ํ์ง๋ง LiveData<String>์๋ ๋ฌธ์ ์ ์ด ์กด์ฌํ๋ค. ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ์กํฐ๋นํฐ๋ก ๋์ด๊ฐ๋ค๊ฐ ๋ค์ ๋์์ค๋ฉด, LiveDate.observer()๊ฐ ๋ค์ ์๋๋์ด ์ด๋ฏธ ํ์ํ๋ Toast ๋ฉ์์ง๊ฐ ๋ค์ ํ์๋๊ธฐ ๋๋ฌธ์ด๋ค.
#4-2 LiveData์ ๋ด์ Event ๊ฐ์ฒด
LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)
2021 Update: This guidance is deprecated in favor of the official guidelines.
medium.com
์ ๋ธ๋ก๊ทธ ๊ฒ์๊ธ์์ LiveData์ ๋ด์ ๊ฐ์ฒด์, ๊ทธ ๊ฐ์ฒด์ ๋ํ ์ด๋ฒคํธ(Toast ๋ฉ์์ง ํ์ ๋ฑ)๊ฐ ์ด๋ฏธ ์ํ๋์๋์ง ์ฒดํฌํ๋ ํ๋กํผํฐ๋ฅผ ๋ฃ์์ผ๋ก์จ, LiveData<String>์ ์ฐ๋ ๋ฐฉ์์ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๋ค. ๊ฒ๋ค๊ฐ, ์ฌ๋ฌ ์ค๋ ๋์์ ๋์์ ๊ฐ์ Event์ ์ ๊ทผํ๋ ๊ฒฝ์ฐ๊น์ง ๊ณ ๋ คํด ์ค๊ณํ๋ค๊ณ ํ๋ค.
์๋ ์ฝ๋๋ ์ ๋ธ๋ก๊ทธ ๊ฒ์๊ธ์ ์๋ Event ํด๋์ค์ ์ฝ๋๋ฅผ ๋ณต์ฌ/๋ถ์ฌ๋ฃ๊ธฐ ํ ๊ฒ์ด๋ค. ์ํ๊น๊ฒ๋(?) ์ ๋ธ๋ก๊ทธ ๊ฒ์๊ธ์ ์ฝ๋๋ ์ต์ ํ๋ ์๋๋ก์ด๋ ๊ณต์ ๊ฐ์ด๋๋ผ์ธ์ผ๋ก ๋์ฒด๋์ด Deprecated๋์๋ค๊ณ ํ๋ค. ํ์ง๋ง, ํด๋น ๊ฐ์ด๋๋ผ์ธ์ StateFlow๋ฅผ ์ฌ์ฉํ๋ค. StateFlow๋ ์์ง ๋ด๊ฐ ๋ชจ๋ฅด๋(ํ์ง๋ง ๊ณง ์๊ฒ ๋ ) ๊ฐ๋
์ด๊ธฐ ๋๋ฌธ์ ์ฌ๊ธฐ์ ์ ๋ธ๋ก๊ทธ ๊ฒ์๊ธ์ ์ฝ๋๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค.
#4-3 Event.kt
// https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
/* ์ฌ์ฉ ๋ก
๋ทฐ ๋ชจ๋ธ์์,
class ListViewModel : ViewModel {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToDetails : LiveData<Event<String>>
get() = _navigateToDetails
fun userClicksOnButton(itemId: String) {
_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value
}
}
์กํฐ๋นํฐ์์,
myViewModel.navigateToDetails.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
startActivity(DetailsActivity...)
}
})
*/
์ ํด๋์ค๋ฅผ ํ๋ก์ ํธ์ ์ถ๊ฐํ๋ค.
#5 ์ฝ๋ ์์ - MVVM ํจํด ์ค์
#5-1 ViewModel ์์
...
class MainActivityViewModel : ViewModel() {
...
private val _toastMessage = MutableLiveData<Event<String>>()
val toastMessage: LiveData<Event<String>>
get() = _toastMessage
...
fun updateCount() {
count.value = (count.value)?.plus(1)
if (10 <= count.value!!) {
_toastMessage.value = Event("${count.value}")
}
}
}
#5-2 Activity ์์
...
class MainActivity : AppCompatActivity() {
...
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
displayToastEvent()
}
private fun displayToastEvent() {
viewModel.toastMessage.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
})
}
}
#4-3์ ์๋์ชฝ ์ฃผ์์ ์๋ ์ฌ์ฉ ๋ก๋ฅผ ๊ทธ๋๋ก ๊ฐ์ ธ์๋ค. ์ด ์ฝ๋ ๋ํ #4-2์ ์๋ ๋ธ๋ก๊ทธ ๊ฒ์๊ธ์์ ๊ฐ์ ธ์จ ๊ฒ์ด๋ค.
#6 ์๋ ํ์ธ
#7 ์์ฝ
๋ณธ ๊ฒ์๊ธ์์์ ๊ฐ์ด MVVM ํจํด์ ๋ถ๋ถ์ ์ธ ๋ฌด๊ฒฐ์ฑ์ ์ ์งํ๋ค๋ณด๋ฉด, ์ ์ฒด์ ์ธ ๊ทธ๋ฆผ๋ ์ ๋ณด์ผ ๋ ์ด ์ฌ ๊ฒ์ผ๋ก ๊ธฐ๋ํ๋ค.
#8 ์์ฑ๋ ์ฑ
android-practice/view-model/EventWithView at master · Kanmanemone/android-practice
Contribute to Kanmanemone/android-practice development by creating an account on GitHub.
github.com
'๊นจ์ ๊ฐ๋ ๐ > Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] Room - ๋ฐํ ๊ฐ์ด ์๋ INSERT (0) | 2024.02.29 |
---|---|
[Android] RecyclerView - notifyDataSetChanged() (0) | 2024.02.28 |
[Android] Room - UPDATE ์ฐ์ต (0) | 2024.02.26 |
[Android] Room - Entity, DAO, Database (0) | 2024.02.24 |
์ฐธ[Android] Room - ๊ธฐ์ด, INSERT์ DELETE ์ฐ์ต (0) | 2024.02.23 |