DataStore
는 프로토콜 버퍼를 사용하여 키-값 쌍 및 커스텀 객체를 저장할 수 있는 데이터 저장 솔루션이다. 내부적으로 Coroutine 및 Flow를 사용하기 때문에 비동기 트랜잭션을 통해 데이터를 일관성 있게 저장할 수 있다. 공식 문서에서는 DataStore
를 규모가 작고 단순한 데이터셋에만 사용하고 반대의 경우에는 Room 라이브러리를 사용하도록 권장하고 있다.
DataStore
는 두 가지 방식으로 구현할 수 있다.
Preferences DataStore
SharedPreferences
와 유사하게 키-값 쌍을 사용하여 데이터를 저장하는 방식이다. primitive 및 간단한 컬렉션 타입만 지원하기 때문에 타입 안전성을 보장하지 않는다.
객체 생성하기
androidx.datastore.preferences
패키지의 preferencesDataStore
함수를 사용하여 DataStore<Preferences>
프로퍼티에 대한 위임을 생성한다. String
파라미터는 DataStore
가 저장되는 파일의 이름을 나타낸다. 이 함수는 Kotlin 파일의 최상위 레벨에서 한 번만 호출해야 하며 이후에 DataStore
를 사용하려면 동일한 인스턴스를 참조해야 한다. 생성된 위임을 소유하는 객체는 Context
인스턴스이어야 하므로 Context
의 확장 프로퍼티로 정의하여 사용한다.
// Kotlin 파일의 최상위 레벨에서 호출하여 싱글톤으로 사용한다.
val Context.myDataStore by preferencesDataStore(name = "my_prefs")
데이터 읽기 및 쓰기
Preferences
의 데이터를 구별할 때 Preferences.Key
를 키로 사용한다. Key
인스턴스를 참조하려면 데이터 타입에 대응하는 함수를 사용해야 한다. 각 함수와 이에 대응하는 데이터 타입은 다음과 같다. String
파라미터는 키의 이름을 나타낸다.
intPreferencesKey(String): Preferences.Key<Int>
longPreferencesKey(String): Preferences.Key<Long>
floatPreferencesKey(String): Preferences.Key<Float>
doublePreferencesKey(String): Preferences.Key<Double>
booleanPreferencesKey(String): Preferences.Key<Boolean>
stringPreferencesKey(String): Preferences.Key<String>
byteArrayPreferencesKey(String): Preferences.Key<ByteArray>
stringSetPreferencesKey(String): Preferences.Key<Set<String>>
데이터를 저장할 때는 DataStore<Preferences>
의 확장 함수인 edit(suspend (MutablePreferences) -> Unit)
을 사용할 수 있다. 파라미터로 전달되는 함수의 각 코드는 단일 트랜잭션으로 간주된다. 이 함수의 소스를 보면 알 수 있듯 DataStore<T>#updateData(suspend (T) -> T)
가 호출된다.
androidx.datastore.preferences.core.Preferences.kt
public suspend fun DataStore<Preferences>.edit(
transform: suspend (MutablePreferences) -> Unit
): Preferences {
this.updateData {
it.toMutablePreferences().apply { transform(this) }
}
}
데이터에 액세스할 때는 DataStore
의 data
프로퍼티와 map
함수를 사용한다.
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
Proto DataStore
DataStore
과 프로토콜 버퍼를 사용하여 타입이 지정된 객체를 디스크에 저장하는 방식이다. Proto DataStore를 사용하려면 app/src/main/proto 디렉터리의 proto 파일에 사용하고자 하는 데이터 타입 스키마를 미리 정의해야 한다.
객체 생성하기
Proto DataStore 인스턴스는 두 단계를 거쳐 생성된다.
첫번째로 Serializer<T>
인터페이스를 구현한다. 이는 사전에 proto 파일에 정의한 데이터 타입을 직렬화/역직렬화하는 방식을 정의한다. 타입 파라미터 T
는 불변형 타입을 사용해야 한다. 가변형 타입을 사용하는 경우 DataStore
의 기능이 제대로 동작하지 않을 수 있다.
androidx.datastore.core.Serializer.kt
public interface Serializer<T> {
// 디스크에 데이터가 없는 경우 반환하는 기본값
public val defaultValue: T
// 입력 스트림에서 가져온 객체를 언마셜링(역직렬화)
public suspend fun readFrom(input: InputStream): T
// 객체를 마셜링(직렬화)하여 출력 스트림에 전달
public suspend fun writeTo(t: T, output: OutputStream)
}
구현 예시
object SettingsSerializer : Serializer<Settings> {
override val defaultValue: Settings = Settings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(
t: Settings,
output: OutputStream) = t.writeTo(output)
}
두번째로 androidx.datastore
패키지의 dataStore
함수를 사용하여 DataStore<T>
인스턴스에 대한 위임을 생성한다. Preferences DataStore과 마찬가지로 파일 최상위 레벨에서 Context
의 확장 프로퍼티로 정의하여 싱글톤으로 사용한다. String
파라미터는 DataStore
가 저장되는 파일 이름을 나타내며 Serializer
프로퍼티는 해당 데이터 타입에 대한 Serializer
구현체를 나타낸다.
val Context.settingsDataStore: DataStore<Settings> by dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
데이터 읽기 및 쓰기
데이터를 저장할 때는 DataStore<T>#updateData(suspend (T) -> T)
를 사용한다. 함수형 파라미터의 T
타입 파라미터는 현재 데이터의 상태를 나타내며 각 코드는 단일 트랜잭션으로 간주된다.
suspend fun incrementCounter() {
context.settingsDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setExampleCounter(currentSettings.exampleCounter + 1)
.build()
}
}
데이터에 액세스할 때는 DataStore
의 data
프로퍼티와 map
함수를 사용한다.
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
.map { settings ->
// exampleCounter 프로퍼티는 proto 스키마에서 생성된다.
settings.exampleCounter
}
'Android' 카테고리의 다른 글
WorkManager (2) : 작업 상태 (0) | 2022.12.02 |
---|---|
WorkManager (1) : 작업 설정/예약하기 (0) | 2022.12.02 |
Lifecycle (0) | 2022.11.11 |
데이터 바인딩 (4) : 양방향 바인딩 (0) | 2022.11.05 |
데이터 바인딩 (3) : 바인딩 어댑터 (0) | 2022.11.03 |