[DataStore] Preference DataStore 사용하기
DataStore는 SharedPreference를 대체하기 위해 구현된 라이브러리로, PreferenceDataStore와 ProtoDataStore 두 가지 종류가 있습니다. 이 글에서는 Key-Value 형태로 데이터를 저장하는 PreferenceDataStore를 사용해보도록 하겠습니다.
1. 의존성 추가
// PreferenceDataStore
def datastore_version = "1.0.0"
implementation "androidx.datastore:datastore-preferences:$datastore_version"
App Gradle에 추가해줍니다.
2. DataStoreRepository 생성
저는 DataStore를 Repository에 넣어 관리하겠습니다.
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
@ActivityRetainedScoped
class DataStoreRepository @Inject constructor(
@ApplicationContext private val context: Context
) {
}
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
DataStore 객체를 클래스 밖에서 정의하면 싱글톤으로 관리할 수 있다고 합니다.
Repository 클래스는 @ActivityRetainedScoped 어노테이션으로 액티비티의 생명주기를 따르도록 하였습니다.
참고
@ActivityRetainedScoped: 액티비티의 첫 onCreate()에 생성되고 마지막 onDestroy()에 제거됨
@ActivityScoped: onCreate()와 onDestroy()를 그대로 따라감
→ @ActivityRetainedScoped는 화면 방향 변경이나 언어 변경등의 Configuration Change에 영향을 받지 않습니다.
3. PreferenceKey 정의
이제 PreferenceDataStore에서 사용할 Key를 정의해야 합니다.
preferencesKey<Boolean>("dark_mode")
예전에는 위와 같이 제네릭으로 Key가 저장할 Value의 자료형을 정의했는데,
업데이트로 위와 같은 형식으로 바뀌게 되었습니다. 생성자 안의 name 파라미터는 Key의 이름을 뜻합니다.
@ActivityRetainedScoped
class DataStoreRepository @Inject constructor(
@ApplicationContext private val context: Context
) {
val selectedMealType = stringPreferencesKey(Constants.PREFERENCES_MEAL_TYPE)
val selectedMealTypeId = intPreferencesKey(Constants.PREFERENCES_MEAL_TYPE_ID)
val selectedDietType = stringPreferencesKey(Constants.PREFERENCES_DIET_TYPE)
val selectedDietTypeId = intPreferencesKey(Constants.PREFERENCES_DIET_TYPE_ID)
val backOnline = booleanPreferencesKey(Constants.PREFERENCES_BACK_ONLINE)
...
}
이런 식으로 저장하고 싶은 키들을 생성해주면 됩니다.
@ActivityRetainedScoped
class DataStoreRepository @Inject constructor(@ApplicationContext private val context: Context) {
private object PreferenceKeys {
val selectedMealType = stringPreferencesKey(Constants.PREFERENCES_MEAL_TYPE)
val selectedMealTypeId = intPreferencesKey(Constants.PREFERENCES_MEAL_TYPE_ID)
val selectedDietType = stringPreferencesKey(Constants.PREFERENCES_DIET_TYPE)
val selectedDietTypeId = intPreferencesKey(Constants.PREFERENCES_DIET_TYPE_ID)
val backOnline = booleanPreferencesKey(Constants.PREFERENCES_BACK_ONLINE)
}
...
}
↑Key들을 묶어준 경우
위의 코드처럼 Key들이 많을 경우에 제가 보던 강의에서는 object로 Key들을 묶어주었는데, 아마 코드 구조를 깔끔하게 하려고 한 것 같습니다. 제 생각에는 enum이나 sealed class 등을 써도 상관없을 것 같습니다.
4. Key로 값을 저장하기
이제 Key들도 만들어 주었으니 이를 통해 값을 저장하는 법을 알아보겠습니다.
suspend fun saveIsNotificationAllowed(isAllowed: Boolean) {
context.dataStore.edit {
}
}
값을 쓰기 위해서는 먼저 주입받은 context 안의 dataStore 객체에서 .edit을 호출해줍니다.
그러면 위와 같이 MutablePreferences 형태의 값에 접근할 수 있게 되는데, MutablePreferences는 DataStore 안에 저장된 값들을 가지고 있는, 변경이 가능한 Map 형태의 자료형이라고 보면 될 것 같습니다.
suspend fun saveIsNotificationAllowed(isAllowed: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferenceKeys.isNotificationAllowed] = isAllowed
}
}
아무튼 위 코드처럼 .edit을 통해 MutablePreferences에 접근한 후 Map을 쓰듯이 아까 만든 Key로 접근해 값을 넣어주면 됩니다.
5. Key로 값을 가져오기
이번엔 저장했던 값을 가져와 보겠습니다. DataStore에서 값을 불러올 때는 Flow를 사용합니다.
값을 가져올 때는 context 의 dataStore 내부의 data 프로퍼티를 통해 값을 노출한 후,
val readIsNotificationAllowed: Flow<Boolean> = context.dataStore.data
.catch { exception ->
if (exception is IOException)
emit(emptyPreferences())
else
throw exception
}
.map { preferences ->
preferences[PreferenceKeys.isNotificationAllowed] ?: false
}
위와 같이 .catch로 예외처리를, .map으로 값을 저장합니다.
사실 이정도만 알고 있어도 대충 필요한 기능을 사용할 수 있다고 생각하지만, 더 적을 내용이 생긴다면 적어보도록 하겠습니다.