DataStore는 SharedPreferences를 대체하기 위해서 고안되었다.
SharedPreferences를 대체해야 겠다는 생각을 구글개발자들은 왜 했을까?
1. SharedPreferences는 UIThread에서 호출이 가능한데 기본적으로 disk I/O operation이기 때문에 UITHread에 대해 block을 유발하게 된다. 때에 따라서는 이점이 anr을 유발할수도 있다.
2. 런타임 파싱에러를 발생시킬 수 있다.
이런 문제점이 마음에 들지 않았던 어느 개발자가 DataStore를 만들었다.
DataStore는 두가지 구현을 제공하는데 모두 파일로 저장하고 Dispatchers.IO에서 동작한다.
1. Preference DataStore는 SharedPreference와 유사하고 key/value쌍으로 저장한다.
2. Proto DataStore는 Protocol buffer로 스키마를 정의하고 강력한 데이타타입체크를 지원한다. protocol buffer는 더 빠르고 작고 단
순하며 다른 XML같은 포맷보다 덜 모호하다. protocol buffer 정의하고 시리얼화 매커니즘을 추가로 배워야 하지만 강력한 타입스키마체크라는 장점때문에 충분히 배울 가치가 있다고 본다.
Room이라는 또 다른 Store를 제공하는 라이브러리가 있으며 DataStore가 작고 단순한 데이터셋을 지원하는 반면 Room은 부분업데이트나 참조무결성을 지원한다. 정확하게 어떤 다른 특성이 있는지는 더 조사해 보아야 겠다.
// Preferences DataStore 사용시
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"
// Proto DataStore 사용시
implementation "androidx.datastore:datastore-core:1.0.0-alpha01"
Proto DataStore를 사용하고자 한다면 스키마를 proto file로 /app/src/main/proto 폴더에 저장한다. 스키마정의 링크에서 자세한 정의 방법을 더 숙지해야 한다.
syntax = "proto3";
option java_package = "<your package name here>";
option java_multiple_files = true;
message Settings {
int my_counter = 1;
}
DataStore 생성
Preference DataStore 생성
// with Preferences DataStore
val dataStore: DataStore<Preferences> = context.createDataStore(
name = "settings"
)
Proto DataStore 생성
protobuff file명과 Serializer의 구현체를 제공해야 한다.
object SettingsSerializer : Serializer<Settings> {
override fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
}
// with Proto DataStore
val settingsDataStore: DataStore<Settings> = context.createDataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
DataStore 읽기
Preference DataStore 읽기
val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
.map { currentPreferences ->
// Unlike Proto DataStore, there's no type safety here.
currentPreferences[MY_COUNTER] ?: 0
}
Proto DataStore 읽기
val myCounterFlow: Flow<Int> = settingsDataStore.data
.map { settings ->
// The myCounter property is generated for you from your proto schema!
settings.myCounter
}
DataStore 쓰기
Preference DataStore 쓰기
suspend fun incrementCounter() {
dataStore.edit { settings ->
// We can safely increment our counter without losing data due to races!
val currentCounterValue = settings[MY_COUNTER] ?: 0
settings[MY_COUNTER] = currentCounterValue + 1
}
}
Proto DataStore 쓰기
suspend fun incrementCounter() {
settingsDataStore.updateData { currentSettings ->
// We can safely increment our counter without losing data due to races!
currentSettings.toBuilder()
.setMyCounter(currentSettings.myCounter + 1)
.build()
}
}
Data를 Modeling할때 DataStore를 사용해서 해보는것도 좋은 경험이 될것 같다.
언제 해볼 수 있을지 모르겠지만 .. ㅠㅜ