이번 영상에서는 DataStore에 대해 알아보도록 하겠습니다.
데이터를 앱에 저장하는 방법으로는 크게 세가지를 들 수 있습니다.
SharedPreferences는 보통 복잡한 데이터를 기록하기보다는, 앱의 환경설정과 같이 단순한 내용을 저장하는데 적절한 저장공간이라고 생각하면 됩니다.
SharedPreferences를 사용하기 위해서는 우선 getSharedPreferences(KEY, MODE)로 인스턴스를 생성해야 합니다. 이 때 선택가능한 MODE에는 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITABLE, MODE_MULTI_PROCESS가 있는데, 일반적으로 작성한 앱에서만 접근 가능하게 하는 MODE_PRIVATE 를 사용하면 됩니다.
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
인스턴스가 생성되었으면 데이터를 기록하면 됩니다. edit를 써서 기록할 데이터를 메모리에 올린 뒤 commit 혹은 apply를 이용해서 xml 파일에 기록하게 됩니다.
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
putInt(getString(R.string.saved_high_score_key), newHighScore)
apply()
}
그리고 값을 읽어올 때는 get 명령을 키와 함께 사용하면 됩니다.
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val defaultValue = resources.getInteger(R.integer.saved_high_score_default_key)
val highScore = sharedPref.getInt(getString(R.string.saved_high_score_key), defaultValue)
하지만 SharedPreferences는 스레드나 타입을 다루는 방법이 구조적으로 부족한 부분이 있었기에 구글에서는 이런 단점들을 개선한 DataStore를 발표했습니다.
DataStore에는 Preferences DataStore와 Proto DataStore가 있는데요, 각각의 특징은 다음과 같습니다.
Preferences DataStore는 키를 사용하여 데이터를 저장하고 데이터에 액세스합니다. 이 구현은 유형 안전성을 제공하지 않으며 사전 정의된 스키마가 필요하지 않습니다.
Proto Datastore는 맞춤 데이터 유형의 인스턴스로 데이터를 저장합니다. 이 구현은 유형 안전성을 제공하며 프로토콜 버퍼를 사용하여 스키마를 정의해야 합니다.
All about Preferences DataStore의 Defining keys 문단을 보면 다음과 같은 표현을 하고 있습니다. Preferences Datastore는 타입세이프가 아니며 단지 지정한 타입이 반환될 것이라고 '기대'한다는 이상한 표현을 사용하고 있지요.
While this does put some constraints on data types, keep in mind that it doesn’t provide definite type safety. By specifying a preference key of a certain type, we hope for the best and rely on our assumptions that a value of a certain type would be returned.
Preferences.Key를 사용하면 타입을 관리해 주는 것 같은데 왜 이런 표현을 사용했을까요? Get Your Hand Dirty With Jetpack Datastore에 따르면 Preference Datastore의 데이터는 json처럼 저장이 됩니다. 저장된 시점에서 타입 정보가 사라졌다가 읽어들일 때 다시 복원을 하는 것이기에 타입이 잘 복원될 것이라고 '기대'는 하지만 타입 세이프는 아니라는 것이죠.
1 {
1: "app_name"
2 {
5: "Datastore sample"
}
}
1 {
1: "is_demo_mode"
2 {
1: 1
}
}
하지만 Proto Datastore는 Protocol Buffers라는 것을 이용해서 저장할 데이터의 타입 정보를 스키마로 만들어 놓고 타입 정보 그 자체를 저장하는 방식을 사용합니다. 단순 원시 타입이 아닌 우리가 커스텀한 타입을 저장하고 싶으면서 높은 수준의 데이터 무결성이 요구되는 경우에 Proto Datastore를 사용한다고 생각하시면 됩니다.
SharedPreferences와 DataStore의 차이를 다음 표에 정리했습니다. 기본적으로는 Preferences DataStore가 SharedPreferences를 대체한다고 생각하시면 됩니다.
| Feature | SharedPreferences | PreferencesDataStore | ProtoDataStore |
|---|---|---|---|
| Async API | ✅ (only for reading changed values, via listener) | ✅ (via Flow and RxJava 2 & 3 Flowable) | ✅ (via Flow and RxJava 2 & 3 Flowable) |
| Synchronous API | ✅ (but not safe to call on UI thread) | ❌ | ❌ |
| Safe to call on UI thread | ❌1 | ✅ (work is moved to Dispatchers.IO under the hood) | ✅ (work is moved to Dispatchers.IO under the hood) |
| Can signal errors | ❌ | ✅ | ✅ |
| Safe from runtime exceptions | ❌2 | ✅ | ✅ |
| Has a transactional API with strong consistency guarantees | ❌ | ✅ | ✅ |
| Handles data migration | ❌ | ✅ | ✅ |
| Type safety | ❌ | ❌ | ✅ with Protocol Buffers |
내용을 보시면 스레드나 예외처리 부분에서 DataStore가 더 개선된 것을 알 수 있는데요, 이런 개선이 이루어진 이유는 DataStore가 데이터 처리에 Flow를 도입했기 때문입니다.
이렇게 해서 DataStore에 대해 알아보았습니다.