Preference Datastore이란?
public interface DataStore<T> {
public val data: Flow<T>
public suspend fun updateData(transform: suspend (t: T) -> T): T
}
Preference Datastore이란 DataStore 인터페이스를 구현하는 구현체로 Android에서 간단한 데이터를 key-value 쌍으로 저장하기 위해 사용하는 라이브러리이다. SharedPreference와 같이 Type Safety를 제공하지 않는 데이터 저장소 솔루션이다. Type Safety을 제공하지 않아 데이터 저장소에서 꺼낸 데이터에 대해 타입을 잘못 지정한다면 오류가 발생할 수 있다.
그럼에도 여전히 비동기 작업을 위해 Coroutines를 기본으로 사용한다는 점과 데이터 업데이트를 Transaction으로 처리해 Strong Consistency를 보장해 다중 스레드 환경에 최적화 되어 있다는 점에서 사용하기에 충분히 경쟁력 있다.
Datastore의 Coroutines
1. 데이터를 꺼낼 때는 Flow를 사용
2. 데이터를 입력할 때는 Dispatchers.IO를 강제로 사용하도록 만듦
Preference Datastore 사용 준비
Preference Datastore을 사용하기 위해서는 다음 라이브러리를 추가해야 한다.
dependencies {
..
implementation("androidx.datastore:datastore-preferences:1.0.0")
}
*Protocol Buffer을 사용하기 위해 설정이 복잡한 Proto Datastore과는 달리 설정이 매우 간편하다. 따라서 가벼운 프로젝트에서는 Preference Datastore을 사용하는 것이 효율적이다.
Preference Datastore에서 데이터 조회하기
Preference Datastore은 Android의 Context에 dependent 하므로 아래와 같이 Context의 확장 프로퍼티로 선언한다.
val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
이렇게 하면 Context를 상속하는 클래스인 Activity나 Fragment에서 직접 접근이 가능하며, Context가 있는 곳 어디에서든지 사용이 가능하다.
이를 실제로 사용하는 코드는 아래와 다음과 같다.
val KEY_COUNTER = intPreferencesKey("example_counter")
val counterFlow: Flow<Int> = settingsDataStore.data.map { preferences ->
preferences[KEY_COUNTER] ?: 0
}
첫째 줄에서 Key를 PreferenceKey클래스로 선언한 후 이 Key를 datastore Key-Value 쌍의 Key값으로 사용한다. data의 map에 key값으로 하여 map에서 value값을 추출한다. 여기서 추출된 value값이 Flow로 추출된다. 아래에서 구성 요소를 하나하나 살펴보자.
PreferenceKey
첫째 줄의 intPreferenceKey는 Preference.Key<Int> 를 리턴하는 PreferenceKey이다. 이 외에도 다양한 Preferences.Key들이 있는데 그 종류는 [그림1]에서 확인할 수 있다. 이 7가지 종류가 Preference Datastore에서 저장할 수 있는 데이터의 종류이다.
여기서 지원하지 않는 Class객체들은 직렬화, 역직렬화를 통해 string으로 저장하면 된다.
Flow
public interface DataStore<T> {
public val data: Flow<T>
public suspend fun updateData(transform: suspend (t: T) -> T): T
}
기본적으로 DataStore Interface의 data 프로퍼티는 Flow를 반환한다.
이때 Key-Value 쌍들의 집합을 Map 형태의 데이터 구조로 반환하는데 이 데이터 구조에서 특정한 값을 추출하려면 아래와 같이 map 함수를 사용해 데이터를 변환한다.
val counterFlow: Flow<Int> = settingsDataStore.data.map { preferences ->
preferences[KEY_COUNTER] ?: 0
}
그러면 Flow가 나오게 된다. Flow에 대한 이해가 부족하면 아래 링크를 참조 하는 것을 추천한다.
Preference Datastore에서 데이터 수정하기
Preference Datastore은 아래 코드와 같이 dataStore 객체를 edit 하여 수정이 가능하다. SharedPreference와 다르게 commit을 하지 않아도 값이 업데이트 된다.
lifecycleScope.launch {
settingsDataStore.edit { settings ->
settings[KEY_COUNTER] = (settings[KEY_COUNTER] ?: 0) + 1
}
}
Datastore은 Coroutine을 사용한 비동기 처리를 강제하기 때문에 꼭 CoroutineScope 안에서 실행되어야 한다. 위의 코드는 Activity에서 수행되었기 때문에 Activity의 Lifecycle동안 지속되는 lifecyclescope에서 launch 했다.
단순히 edit에서 반환되는 객체에 Key값을 설정하고 값을 넣는 것만으로 값이 업데이트 된다.
*만약 Coroutines에 대해 잘 모른다면 아래 카테고리의 글을 모두 읽도록 하자.
정리하며
우리는 위에서 Preference Datastore에 대해 알아봤다. Preference Datastore은 SharedPreference에 비해 비동기 처리와 Strong Consistency에 대한 장점이 있다.
하지만 위 장점 중 일부는 Coroutines에 의해 생겨난다. Datastore 은 Coroutines에 대한 이해가 없으면 사용은 할 수 있어도 어떻게 동작하는지 이해하기 어렵기 때문에 그 자체로 러닝 커브가 높은 편에 속한다. 만약 Coroutines를 모른다면 Coroutines에 대해 먼저 공부하고 공부하는 것을 추천한다.