[AAC] ViewModel

dwjeongยท2023๋…„ 10์›” 28์ผ
0

์•ˆ๋“œ๋กœ์ด๋“œ

๋ชฉ๋ก ๋ณด๊ธฐ
7/28

๐Ÿ”Ž ViewModel์ด๋ž€

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ํ˜น์€ ํ™”๋ฉด ๋ ˆ๋ฒจ์˜ ์ƒํƒœ ๋ณด์œ ์ž. ์ƒํƒœ๋ฅผ UI์— ๋…ธ์ถœํ•˜๊ณ  ๊ด€๋ จ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์บก์Šํ™”ํ•จ.
์ƒํƒœ๋ฅผ ์บ์‹ฑํ•˜๊ณ  ๊ตฌ์„ฑ ๋ณ€๊ฒฝ์„ ํ†ตํ•ด ์ƒํƒœ ์œ ์ง€ -> ํ™”๋ฉด์„ ํšŒ์ „ํ•˜๋Š” ๋“ฑ์˜ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ ์‹œ์—๋„ UI๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์•„๋„ ๋จ.

๐Ÿ“– ViewModel ์žฅ์ 

  1. UI ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ.
  2. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Œ.
  3. Hilt ๋ฐ Navigation๊ณผ ๊ฐ™์€ ์ฃผ์š” JetPack ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€์˜ ํ†ตํ•ฉ์„ ์ง€์›, Compose์™€๋„ ์™„๋ฒฝํ•˜๊ฒŒ ํ˜ธํ™˜๋จ.
  • ViewModel ๋Œ€์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์œ ํ•˜๋Š” ์ผ๋ฐ˜ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ
    ์•กํ‹ฐ๋น„ํ‹ฐ๋‚˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ชฉ์ ์ง€ ๊ฐ„ ์ด๋™ ์‹œ ๋ฌธ์ œ๊ฐ€ ๋จ.
    ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์†Œ๋ฉธ๋จ.

๐Ÿ“• ์ง€์†์„ฑ

ViewModel์ด ๋ณด์œ ํ•˜๋Š” ์ƒํƒœ์™€ ViewModel์ด ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ์ž‘์—…์„ ํ†ตํ•ด ์ง€์†์„ฑ ์ œ๊ณต.
์ด๋Ÿฌํ•œ ์บ์‹ฑ์„ ํ†ตํ•ด ํ™”๋ฉด ํšŒ์ „๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚  ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ฌ ํ•„์š”๊ฐ€ ์—†๊ฒŒ ํ•จ.


๐Ÿ“• ๋ฒ”์œ„

ViewModel์„ ์ƒ์„ฑํ•  ๋•Œ ViewModelStoreOwner ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ. (Navigation ๋ชฉ์ ์ง€, Navigation ๊ทธ๋ž˜ํ”„, ์•กํ‹ฐ๋น„ํ‹ฐ, ํ”„๋ž˜๊ทธ๋จผํŠธ ๋˜๋Š” ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋‹ค๋ฅธ ์œ ํ˜•์ผ ์ˆ˜ ์žˆ์Œ.)

ViewModel์€ ViewModelStoreOwner์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ์— ๋Œ€ํ•œ ๋ฒ”์œ„๋กœ ์„ค์ •๋จ.
ViewModelStoreOwner๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ์‚ฌ๋ผ์งˆ ๋•Œ๊นŒ์ง€ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€๋จ.

ViewModel์ด ๋ฒ”์œ„๋กœ ์„ค์ •๋œ ์•กํ‹ฐ๋น„ํ‹ฐ๋‚˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ํŒŒ๊ดด๋  ๋•Œ, ViewModel์ด ํ•ด๋‹น ๋ฒ”์œ„๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š” ์ƒํƒœ์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์ด ๊ณ„์†๋จ.


๐Ÿ“• SavedStateHandle

SavedStateHandle์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ตฌ์„ฑ ๋ณ€๊ฒฝ ๋ฟ๋งŒ์ด ์•„๋‹Œ ํ”„๋กœ์„ธ์Šค ์žฌ์ƒ์„ฑ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์†์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ.
์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์„ ๋‹ซ๊ณ  ๋‚˜์ค‘์— ๋‹ค์‹œ ์—ด์–ด๋„ UI ์ƒํƒœ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ.


๐Ÿ“• ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์•ก์„ธ์Šค

UI ๋ ˆ์ด์–ด์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜๋Š” ViewModel.
ViewModel์€ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•  ๋•Œ ๊ณ„์ธต ๊ตฌ์กฐ์˜ ๋‹ค๋ฅธ ๋ ˆ์ด์–ด๋กœ ์ด๋ฒคํŠธ๋ฅผ ์œ„์ž„ํ•˜๋Š” ์—ญํ• ์„ ํ•จ.



๐Ÿ“– ViewModel ๊ตฌํ˜„

  • ์ฃผ์‚ฌ์œ„ ์˜ˆ์ œ
    -> ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ์–ป๊ณ  ์œ ์ง€ํ•˜๋Š” ์—ญํ• ์€ ์•กํ‹ฐ๋น„ํ‹ฐ๋‚˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ์•„๋‹Œ ViewModel์— ์žˆ์Œ.

๋ทฐ๋ชจ๋ธ ์ฝ”๋“œ

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}




์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ๋ทฐ๋ชจ๋ธ์— ์ ‘๊ทผ

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}



//Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}
  • โ—๏ธ์ฃผ์˜ํ•  ์ 
    ViewModel์€ ๋ทฐ,๋ผ์ดํ”„์‚ฌ์ดํด ํ˜น์€ ์•กํ‹ฐ๋น„ํ‹ฐ ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š์•„์•ผ ํ•จ.
    ViewModel์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๊ฐ€ UI๋ณด๋‹ค ํฌ๊ธฐ ๋•Œ๋ฌธ์— ViewModel ๋‚ด์—์„œ ๋ผ์ดํ”„์‚ฌ์ดํด ๊ด€๋ จ API์˜ ๋ณด์œ ํ•˜๋Š” ๊ฒƒ์€ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ.


๐Ÿ“– ViewModel์˜ ์ˆ˜๋ช…์ฃผ๊ธฐ

ViewModel์€ ํ•ด๋‹น ViewModelStoreOwner๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ๊นŒ์ง€ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์•„์žˆ์Œ.

์•กํ‹ฐ๋น„ํ‹ฐ๊ฐ€ ์ข…๋ฃŒ๋  ๋•Œ
ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ๋ถ„๋ฆฌ๋  ๋•Œ
**Navigation ํ•ญ๋ชฉ์ด ๋ฐฑ ์Šคํƒ์—์„œ ์ œ๊ฑฐ๋  ๋•Œ

-> ๋”ฐ๋ผ์„œ ViewModel์€ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚˜๋„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š”๋ฐ ์ ํ•ฉ.

ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ์—๋„ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋จ.

์ผ๋ฐ˜์ ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ๋กœ ์‹œ์Šคํ…œ์ด ์•กํ‹ฐ๋น„ํ‹ฐ ๊ฐ์ฒด์˜ onCreate() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ViewModel์„ ์š”์ฒญ. ์‹œ์Šคํ…œ์€ ์•กํ‹ฐ๋น„ํ‹ฐ ์กด์žฌ ๊ธฐ๊ฐ„ ๋™์•ˆ ๊ธฐ๊ธฐ ํ™”๋ฉด์ด ํšŒ์ „ํ•˜๋Š” ๊ฒฝ์šฐ ๋“ฑ์˜ ์ด์œ ๋กœ onCreate()๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ ๊ฐ€๋Šฅ.
ViewModel์€ ๋งจ ์ฒ˜์Œ์œผ๋กœ ์š”์ฒญํ•  ๋•Œ๋ถ€ํ„ฐ ์•กํ‹ฐ๋น„ํ‹ฐ๊ฐ€ ์™„๋ฃŒ๋˜๊ณ  ํŒŒ๊ดด๋  ๋•Œ๊นŒ์ง€ ์กด์žฌํ•จ.



๐Ÿ“• ViewModel ์ข…์†์„ฑ ์ •๋ฆฌ

ViewModel์€ ViewModelStoreOwner๊ฐ€ ํŒŒ๊ดด๋  ๋•Œ onCleared()๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•จ.
์ด ๋•Œ ViewModel์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ผ์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ์ž‘์—…๊ณผ ์ข…์†์„ฑ์„ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

viewModelScope๋Š” ViewModel ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ์ž๋™์œผ๋กœ ๋”ฐ๋ฅด๋Š” ๋‚ด์žฅ๋œ CoroutineScope.
ViewModelStoreOwner๊ฐ€ ์ˆ˜๋ช… ์ฃผ๊ธฐ์˜ ๋์—์„œ ViewModel์„ ์ง€์šฐ๋ฉด ViewModel์€ CoroutineScope๋ฅผ ์ทจ์†Œํ•จ.


๋ผ์ดํ”„์‚ฌ์ดํด ๋ฒ„์ „ 2.5 ์ด์ƒ์—์„œ๋Š” ViewModel์˜ ์ƒ์„ฑ์ž์— ํ•˜๋‚˜ ์ด์ƒ์˜ Closeable ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ViewModel ์ธ์Šคํ„ด์Šค๊ฐ€ ์ง€์›Œ์งˆ ๋•Œ ์ž๋™์œผ๋กœ ๋‹ซํžˆ๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}


๐Ÿ“– Jetpack Compose์™€ ViewModel

Jetpack Compose๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ViewModel์€ ํ™”๋ฉด UI ์ƒํƒœ๋ฅผ Composable์— ๋…ธ์ถœํ•˜๋Š” ์ฃผ์š” ๋ฐฉ๋ฒ•.

ViewModel์€ Composable์— ๋ฒ”์œ„ ์„ค์ •์„ ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ค‘์š”.
๐Ÿ‘‰ Composable์ด ViewModelStoreOwner๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ.


  • Compose์—์„œ ViewModel์˜ ์ด์ ์„ ์–ป๋Š” ๋ฐฉ๋ฒ•
    ๊ฐ ํ™”๋ฉด์„ ํ”„๋ž˜๊ทธ๋จผํŠธ ํ˜น์€ ์•กํ‹ฐ๋น„ํ‹ฐ์— ํ˜ธ์ŠคํŒ…ํ•˜๊ฑฐ๋‚˜ Compose Navigation์„ ์‚ฌ์šฉํ•˜๊ณ  Composable ํ•จ์ˆ˜์—์„œ Navigation ๋ชฉ์ ์ง€์— ๊ฐ€๋Šฅํ•œ ๊ฐ€๊นŒ์šด ์œ„์น˜์—์„œ ViewModel์„ ์‚ฌ์šฉ.

0๊ฐœ์˜ ๋Œ“๊ธ€