Jetpack Datastore는 프로토콜 버퍼를 사용하여 키-값 쌍 또는 유형이 지정된 객체를 저장할 수 있는 데이터 저장소 솔루션입니다. Datastore는 Kotlin 코루틴 및 Flow를 사용하여 비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장합니다.
현재 SharedPreferences를 사용하여 데이터를 저장하고 있다면 대신 Datastore로 이전하는 것이 좋습니다.
비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장하여 SharedPreferences의 일부 단점을 극복합니다. - Android Developer
= SharePreferences를 대체하는 Data Storage Solution
요약해서, SharedPreferences 대신 Datastore를 사용해야하는 이유는?
DataStore은 Dispatcher.IO
를 내부적으로 사용하기 때문에 UI 스레드(메인 스레드)에서 호출하는 것이 안전하다. (main-safety)
Runtime Exception으로부터 안정적
변경 사항을 저장하기 위해 apply()
또는 commit()
기능을 사용할 필요가 없다.
데이터 업데이트를 트랜잭션 방식으로 처리한다.
Kotlin Flow 및 LiveData와도 communication 가능한 라이브러리로 보다 정교한 옵션으로 사용 가능하다. (ex. 데이터의 현재 상태를 나타내는 Flow를 노출함)
키를 사용하여 데이터를 저장하고 데이터에 접근합니다. 이 구현은 type 안정성을 제공하지 않으며 사전 정의된 스키마가 필요하지 않습니다.
맞춤 데이터 유형의 인스턴스로 데이터를 저장합니다. 이 구현은 type 안정성을 제공하며 프로토콜 버퍼를 사용하며 스키마를 정의해야 합니다.
// Preferences DataStore (SharedPreferences like APIs)
dependencies {
implementation "androidx.datastore:datastore-preferences:1.0.0"
// optional - RxJava2 support
implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"
// optional - RxJava3 support
implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0"
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
implementation "androidx.datastore:datastore-preferences-core:1.0.0"
}
물론 core artifact를 사용한다면 Proguard 규칙을
proguard-rules.pro
파일에 직접 추가하여 필드가 삭제되지 않도록 해야 합니다.
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>;
}
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
최상위 수준에서 인스턴트 한 번 호출한 후 싱글톤으로 DataStore 유지. RxJava를 사용하는 경우 RxPreferenceDataStoreBuilder
를 사용합니다.
DI를 사용한다면 아래와 같이 lifecycle은 @Singleton
으로 선언 후 사용할 수도 있습니다.
@Singleton
@Provides
fun provideDataStore(@ApplicationContext app: Context
) : DataStore = app.dataStore
이 때 name은 필수 파라미터 입니다.
DataStore<Preferences>
인스턴스에 저장해야 하는 각 값의 키를 정의하려면 선언하고자 하는 키 유형 함수를 맞게 사용해야 합니다.
예를 들어 INT 값의 키를 정의하려면 inPreferencesKey()
를 사용하면 됩니다. 그런 다음 DataStore.data 속성을 사용하여 Flow를 사용한 적절한 저장 값을 노출합니다.
stringPreferencesKey(key)
booleanPreferencesKey(key)
doublePreferencesKey(key)
floatPreferencesKey(key)
longPreferencesKey(key)
readExample.kt
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
Preferences DataStore는 DataStore
의 데이터를 transactionally으로 업데이트하는 edit()
함수를 제공합니다. 함수의 transform 매개변수는 필요에 따라 값을 업데이트할 수 있는 코드 block을 허용합니다. transform block 안에 있는 모든 코드는 단일 트랜잭션으로 취급됩니다.
suspend fun incrementCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
suspend fun save(key: String,value: String){
val dataStoreKey = stringPreferencesKey(key)
this.dataStore.edit { settings ->
settings[dataStoreKey] = value
}
}
suspend fun read(key: String): String {
val dataStoreKey = stringPreferencesKey(key)
val preferences = dataStore.data.first()
return preferences[dataStoreKey] ?: "default"
}
CoroutineScope(Dispatchers.Main).launch{
var isTheme = read("THEME")
Log.d("TAG", "onCreate: themevalue $isTheme")
save("THEME","2")
isTheme = read("THEME")
Log.d("TAG", "onCreate: themevalue after save $isTheme")
}
D/TAG: onCreate: themevalue default
D/TAG: onCreate: themevalue after save 2
https://developer.android.com/topic/libraries/architecture/datastore#prefs-vs-proto
https://proandroiddev.com/lets-explore-jetpack-datastore-in-android-621f3564b57
https://we-launch.com/3-main-steps-to-learn-jetpack-datastore-preference/
https://medium.com/scalereal/hello-datastore-bye-sharedpreferences-android-f46c610b81d5
https://developer.android.google.cn/codelabs/android-preferences-datastore?hl=ko#0