10
10

DataStore๋Š” ์ฝ”๋ฃจํ‹ด๊ณผ flow๋ฅผ ์‚ฌ์šฉํ•˜๋Š” SharedPreferences์˜ ๊ฐœ์„ ํŒ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. SharedPreferences๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ณดํ†ต ์„ค์ •๊ฐ’๊ณผ ๊ฐ™์€ ๋‹จ์ˆœ ๋ฐ์ดํ„ฐ ์…‹์ธ key-value๋กœ ๋ฌถ์ด๋Š” ๊ฐ’๋“ค์„ ์ €์žฅํ•˜๋Š”๋ฐ, primitive type๋งŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ๊ณผ, String์œผ๋กœ ๋งŒ๋“  JSON์„ ์ €์žฅํ•œ๋‹ค๊ณ  ์ณ๋„ ๋ถˆ์‹œ์— ๋ฐœ์ƒํ•˜๋Š” ๋Ÿฐํƒ€์ž„์—๋Ÿฌ๋ฅผ ํ”ผํ•  ์ˆ˜ ์—†์—ˆ๋‹ค.

๋˜ํ•œ ์ƒ์„ฑ๋˜๋Š” ํŒŒ์ผ์ด XMLํŒŒ์ผ์ด๊ณ  ์‹ค์ œ ํŒŒ์ผ์ž‘์—…์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— UI ์“ฐ๋ ˆ๋“œ์—์„œ ๋Œ๋ ค๋ฒ„๋ฆฌ๋ฉด ANR์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋Ÿฐํƒ€์ž„์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†ํ›„ํ•˜๋‹ค. ๋น„๋™๊ธฐ ๊ธฐ๋Šฅ๋„ ์œ ์—ฐํ•˜์ง€์•Š๋‹ค. DB์ˆ˜์ค€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋ฉด ๋‹น์—ฐํžˆ Room์„ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

 

DataStore๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ ๊ณ„๊ธฐ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜ค๋Š” access token์„ ์ €์žฅํ•ด๋†”์•ผ api ์š”์ฒญํ•  ๋•Œ ํ—ค๋”์— ํ† ํฐ์„ ๋„ฃ์–ด์„œ ์š”์ฒญํ•˜๋Š”๋ฐ, SharedPreferences๋Š” ํŒŒ์ผ์ด ๋‚จ์•„์žˆ์–ด ์•ˆ์ „ํ•˜์ง€ ์•Š์•„ DataStore๋ฅผ ์„ ํƒํ–ˆ๋‹ค.

 

์ด ๊ธ€์—์„œ SharedPreference -> DataStore ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋‹ค๋ฃจ์ง€ ์•Š๋Š”๋‹ค.

DataStore

DataStore๋Š” Preferences DataStore์™€ Proto DataStore๋กœ ๋‚˜๋‰œ๋‹ค.

์ „์ž๋Š” ๊ธฐ์กด์˜ SharedPreferences์™€ ๊ฑฐ์˜ ๋™์ผํ•œ ๊ธฐ๋Šฅ, ์‚ฌ์šฉ๋ฒ•์„ ๊ฐ–๊ณ , ํ›„์ž๋Š” ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐํƒ€์ž…์„ Protocol Buffer๋ฅผ ํ†ตํ•ด ์ €์žฅํ•œ๋‹ค. ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— Type-Safety๋ฅผ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ์Šคํ‚ค๋งˆ๋ฅผ ๋ฏธ๋ฆฌ ์ง€์ •ํ•ด์•ผ๋œ๋‹ค๋Š” ๊ท€์ฐฎ์Œ์ด ์žˆ๊ธดํ•˜๋‹ค.

์„ค์ •๋ฒ•๊ณผ ๊ฐ™์€ ๊ฑด ๊ณต์‹๋ฌธ์„œ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉด ์•Œ ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ด๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ๋‹ค๋ฃจ์ง„ ์•Š๊ฒ ๋‹ค. ๋˜ํ•œ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Preferences DataStore๊ฐ€ ์‚ฌ์šฉ๋๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฑธ ๋‹ค๋ฃจ๊ฒ ๋‹ค.

Preferences DataStore

DataStore ํด๋ž˜์Šค์™€ Preferencesํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค. Preferencesํด๋ž˜์Šค๋Š” Mapํ˜•์‹์œผ๋กœ DataStore๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํƒ€์ž…์ด๋‹ค.

์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์ธ์Šคํ„ด์Šค ๋งŒ๋“ค๊ธฐ

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "preferences name")
 

์ด ๊ณผ์ •์ด ์ œ์ผ ์ค‘์š”ํ•˜๋‹ค. ์ตœ์ƒ์œ„ ํ”„๋กœํผํ‹ฐ๋กœ data store์ธ์Šคํ„ด์Šค๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋งŒ๋“œ๋Š” ์ž‘์—…์ธ๋ฐ, ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

You should either maintain your DataStore as a singleton or confirm that there is no two DataStore’s active on the same file (by confirming that the scope is cancelled)

๋™์ผํ•œ ์ด๋ฆ„์„ ๊ฐ–๋Š” datastore์ธ์Šคํ„ด์Šค๊ฐ€ ์ค‘๋ณต์œผ๋กœ ์ƒ์„ฑ๋˜์–ด ๋ฒ„๋ฆฐ ๋Œ€์ฐธ์‚ฌ๋‹ค.

์ฝ๊ณ  ์“ฐ๊ธฐ(๋ฉ€ํ‹ฐ๋ชจ๋“ˆ์—์„œ)

์“ธ ๋•Œ๋Š” ์ฝ”๋ฃจํ‹ด์—์„œ, ์ฝ์„ ๋•Œ๋Š” flow๋กœ ์ž‘๋™ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์ƒ˜ํ”Œ์ฝ”๋“œ ๋ง๊ณ  ์‹ค์ œ๋กœ ์ž‘์—…ํ–ˆ๋˜ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€๋ดค๋‹ค.

๋ฉ€ํ‹ฐ๋ชจ๋“ˆ์„ ์‚ฌ์šฉ ์ค‘์ด๊ณ  data layer์— ์ž‘์„ฑ๋œ ์ฝ”๋“œ๋‹ค. ์ „์ฒด ์ฝ”๋“œ ๋จผ์ € ์˜ฌ๋ ค๋†“๊ฒ ๋‹ค. ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ, ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ถ€๋ถ„๋„ ์žˆ์ง€๋งŒ ํŽธ์˜๋ฅผ ์œ„ํ•ด์„œ Access Token๋ถ€๋ถ„๋งŒ ๊ฐ€์ ธ์™”๋‹ค.

@Singleton
val Context.tokenDataStore by preferencesDataStore("tokens")

class TokenRepoImpl @Inject constructor(
    private val api: TokenApi,
    @ApplicationContext private val context: Context
): TokenRepo {

    private object PreferenceKeys {
        val ACCESS_TOKEN = stringPreferencesKey("access_token")
    }

    override suspend fun getAccessToken(): String {
        val userAccessToken: Flow<String?> = context.tokenDataStore.data.map {
            it[ACCESS_TOKEN]
        }
        return userAccessToken.first().toString()
    }
    override suspend fun setAccessToken(accessToken: String) {
        context.tokenDataStore.edit {prefs ->
            prefs[ACCESS_TOKEN] = accessToken
        }
    }
}
 

์ตœ์ƒ์œ„ ํ”„๋กœํผํ‹ฐ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค.

@Singleton
val Context.tokenDataStore by preferencesDataStore("tokens")
 

์—ฌ๊ธฐ์„œ preferencesDataStore์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค์–ด๊ฐ€๋Š” String๊ฐ’์€ ์ผ์ข…์˜ ๋””๋ ‰ํ„ฐ๋ฆฌ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” tokens์ธ๋ฐ, ์•ž์œผ๋กœ tokenDataStore๋กœ ์ ‘๊ทผํ•˜๋Š” key-value ์Œ์€ ๋ชจ๋‘ tokens ๋ฐ‘์— ์ƒ์„ฑ๋œ๋‹ค.

private object PreferenceKeys {
        val ACCESS_TOKEN = stringPreferencesKey("access_token")
    }
 

key-value์Œ์œผ๋กœ ๋“ค์–ด๊ฐˆ ๊ฐ’์˜ Key๋ฅผ ๋‹ด๋Š” ๊ณณ์ด๋‹ค. ๊ทธ๋ƒฅ “access_token”์ด๋ผ๊ณ  ์ง์ ‘ ๊ฐ’์„ ๋„ฃ์–ด๋„ ๋˜์ง€๋งŒ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด object๋กœ ๋ฌถ์–ด์„œ ๋งŒ๋“ค์–ด ์คฌ๋‹ค.

์ง€์›๋˜๋Š” PreferencesKey ์œ ํ˜•์€ ์—ฌ๊ธฐ์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

override suspend fun setAccessToken(accessToken: String) {
        context.tokenDataStore.edit {prefs ->
            prefs[ACCESS_TOKEN] = accessToken
        }
    }
 

datastore์— ์“ฐ๋Š” ๋ถ€๋ถ„์ด๋‹ค. SharedPreferences์™€ ์œ ์‚ฌํ•œ ๋ชจ์Šต์ธ๋ฐ, edit ๋ฉ”์„œ๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํŠธ๋žœ์žญ์…˜ ๋ฐฉ์‹์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค. ๋žŒ๋‹ค๋กœ ๋ฌถ์ธ ๋ถ€๋ถ„์€ ๋‹จ์ผ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ทจ๊ธ‰๋œ๋‹ค. suspend fun์œผ๋กœ ์„ ์–ธ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ์ฝ”๋ฃจํ‹ด์—์„œ ํ˜ธ์ถœํ•ด์•ผํ•œ๋‹ค.

override suspend fun getAccessToken(): String {
        val userAccessToken: Flow<String?> = context.tokenDataStore.data.map {
            it[ACCESS_TOKEN]
        }
        return userAccessToken.first().toString()
    }
 

Flow๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์ฝ๋Š” ์ฝ”๋“œ๋‹ค. first()๋Š” Flow์˜ ์ฒซ๋ฒˆ์งธ ๊ฐ’์„ collectํ•˜๋Š” ๋ฉ”์„œ๋“œ์ธ๋ฐ, ์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๊ฐ’์ด ํ•˜๋‚˜๊ธฐ ๋•Œ๋ฌธ์— first๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค.

HTTPํ†ต์‹  ํ—ค๋”์— ํ† ํฐ ๊ฐ’์„ ๋„ฃ์„ ๋•Œ๋Š” ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•  ๋•Œ ๊ฐ’ ๋ณด์žฅ์ด ์•ˆ๋  ์ˆ˜ ์žˆ์–ด์„œ runBlocking์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

์ฐธ์กฐ:

https://developer.android.com/jetpack/androidx/releases/datastore?authuser=1&hl=ko#:~:text=kotlin%20%ED%8C%8C%EC%9D%BC%EC%9D%98%20%EC%B5%9C%EC%83%81%EC%9C%84%20%EC%88%98%EC%A4%80%EC%97%90%20%EB%B0%B0%EC%B9%98%ED%95%98%EB%A9%B4%20%EC%9D%B4%20%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EA%B0%80%20%ED%95%98%EB%82%98%EB%A7%8C%20%EC%83%9D%EA%B9%81%EB%8B%88%EB%8B%A4.

https://medium.com/androiddevelopers/introduction-to-jetpack-datastore-3dc8d74139e7

https://developer.android.com/topic/libraries/architecture/datastore?authuser=1&hl=ko

"๋Œ“๊ธ€, ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์‹œ๋ฉด ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค"
๋ฐ˜์‘ํ˜•
COMMENT