์•ˆ๋…• DataStore, ์•ˆ๋…•SharedPreferences๐Ÿ‘‹โ€Š-โ€ŠAndroid๐Ÿ“ฑโ€Š-โ€ŠPart 1: Preference DataStore

Ashton Yoonยท2021๋…„ 4์›” 17์ผ
0
post-thumbnail

์ด ๊ธ€์€ Hello DataStore, Bye SharedPreferences๐Ÿ‘‹ โ€” Android๐Ÿ“ฑ โ€” Part 1: Preference DataStore์„ ๋ฒˆ์—ญํ•œ ํฌ์ŠคํŠธ.

DataStore?

  • Jetpack DataStore๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ˆ˜๋‹จ์ด๋‹ค.

  • ์ด๋ฅผ ํ†ตํ•ด SharedPreferences์ฒ˜๋Ÿผ Key-Value Pair๋‚˜ Protocol Buffer๋ฅผ ์ด์šฉํ•œ Typed Objects๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. (๋‹ค์Œ ํฌ์ŠคํŠธ์—์„œ ๋‹ค๋ฃฐ ์˜ˆ์ •)

  • DataStore๋Š” Kotlin, Coroutine ๋ฐ Flow๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๊ด€์„ฑ๊ณผ ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•จ์œผ๋กœ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ €์žฅํ•œ๋‹ค.

  • ๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ DataStore๋Š” SharedPreferences๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ ์ €์žฅ์ˆ˜๋‹จ์ด๋‹ค.






DataStore๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

  • ๊ฐ€์žฅ ์ข‹์•„ํ•˜๋Š” ์ด์œ ๋กœ๋Š” Kotlin, Coroutine ๋ฐ Flow๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • SharedPreferences๋Š” ๋™๊ธฐ API๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ๊ณผ MAIN ์Šค๋ ˆ๋“œ๋กœ๋ถ€ํ„ฐ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋‹จ์ ์ด ์žˆ๋‹ค ๋ฐ˜๋ฉด DataStore๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Dispatchers.IO๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— UI ์Šค๋ ˆ๋“œ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ์— ์•ˆ์ „ํ•˜๋‹ค.

  • ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋กœ๋ถ€ํ„ฐ ์•ˆ์ „ํ•˜๋‹ค.

  • SharedPreference์—์„œ DataStore๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ์ œ๊ณตํ•ด์ค€๋‹ค.

  • Protocol Buffer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Type safety ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค






๊ทธ๋ฆฌ๊ณ  DataStore๋Š”...

๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋‘ ๊ฐ€์ง€ ํƒ€์ž… ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๋Š”๋ฐ

  • Preference DataStore : ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด Key-Value Pair์„ ์‚ฌ์šฉํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฑด Type safety ํ•˜์ง€ ๋ชปํ•จ

  • Proto DataStore : ๋ฐ์ดํ„ฐ๋ฅผ Protocol Buffer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ํƒ€์ž…์˜ ํ˜•ํƒœ๋กœ ์ €์žฅํ•œ๋‹ค (๋‹ค์Œ ํฌ์ŠคํŠธ์—์„œ ๋‹ค๋ฃฐ ์˜ˆ์ •)

DataStore์— ๋Œ€ํ•œ ์†Œ๊ฐœ๋Š” ์ด์ •๋„๋ฉด ์ถฉ๋ถ„ํ•œ ๊ฒƒ ๊ฐ™๊ณ  ์ด์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž






์ฝ”๋”ฉ ์‹œ์ž‘

DataStore์— ๊ด€ํ•œ ์˜ˆ์ œ๋Š” ์ด Repository์—์„œ ํด๋ก ํ•˜๊ฑฐ๋‚˜ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ์ž์˜ UI ๋ชจ๋“œ๋ฅผ (Ex : ๐ŸŒž Light Mode ๋˜๋Š” ๐ŸŒ‘ Dark Mode) ์ €์žฅํ•˜๋Š” ์ƒ˜ํ”Œ Android ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ๊ฒƒ์ด๋‹ค.

dependencies {
    // Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha08"
}

๋จผ์ € ์•ฑ ๋ชจ๋“ˆ์˜ build.gradle์— Gradle ์ข…์†์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.



First of all, letโ€™s add a Gradle dependency in build.gradle of your app module. Currently 1.0.0-alpha01 is the latest release. You can keep an eye here to get info about the latest version.

์›๊ธ€์ด ์ž‘์„ฑ๋œ ์‹œ์ ์—๋Š” 1.0.0-alpha01์ด ์ตœ์‹  ๋ฒ„์ „์ด์˜€์ง€๋งŒ ํ˜„์žฌ๋Š” 1.0.0-alpha08์ด ์ตœ์‹ ๋ฒ„์ „์ด๋‹ค. ๊ฐ€์žฅ ์ตœ์‹ ๋ฒ„์ „์€ ์—ฌ๊ธฐ์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค






DataStore ๊ตฌํ˜„ํ•˜๊ธฐ

Dark๋‚˜ Light๊ฐ™์€ UI ๋ชจ๋“œ๋ฅผ ์œ„ํ•œ enum class๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ์ƒ์„ฑํ•ด์ค€๋‹ค

enum class UiMode {
    LIGHT, DARK
}

๊ทธ ๋‹ค์Œ์— SettingsManager๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์ค„๊ฑด๋ฐ ์ด ํด๋ž˜์Šค๋Š” ์•ฑ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ฐ’์„ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ์—ญํ• 

class SettingsManager(context: Context) {

    private val dataStore = context.createDataStore(name = "settings_pref")
    ...

์ด ํด๋ž˜์Šค๋Š” dataStore ํ•„๋“œ๋ฅผ DataStore๋ฅผ ์ด์šฉํ•ด settings_pref ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ดˆ๊ธฐํ™”๋œ๋‹ค.

createDataStore()๋Š” context์˜ ์ต์Šคํ…์…˜ ๋ฉ”์„œ๋“œ




์ด์ œ UI ๋ชจ๋“œ๋ฅผ ํ‚ค๋ฅผ ์ด์šฉํ•˜์—ฌ (SharedPreference์ฒ˜๋Ÿผ) ์„ค์ •ํ• ๊ฑด๋ฐ ํ‚ค๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ์ƒ์„ฑ๋˜์–ด์žˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ SettingsManager ํด๋ž˜์Šค ๋‚ด๋ถ€์— ์ž‘์„ฑํ•ด์•ผํ•จ

companion object {
    val IS_DARK_MODE = preferencesKey<Boolean>("dark_mode")
}

๐Ÿ‘† ์ด๊ฒƒ์ด ์ƒ์„ฑ๋œ ํ‚ค์ธ IS_DARK_MODE๊ณ  ์ด๊ฑด boolean ๊ฐ’์œผ๋กœ ์ €์žฅ๋˜๋Š”๋ฐ false๋ฉด Light mode, true๋ฉด Dark mode. ์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” DataStore๋Š” ์Šคํ‚ค๋งˆ๋ฅผ ๋ฏธ๋ฆฌ ์ •์˜ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด๊ณ 

DataStore<Preferences>์— ์ €์žฅํ•ด์•ผํ•˜๋Š” ๊ฐ ๊ฐ’์— ๋Œ€ํ•œ ํ‚ค๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ Preferences.preferencesKey()๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.




์ด์ œ UI/Acitivty์—์„œ UI ๋ชจ๋“œ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์ฐจ๋ก€์ธ๋ฐ

์ฐธ๊ณ  : Preferences DataStore๋Š” DataStore์˜ ๊ฐ’์„ transactionalํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๋Š” edit() ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค

suspend fun setUiMode(uiMode: UiMode) {
    dataStore.edit { preferences ->
        preferences[IS_DARK_MODE] = when (uiMode) {
            UiMode.LIGHT -> false
            UiMode.DARK -> true
        }
    }
}

์ด์ œ ์„ค์ •์„ ๊ฐ€์ ธ์™€์•ผํ•˜๋Š”๋ฐ DataStore๋Š” Flow๋ฅผ ์ด์šฉํ•ด์„œ ์„ค์ • ๊ฐ’์„ ๋“œ๋Ÿฌ๋‚ด์ฃผ๋Š” data๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

Flow๋ฅผ ํ™œ์šฉํ•  ๋•Œ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐ ๐Ÿ‘‡

val uiModeFlow: Flow<UiMode> = dataStore.data
    .catch {
        if (it is IOException) {
            it.printStackTrace()
            emit(emptyPreferences())
        } else {
            throw it
        }
    }
    .map { preference ->
        val isDarkMode = preference[IS_DARK_MODE] ?: false

        when (isDarkMode) {
            true -> UiMode.DARK
            false -> UiMode.LIGHT
        }
    }

๐Ÿ‘† Flow๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. uiModeFlow ํ•„๋“œ๋Š” ์„ค์ •์ด ํŽธ์ง‘/์—…๋ฐ์ดํŠธ ๋  ๋•Œ๋งˆ๋‹ค ๊ฐ’์„ ๋‚ด ๋ณด๋ƒ„. ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์— boolean์„ ์ €์žฅํ•œ๋‹ค.

map {}์„ ์‚ฌ์šฉํ•˜์—ฌ boolean ๊ฐ’์„ Ui ๋ชจ๋“œ, ์ฆ‰ UiMode.LIGHT ๋˜๋Š” UiMode.DARK์— ๋งคํ•‘ํ•œ๋‹ค.

์ฐธ๊ณ  : DataStore๋Š” ๊ฐ’ ์ฝ๊ธฐ์— ์‹คํŒจํ•˜๋ฉด IOException์„ ๋ฐœ์ƒ์‹œํ‚ด. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” emptyPreferences ()๋ฅผ ๋ฐฉ์ถœํ•จ์œผ๋กœ์จ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

์ด๊ฒƒ์ด DataStore ์„ค์ •์— ๊ด€ํ•œ ๋ชจ๋“  ๊ฒƒ์ด๋‹ค ๐Ÿ˜ƒ, ์ด์ œ UI๋ฅผ ๋””์ž์ธ ํ•ด๋ณด๋ฉด






์•กํ‹ฐ๋น„ํ‹ฐ ์„ค์ •

์•กํ‹ฐ๋น„ํ‹ฐ์—๋Š” UI ๋ชจ๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์ด๋ฏธ์ง€ ๋ฆฌ์†Œ์Šค, ์ฆ‰ ๐ŸŒž ๋ฐ ๐ŸŒ˜์ด์žˆ๋Š” ImageButton ๋งŒ ์žˆ๊ณ 

class MainActivity : AppCompatActivity() {

    private lateinit var settingsManager: SettingsManager
    private var isDarkMode = true

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        settingsManager = SettingsManager(applicationContext)

        observeUiPreferences()
        initViews()
    }



initViews()์—์„œ๋Š” ImageButton์„ ํด๋ฆญํ•  ๋•Œ UI ๋ชจ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ์ฒ˜๋ฆฌ๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค.

private fun initViews() {
    imageButton.setOnClickListener {
        lifecycleScope.launch {
            when (isDarkMode) {
                true -> settingsManager.setUiMode(UiMode.LIGHT)
                false -> settingsManager.setUiMode(UiMode.DARK)
            }
        }
    }
}



observeUiPreferences()์—์„œ๋Š” ์„ค์ •์ด ์—…๋ฐ์ดํŠธ ๋  ๋•Œ๋งˆ๋‹ค ๊ฐ’์„ ๋‚ด๋ณด๋‚ด๋Š” Flow๐ŸŒŠ ์ธ SettingsManager์— ์กด์žฌํ•˜๋Š” uiModeFlow ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UI ๋ชจ๋“œ ๊ธฐ๋ณธ ์„ค์ •์„ observeํ•œ๋‹ค.

private fun observeUiPreferences() {
    settingsManager.uiModeFlow.asLiveData().observe(this) { uiMode ->
        when (uiMode) {
            UiMode.LIGHT -> onLightMode()
            UiMode.DARK -> onDarkMode()
        }
    }
}

๐Ÿ‘† LiveData์˜ Flow์—์„œ ๋ฐฉ์ถœ ๋œ ๊ฐ’์„ ์ œ๊ณตํ•˜๋Š” flow ์ต์Šคํ…์…˜์ธ asLiveData()๊ฐ€ ์žˆ๋‹ค

UI ๋ชจ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ด๋ฏธ์ง€ ๋ฆฌ์†Œ์Šค์™€ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ปฌ๋Ÿฌ๋งŒ ์—…๋ฐ์ดํŠธํ•จ. (์‹ค์ œ Dark/Light ๋ชจ๋“œ๋Š” AppCompatDelegate.setDefaultNightMode()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.)

์ด์ œ ์•ฑ์„ ์‹คํ–‰ํ•  ์‹œ๊ฐ„์ด๋‹ค ๐Ÿš€. ์ด ์•ฑ์„ ์‹คํ–‰ํ•˜๋ฉด ์•„๋ž˜๊ฐ™์€ ํ™”๋ฉด์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ

๋ฉ‹์ง€๋‹ค! ๐Ÿ˜, ๊ทธ๋ ‡์ง€ ์•Š๋‚˜์š”?

์ด๊ฒƒ์ด SharedPreferences ๋Œ€์‹  Preferences DataStore๋ฅผ ๊ตฌํ˜„ ํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค.

DataStore๋Š” ํ˜„์žฌ ์•ŒํŒŒ ๋ฒ„์ „์ด๋ฏ€๋กœ ์•ž์œผ๋กœ ๋” ๋งŽ์€ ๋ฒ„์ „์ด ์ถœ์‹œ ๋  ์˜ˆ์ •์ด๋‹ค ๐Ÿ›ฃ๏ธ.

DataStore๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ํŒŒ์ผ ๊ด€๋ฆฌ์ž์˜ ๋งค์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ SharedPreferences์—์„œ ๊ด€๋ฆฌ๋˜๋Š” ๊ฒƒ ๊ณผ๋Š” ๋‹ค๋ฅด๋‹ค.

๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ €์žฅ๋˜๋Š”์ง€ ์•Œ๊ณ  ์‹ถ์œผ๋ฉด ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค์˜ Device File Explorer๋ฅผ ์ด์šฉํ•˜๋ฉด ๋˜๋Š”๋ฐ /data/app/YOUR_APP_PACKAGE_NAME/files/datastore ์ด ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ณ  ์•„๋ž˜์˜ ํŒŒ์ผ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ settings_perf.preferences_pb ํŒŒ์ผ์€ ์•„๋ž˜์ฒ˜๋Ÿผ ์ฝ์„ ์ˆ˜ ์—†๋‹ค

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