[Android] SavedStateHandle๋ž€?

์ด์ฃผํ˜•ยท2025๋…„ 1์›” 20์ผ

๐Ÿ“Œ SavedStateHandle๋ž€?

SavedStateHandle์€ Android ๊ฐœ๋ฐœ์—์„œ ViewModel์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์ค‘์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ์— ์˜ํ•œ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ๋‚˜ ์„ค์ • ๋ณ€๊ฒฝ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด์กดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

๐Ÿ“Œ ๋ฒˆ๋“ค

๋‹จ, SavedStateHandle์€ local์ด๋‚˜ remote ์ €์žฅ์†Œ์™€๋Š” ๋‹ค๋ฅธ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.
SavedStateHandle์€ ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ์˜ "saved instance state" ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ž„์‹œ๋กœ ์ €์žฅ๋˜๋Š” ๋ฒˆ๋“ค(Bundle) ํ˜•ํƒœ๋กœ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ์ €์žฅ ์œ„์น˜์™€ ์ˆ˜๋ช…

    • ๋ฉ”๋ชจ๋ฆฌ์ƒ์— ์ž„์‹œ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค
    • ์•ฑ์ด ์™„์ „ํžˆ ์ข…๋ฃŒ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋Š” ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค
    • ์‹œ์Šคํ…œ์— ์˜ํ•œ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ๋‚˜ ์„ค์ • ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์กด๋ฉ๋‹ˆ๋‹ค
  2. ์ ํ•ฉํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€

    • ํ˜„์žฌ ํ™”๋ฉด์˜ UI ์ƒํƒœ (์Šคํฌ๋กค ์œ„์น˜, ์ž…๋ ฅ ์ค‘์ธ ํ…์ŠคํŠธ ๋“ฑ)
    • ์ผ์‹œ์ ์œผ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ
    • ์ž‘์€ ํฌ๊ธฐ์˜ ๋ฐ์ดํ„ฐ (Bundle ํฌ๊ธฐ ์ œํ•œ์ด ์žˆ์Œ)
  3. ๋ถ€์ ํ•ฉํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€

    • ์˜๊ตฌ์ ์œผ๋กœ ๋ณด์กดํ•ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ
    • ํฐ ํฌ๊ธฐ์˜ ๋ฐ์ดํ„ฐ
    • ์•ฑ์ด ์ข…๋ฃŒ๋˜์–ด๋„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ

์˜๊ตฌ์ ์ธ ๋ฐ์ดํ„ฐ ์ €์žฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • SharedPreferences
  • Room Database
  • DataStore
  • ์„œ๋ฒ„ ์ €์žฅ์†Œ

๐Ÿ“Œ ์ฃผ์š” ํŠน์ง•

  1. ์ž๋™ ์ƒํƒœ ์ €์žฅ

    • ์‹œ์Šคํ…œ์— ์˜ํ•œ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹œ์—๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
    • Configuration changes (ํ™”๋ฉด ํšŒ์ „ ๋“ฑ) ๋ฐœ์ƒ ์‹œ์—๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
  2. ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ง€์›

    • ๊ธฐ๋ณธ ํƒ€์ž… (Int, String, Boolean ๋“ฑ)
    • Parcelable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ ๊ฐ์ฒด
    • ArrayList์™€ ๊ฐ™์€ ์ปฌ๋ ‰์…˜

๐Ÿ“Œ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

ViewModel์—์„œ์˜ ์‚ฌ์šฉ

kotlinCopyclass MyViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    var username: String
        get() = savedStateHandle.get<String>(KEY_USERNAME) ?: ""
        set(value) {
            savedStateHandle[KEY_USERNAME] = value
        }

    companion object {
        private const val KEY_USERNAME = "username"
    }
}

Compose์—์„œ์˜ ์‚ฌ์šฉ ์˜ˆ์‹œ

kotlinCopy@Composable
fun UserScreen(
    viewModel: MyViewModel = viewModel()
) {
    var username by remember { mutableStateOf(viewModel.username) }
    
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        OutlinedTextField(
            value = username,
            onValueChange = { 
                username = it
                viewModel.username = it
            },
            label = { Text("์‚ฌ์šฉ์ž ์ด๋ฆ„") }
        )
    }
}

StateFlow์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ
StateFlow๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ํšจ์œจ์ ์ธ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

kotlinCopyclass MyViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    private val _username = savedStateHandle.getStateFlow("username", "")
    val username = _username.asStateFlow()
    
    fun updateUsername(newName: String) {
        savedStateHandle["username"] = newName
    }
}

Compose์—์„œ StateFlow ํ™œ์šฉ

kotlinCopy@Composable
fun UserProfileScreen(
    viewModel: MyViewModel = viewModel()
) {
    val username by viewModel.username.collectAsState()
    
    Column {
        Text("ํ˜„์žฌ ์‚ฌ์šฉ์ž: $username")
        
        OutlinedTextField(
            value = username,
            onValueChange = { viewModel.updateUsername(it) },
            label = { Text("์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ณ€๊ฒฝ") }
        )
    }
}

๐Ÿ“Œ ์ฃผ์˜์‚ฌํ•ญ

  1. ํ‚ค ๊ด€๋ฆฌ

    • ํ‚ค ๊ฐ’์€ ์ƒ์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
    • ์ค‘๋ณต๋˜์ง€ ์•Š๋Š” ๊ณ ์œ ํ•œ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ €์žฅ ์šฉ๋Ÿ‰

    • SavedStateHandle์€ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ์ด ์žˆ์œผ๋ฏ€๋กœ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋Š” ๋‹ค๋ฅธ ์ €์žฅ์†Œ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  3. Parcelable ๊ตฌํ˜„

    • ์ปค์Šคํ…€ ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ Parcelable์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ ํ™œ์šฉ ์‚ฌ๋ก€
๋ณต์žกํ•œ ํผ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ:

class FormViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    private val _formData = savedStateHandle.getStateFlow("form_data", FormData())
    val formData = _formData.asStateFlow()
    
    fun updateField(field: String, value: String) {
        val currentData = _formData.value
        val updatedData = when(field) {
            "name" -> currentData.copy(name = value)
            "email" -> currentData.copy(email = value)
            else -> currentData
        }
        savedStateHandle["form_data"] = updatedData
    }
    
    @Parcelize
    data class FormData(
        val name: String = "",
        val email: String = ""
    ) : Parcelable
}

๐Ÿ“Œ ๊ฒฐ๋ก 

SavedStateHandle์€ Android ์•ฑ์—์„œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ViewModel๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์„ค์ • ๋ณ€๊ฒฝ์ด๋‚˜ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ๋„ ์•ˆ์ •์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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