- DataStore
Protocol Buffer๋ฅผ ์ฌ์ฉํ์ฌ ํค-๊ฐ ์ ๋๋ Type(์ ํ)์ด ์ง์ ๋ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ ์ ์๋ ๋ฐ์ดํฐ ์ ์ฅ์์ด๋ค.
DataStore๋ Kotlin Coroutine ๋ฐ Flow๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ์ ์ด๊ณ ์ผ๊ด๋ ํธ๋์ญ์
๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค.
DataStore๋ ์๊ท๋ชจ ๋จ์ ๋ฐ์ดํฐ ์
์ ์ ํฉํ๋ฉฐ, ๋ถ๋ถ์
๋ฐ์ดํธ๋ ์ฐธ์กฐ ๋ฌด๊ฒฐ์ ์ ์ง์ํ์ง ์๋๋ค.
๋ณต์กํ ๋๊ท๋ชจ ๋ฐ์ดํฐ ์
, ๋ถ๋ถ ์
๋ฐ์ดํธ, ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ ๋ฑ์ ์ง์ํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ DataStore๋์ Room์ ์ฌ์ฉํ๋๊ฒ์ด ์ข๋ค.
- Preferences DataStore : ํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๋ฐ์ดํฐ์ ์์ธ์คํ๋ค. ํ์ ์์ ์ฑ์ ์ ๊ณตํ์ง ์์ผ๋ฉฐ ์ฌ์ ์ ์๋ ์คํค๋ง๊ฐ ํ์ํ์ง ์๋ค
- Proto DataStore : ๋ง์ถค ๋ฐ์ดํฐ ํ์ (Type)์ ์ธ์คํด์ค๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค. ํ์ ์์ ์ฑ์ ์ ๊ณตํ๋ฉฐ Protocol Buffer๋ฅผ ์ฌ์ฉํ์ฌ ์คํค๋ง๋ฅผ ์ ์ํด์ผ ํ๋ค
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์ ์์ธ์ด ๋๋ค.
- 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"
}
private const val USER_PREFERENCES_NAME = "user_preferences"
val Context.dataStore by preferencesDataStore(name = USER_PREFERENCES_NAME)
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,
)
}
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
}
}
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 ์ธ์ด ๊ฐ์ด๋
// 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 ํ์ผ์ ์ปดํ์ผํ์์ ์์ฑ๋๋ฏ๋ก ํ๋ก์ ํธ ์ฌ๋น๋๋ฅผ ํด์ค๋ค.
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)
}
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
)
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
}
}
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()
}
}