SharedPreference에서 DataStore Preference으로 마이그래이션

강성우, 16 October 2020

Bevan Steele의 ‘DataStore Preferences and migrating from SharedPreferences’을 번역해보았다.

1. 시작 하며

최근 안드로이드 개발 시점에 있어 SharedPreferences를 사용한 개발자는 API에 업데이트가 절실히 필요함을 알고 있다. 동기화 되어 있을떄에만 데이터를 읽을 수 있고 오류를 알려주는 기능도 없었으며 트랜잭션 커밋(Tranjaction commit)을 수행할 수 있는 방법또한 없었다.

대부분의 경우 비동기 계층을 추가하고 가능한 오류를 처리하기 위해서는 SharedPreferences을 사용할때 추상화하여 사용 한다. Jetpack은 최근에 이런 문제들을 해결하는 것 을 목표로 하는 DataSource를 도입 하였다. 즉, 작은 데이터 비트를 저장하는 편리하고 현대적인 방법을 제시한 것 이다.

DataSource라이브러리는 DataSource Preferences및 Proto DataSource, 이렇게 두 부분으로 나뉘어져 있다. 저장된 값에 대해 타입 안정성을 제공하지 않는 DataSource Preferences만 다루며 SharedPreferences를 사용중인 경우 마이그레이션이 더 쉽다.

2. 설정

DataStore를 사용할 모듈의 build.gradle파일에 아래와 같이 의존을 추가 한다. 아래를 복사-붙여넣기 할 경우, 의존 라이브러리의 마지막 버전을 확인 하도록 한다. 이는 큰 작업이 아니긴 하였지만 공용 라이브러리로 추출할 가치가 없기 때문에 계속 반복할수밖에 없었다.

implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"

3. 환경 설정 읽기

먼저 DataStore인스턴스를 생성한다. 이때 필요한 것은 오로지 단 한개의 유니크한 이름으로 문자열로 된 식별자 뿐이다. PreferencesSharedPreferences와 같은 키-값으로 이루어진 저장소를 구현하기 위해 라이브러리에서 제공 하는 클래스 이다. 마지막으로 contextcreateDataStore()라는 확장 함수를 이용해 DataStore의 인스턴스를 생성한다.

private val dataStore: DataStore<Preferences> = context.createDataSotre(name = "movies")

이제 사용하려는 각 키에 대해 preferencesKey를 이용해 생성한다. preferencesKey을 이용해 만들수 있는 키의 타입은 Int, Long, Boolean, Float, String중 하나만 사용할 수 있다. 그리고, Set에 대해서도 사용할 수 있는 preferencesSetKey가 있다.

val LAST_MOVIE_ID = preferencesKey<Int>("last_movie_id")

요청한 키에 대해서 값을 Flow를 통해서 읽게된다. 값 데이터를 읽는동안 IOException이 발생할 수 있으므로 이 예외를 적절하게 처리하는 코드가 있으면 좋을것이다. 아직 값이 존재하지 않을 경우(즉, null이 반환될 경우)에 대해 처리하는 것도 잊지 말아야 한다.

val lastMovieIdFlow: Flow<Int> = dataStore.data
    .catch { exception ->
        if (exception is IOException) {
            emit(-1)
        } else {
            throw exception
        }
    .map { preferences ->
        preferences[LAST_MOVIE_ID] ?: -1
    }

ViewModel에서 이 값을 사용하려면 lastMovieIdFlow.asLiveData()또는, lastMovieIdFlow.collect()를 사용할 수 있다. Flow에 대해서 알고 싶다면 이 링크를 참고 하자.

  • Flow에 대해서 간단하게 말하자면 kotlin에서 제공하는 coroutine builder라고 한다. 기본적으로 비동기로 동작하며 Cold stream으로 lazy하게 동작 한다.

4. 환경 설정 쓰기

쓰기를 할때엔 suspend함수인 edit()를 사용해서 쓰기를 하면 된다. 역시 IOException이 발생할 수 있으므로 핸들링 하기 위한 코드가 있으면 좋다.

dataStore.edit { preferences ->
    preferences[LAST_MOVIE_ID] = currentMovieId
}
  • suspend함수는 코루틴에서 사용되는 키워드이며, 해당 함수는 현재 사용되는 스레드를 차단하지 않고 코루틴의 실행을 일시 중단할 수 있다는 것 이다.

5. 마이그레이션

이미 SharedPreferences를 사용하고 있을 경우 DataStore으로 마이그레이션 하기 위해서는 DataStore인스턴스를 생성 할 때 이전 SharedPreferences이름을 전달 하기만 하면 마이그레이션이 자동으로 수행 된다. 마이그레이션을 진행했다면(예를 들어 마이그레이션 코드 작성 후 실행했을때 예외가 발생하지 않음) SharedPreferences사용을 중단하고 DataStore를 이용해 기존 키를 이용해 읽기/쓰기를 할 수 있다.

private val dataStore: DataStore<Preferences> =
    context.createDataStore(
        name = "movies",
        migrations = 
          listOf(SharedPreferencesMigration(context, "shared_preferences_name"))
    )