Skip to content

Latest commit

ย 

History

History
299 lines (254 loc) ยท 11 KB

data_store.md

File metadata and controls

299 lines (254 loc) ยท 11 KB

DataStore

Index

DataStore ๋ž€ ?

Protocol Buffer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‚ค-๊ฐ’ ์Œ ๋˜๋Š” Type(์œ ํ˜•)์ด ์ง€์ •๋œ ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์ด๋‹ค.
DataStore๋Š” Kotlin Coroutine ๋ฐ Flow๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ์ ์ด๊ณ  ์ผ๊ด€๋œ ํŠธ๋žœ์žญ์…˜ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•œ๋‹ค.
DataStore๋Š” ์†Œ๊ทœ๋ชจ ๋‹จ์ˆœ ๋ฐ์ดํ„ฐ ์…‹์— ์ ํ•ฉํ•˜๋ฉฐ, ๋ถ€๋ถ„์—…๋ฐ์ดํŠธ๋‚˜ ์ฐธ์กฐ ๋ฌด๊ฒฐ์ •์€ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
๋ณต์žกํ•œ ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์…‹, ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ, ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ๋“ฑ์„ ์ง€์›ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” DataStore๋Œ€์‹  Room์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

DataStore ์ข…๋ฅ˜

  • Preferences DataStore : ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ฐ์ดํ„ฐ์— ์—‘์„ธ์Šคํ•œ๋‹ค. ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉฐ ์‚ฌ์ „ ์ •์˜๋œ ์Šคํ‚ค๋งˆ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค
  • Proto DataStore : ๋งž์ถค ๋ฐ์ดํ„ฐ ํƒ€์ž…(Type)์˜ ์ธ์Šคํ„ด์Šค๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•œ๋‹ค. ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์ œ๊ณตํ•˜๋ฉฐ Protocol Buffer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค

SharedPreference vs PreferenceDataStore vs PhotoDataStore

SharedPreference PreferencesDataStore ProtoDataStore
Async(๋น„๋™๊ธฐ)์ฒ˜๋ฆฌ โœ… (Listener๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ์ฝ๋Š” ๊ฒฝ์šฐ๋งŒ ๊ฐ€๋Šฅ) โœ… (Coroutine Flow, RxJava2, RxJava3 ์‚ฌ์šฉ๊ฐ€๋Šฅ) โœ… (Coroutine Flow, RxJava2, RxJava3 ์‚ฌ์šฉ๊ฐ€๋Šฅ)
๋™๊ธฐ์ฒ˜๋ฆฌ โœ… (๊ทธ๋Ÿฌ๋‚˜ UI Thread์—์„œ ํ˜ธ์ถœํ•˜๊ธฐ์— ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ) โŒ โŒ
UI Thread์—์„œ ํ˜ธ์ถœ์ด ์•ˆ์ „ โŒ (1) โœ… (Dispatchers.IO ์—์„œ ์ž‘์—…) โœ… (Dispatchers.IO ์—์„œ ์ž‘์—…)
์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Œ โŒ โœ… โœ…
๋Ÿฐํƒ€์ž„ Exception์—์„œ ์•ˆ์ „ํ•จ โŒ โœ… โœ…
๊ฐ•๋ ฅํ•œ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๋Š” Transaction API๊ฐ€ ์žˆ์Œ โŒ โœ… โœ…
๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€๋Šฅ์—ฌ๋ถ€ โŒ โœ… โœ…
Type ์•ˆ์ •์„ฑ(Type๋ณด์žฅ) โŒ โŒ โœ…

(1) SharedPreferences๋Š” UI Thread์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” Disk I/O ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋™๊ธฐ API๊ฐ€ ์žˆ๋‹ค.
๋˜ํ•œ apply()์—์„œ UI Thread๋ฅผ fsync()์—์„œ ์ฐจ๋‹จํ•œ๋‹ค.
๋ณด๋ฅ˜์ค‘์ธ fsync() ํ˜ธ์ถœ์€ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ ๋ชจ๋“  ์œ„์น˜์—์„œ SharedPreferences์˜ ์ž‘์—…์ด apply()start, stop๋  ๋•Œ ๋งˆ๋‹ค ํŠธ๋ฆฌ๊ฑฐ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์ข…์ข… apply()์— ์˜ํ•ด ์˜ˆ์•ฝ๋œ ๋ณด๋ฅ˜์ค‘์ธ fsync()ํ˜ธ์ถœ์‹œ์— ANR์˜ ์›์ธ์ด ๋œ๋‹ค.

PreferencesDataStore

ํŠน์ง•

  • Transaction ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์ฒ˜๋ฆฌ
  • ๋ฐ์ดํ„ฐ์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๋…ธ์ถœํ•œ๋‹ค
  • apply() commit() ๋ฉ”์†Œ๋“œ๊ฐ€ ์—†๋‹ค
  • ๋‚ด๋ถ€ ์ƒํƒœ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ๊ฐ€๋Šฅํ•œ ์ฐธ์กฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ

์„ค์ •

Preferences DataStore

// .gradle(module)
// Preferences DataStore (SharedPreferences like APIs)
dependencies {
  implementation "androidx.datastore:datastore-preferences:1.0.0-alpha07"

  // optional - RxJava2 support
  implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0-alpha07"

  // optional - RxJava3 support
  implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0-alpha07"
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
  implementation "androidx.datastore:datastore-preferences-core:1.0.0-alpha07"
}

Preferences DataStore ๋งŒ๋“ค๊ธฐ

private const val USER_PREFERENCES_NAME = "user_preferences"

val Context.dataStore by preferencesDataStore(name = USER_PREFERENCES_NAME)

Preferences DataStore ์ฝ๊ธฐ

enum class SortOrder {
    NONE,
    BY_DEADLINE,
    BY_PRIORITY,
    BY_DEADLINE_AND_PRIORITY
}

object PreferencesKeys {
    val SHOW_COMPLETED = booleanPreferencesKey("show_completed ")
    val SORT_ORDER = stringPreferencesKey("sort_order")
}
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .catch { exception ->
        if (exception is IOException) {
            emit(emptyPreferences())
        } else {
            throw exception
        }
    }
    .map { preferences ->
        val sortOrder = 
            SortOrder.valueOf(preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name)
        val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED] ?: false
        UserPreferences(
            showCompleted = showCompleted,
            sortOrder = sortOrder,
        )
    }

Preferences DataStore์— ์“ฐ๊ธฐ

suspend fun enableSortByDeadline(enable: Boolean) {
    dataStore.edit { preferences ->
        val currentOrder = SortOrder.valueOf(
            preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name
        )

        val newSortOrder =
            if (enable) {
                if (currentOrder == SortOrder.BY_PRIORITY) {
                    SortOrder.BY_DEADLINE_AND_PRIORITY
                } else {
                    SortOrder.BY_DEADLINE
                }
            } else {
                if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
                    SortOrder.BY_PRIORITY
                } else {
                    SortOrder.NONE
                }
            }
        preferences[PreferencesKeys.SORT_ORDER] = newSortOrder.name
    }
}

ProtoDataStore

Overview

SharedPreferences ๋ฐ Preferences DataStore์˜ ๋‹จ์  ์ค‘ ํ•˜๋‚˜๋Š” ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜๊ฑฐ๋‚˜ ํ‚ค๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์œ ํ˜•์œผ๋กœ ์•ก์„ธ์Šค๋˜๋Š”์ง€ ํ™•์ธํ•  ๋ฐฉ๋ฒ•์ด ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
ProtoDataStore๋Š” ํ”„๋กœํ† ์ฝœ ๋ฒ„ํผ ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ ํ•จ์œผ๋กœ์จ์ด ๋ฌธ์ œ๋ฅผ ํ•œ๋‹ค.
Protobufs๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด DataStore๋Š” ์–ด๋–ค ์œ ํ˜•์ด ์ €์žฅ๋˜๋Š”์ง€ ์•Œ๊ณ  ์žˆ์œผ๋ฉฐ์ด๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ๋งŒํ•˜๋ฉด ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

ํŠน์ง•

Protocol Buffer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•œ๋‹ค.
Protobufs๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ•๋ ฅํ•œ Type ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
XML ๋ฐ ๊ธฐํƒ€ ์œ ์‚ฌํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹๋ณด๋‹ค ๋” ๋น ๋ฅด๊ณ , ์ž‘๊ณ , ๋‹จ์ˆœํ•˜๋ฉฐ, ๋ชจํ˜ธํ•˜์ง€ ์•Š๋‹ค.
Proto DataStore์—์„œ๋Š” ์ƒˆ๋กœ์šด ์ง๋ ฌํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋ฐฐ์›Œ์•ผํ•˜์ง€๋งŒ Proto DataStore๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ์œ ํ˜•์˜ ์ด์ ์€ ๊ทธ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ๋‹ค.

์„ค์ •

Proto DataStore

plugins {
    ...
    id "com.google.protobuf" version "0.8.12"
}

dependencies {
    implementation  "androidx.datastore:datastore-core:1.0.0-alpha08"
    implementation  "com.google.protobuf:protobuf-javalite:3.10.0"
    ...
    // optional - RxJava2 support
    implementation "androidx.datastore:datastore-rxjava2:1.0.0-alpha07"

    // optional - RxJava3 support
    implementation "androidx.datastore:datastore-rxjava3:1.0.0-alpha07"
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.0"
    }

    // Generates the java Protobuf-lite code for the Protobufs in this project. See
    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
    // for more information.
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}

Proto ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ

proto ํŒŒ์ผ์—์„œ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•ด์•ผํ•œ๋‹ค. Proto ์–ธ์–ด ๊ฐ€์ด๋“œ

// user_prefs.proto ํŒŒ์ผ ์ƒ์„ฑ


syntax = "proto3";

option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;

message UserPreferences {
  bool show_completed = 1;

  enum SortOrder {
    UNSPECIFIED = 0;
    NONE = 1;
    BY_DEADLINE = 2;
    BY_PRIORITY = 3;
    BY_DEADLINE_AND_PRIORITY = 4;
  }

  SortOrder sort_order = 2;
}

proto ํŒŒ์ผ์€ ์ปดํŒŒ์ผํƒ€์ž„์— ์ƒ์„ฑ๋˜๋ฏ€๋กœ ํ”„๋กœ์ ํŠธ ์žฌ๋นŒ๋“œ๋ฅผ ํ•ด์ค€๋‹ค.

Serialize ๋ณ€ํ™˜๊ธฐ ๋งŒ๋“ค๊ธฐ

DataStore์— proto ํŒŒ์ผ์—์„œ ์ •์˜ํ•œ ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์ฝ๊ณ  ์“ฐ๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค์ฃผ๋ ค๋ฉด Serializer๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.
Serializer๋Š” ๋””์Šคํฌ์— ๋ฐ์ดํ„ฐ๊ฐ€์—†๋Š” ๊ฒฝ์šฐ ๋ฐ˜ํ™˜๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’๋„ ์ •์˜ํ•œ๋‹ค.

object UserPreferencesSerializer : Serializer<UserPreferences> {
    override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
    override suspend fun readFrom(input: InputStream): UserPreferences {
        try {
            return UserPreferences.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}

Proto DataStore ๋งŒ๋“ค๊ธฐ

private const val USER_PREFERENCES_NAME = "user_preferences"
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"
private const val SORT_ORDER_KEY = "sort_order"

private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = UserPreferencesSerializer
)

Proto DataStore ์ฝ๊ธฐ

private val TAG: String = "UserPreferencesRepo"

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .catch { exception ->
        if (exception is IOException) {
            Log.e(TAG, "Error reading sort order preferences.", exception)
            emit(UserPreferences.getDefaultInstance())
        } else {
            throw exception
        }
    }

Proto DataStore ์“ฐ๊ธฐ

  suspend fun enableSortByDeadline(enable: Boolean) {
    userPreferencesStore.updateData { currentPreferences ->
        val currentOrder = currentPreferences.sortOrder
        val newSortOrder =
            if (enable) {
                if (currentOrder == SortOrder.BY_PRIORITY) {
                    SortOrder.BY_DEADLINE_AND_PRIORITY
                } else {
                    SortOrder.BY_DEADLINE
                }
            } else {
                if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
                    SortOrder.BY_PRIORITY
                } else {
                    SortOrder.NONE
                }
            }
        currentPreferences.toBuilder().setSortOrder(newSortOrder).build()
    }
}