개발/Android

[DataStore] Preference DataStore 사용하기

Patrick0422 2022. 4. 7. 14:44

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으로 값을 저장합니다.

 

사실 이정도만 알고 있어도 대충 필요한 기능을 사용할 수 있다고 생각하지만, 더 적을 내용이 생긴다면 적어보도록 하겠습니다.