Preferences Datastore를 사용하여 작업하기

1. 소개

Datastore란?

Datastore는 개선된 신규 데이터 저장소 솔루션으로, SharedPreferences를 대체합니다. Kotlin 코루틴과 Flow를 기반으로 한 Datastore는 두 가지 구현, 즉 타입 객체를 저장하는 Proto Datastore(프로토콜 버퍼로 지원) 및 키-값 쌍을 저장하는 Preferences Datastore를 제공합니다. 비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장하여 SharedPreferences의 단점을 일부 극복합니다.

학습할 내용

  • Datastore 특성 및 Datastore를 사용해야 하는 이유
  • 프로젝트에 Datastore를 추가하는 방법
  • Preferences Datastore와 Proto Datastore의 차이점 및 각각의 장점
  • Preferences Datastore를 사용하는 방법
  • SharedPreferences에서 Preferences Datastore로 이전하는 방법

빌드할 항목

이 Codelab에서는 먼저 작업 목록을 표시하는 샘플 앱을 사용합니다. 작업 목록은 완료 상태를 기준으로 필터링하고 우선순위와 기한을 기준으로 정렬할 수 있습니다.

fcb2ffa4e6b77f33.gif

Show completed tasks 필터의 부울 플래그가 메모리에 저장됩니다. 정렬 순서는 SharedPreferences 객체를 사용하여 디스크에 유지됩니다.

이 Codelab에서는 다음 작업을 완료하여 Preferences Datastore를 사용하는 방법을 알아봅니다.

  • Datastore에 완료 상태 필터를 유지합니다.
  • 정렬 순서를 SharedPreferences에서 Datastore로 이전합니다.

둘 사이의 차이를 더 잘 이해할 수 있도록 Proto Datastore Codelab도 살펴보는 것이 좋습니다.

필요한 항목

아키텍처 구성요소에 관한 소개는 뷰 Codelab이 있는 Room을 확인하세요. Flow에 관한 소개는 Kotlin Flow 및 LiveData Codelab을 사용한 고급 코루틴을 확인하세요.

2. 설정

이 단계에서는 전체 Codelab을 위한 코드를 다운로드한 후 간단한 예시 앱을 실행합니다.

Google에서 준비한 시작 프로젝트를 사용하면 신속하게 빌드할 수 있습니다.

git을 설치했다면 아래 명령어를 실행하면 됩니다. git이 설치되어 있는지 확인하려면 터미널이나 명령줄에 git --version을 입력하여 정확하게 실행되는지 확인합니다.

 git clone https://github.com/googlecodelabs/android-datastore

초기 상태는 master 분기에 있으며, 솔루션 코드는 preferences_datastore 분기에 있습니다.

git이 없는 경우 다음 버튼을 클릭하여 이 Codelab을 위한 모든 코드를 다운로드하세요.

소스 코드 다운로드

  1. 코드의 압축을 푼 다음 Android 스튜디오 Arctic Fox에서 프로젝트를 엽니다.
  2. 기기 또는 에뮬레이터에서 app 실행 구성을 실행합니다.

b3c0dfdb92dfed77.png

앱이 실행되고 다음 작업 목록이 표시됩니다.

d3972939a2de88ba.png

3. 프로젝트 개요

앱에서 작업 목록을 볼 수 있습니다. 각 작업에는 이름, 완료 상태, 우선순위, 기한과 같은 속성이 있습니다.

처리해야 하는 코드를 단순화하기 위해 개발자는 앱에서 다음 두 가지 작업만 할 수 있습니다.

  • 완료된 작업 표시 여부 전환(기본적으로 완료된 작업은 숨겨짐)
  • 우선순위, 기한 또는 기한과 우선순위를 기준으로 작업 정렬

앱은 앱 아키텍처 가이드에서 권장하는 아키텍처를 따릅니다. 각 패키지에는 다음과 같은 항목이 포함됩니다.

data

  • Task 모델 클래스
  • TasksRepository 클래스: 작업을 제공합니다. 간단하게 이 클래스는 하드코딩된 데이터를 반환하고 Flow를 통해 노출하여 더욱 현실적인 시나리오를 표현합니다.
  • UserPreferencesRepository 클래스: enum으로 정의된 SortOrder를 포함합니다. 현재 정렬 순서가 SharedPreferences에 저장되며 enum 값 이름에 따라 String으로 저장됩니다. 이 클래스는 정렬 순서를 저장하고 가져오는 동기 메서드를 노출합니다.

ui

  • RecyclerView가 있는 Activity 표시와 관련된 클래스입니다.
  • TasksViewModel 클래스는 UI 로직을 담당합니다.

TasksViewModel: UI에 표시해야 하는 데이터를 빌드하는 데 필요한 모든 요소를 포함합니다. 이러한 요소에는 작업 목록, showCompletedsortOrder 플래그가 있으며 TasksUiModel 객체에 래핑되어 있습니다. 이러한 값 중 하나가 변경될 때마다 TasksUiModel을 새로 재구성해야 합니다. 이를 위해 다음 세 가지 요소를 결합합니다.

  • Flow<List<Task>>: TasksRepository에서 가져옵니다.
  • MutableStateFlow<Boolean>: 메모리에만 유지되고 있는 최신 showCompleted 플래그를 포함합니다.
  • MutableStateFlow<SortOrder>: 최신 sortOrder 값을 포함합니다.

활동이 시작될 때만 UI를 올바르게 업데이트할 수 있도록 LiveData<TasksUiModel>을 노출합니다.

코드에 다음과 같은 문제가 있습니다.

  • UserPreferencesRepository.sortOrder를 초기화할 때 디스크 IO에서 UI 스레드를 차단합니다. 이로 인해 UI 버벅거림이 발생할 수 있습니다.
  • showCompleted 플래그는 메모리에만 보관되므로 사용자가 앱을 열 때마다 재설정됩니다. sortOrder와 마찬가지로 앱을 닫은 후에도 플래그가 유지되어야 합니다.
  • 현재 SharedPreferences를 사용하여 데이터를 보존하지만, 직접 수정할 수 있는 MutableStateFlow를 메모리에 보관하여 변경 시 알림을 받을 수 있습니다. 값이 애플리케이션의 다른 위치에서 수정되는 경우 알림을 받지 못합니다.
  • UserPreferencesRepository에서는 정렬 순서를 업데이트하는 두 가지 메서드, 즉 enableSortByDeadline()enableSortByPriority()를 노출합니다. 두 메서드는 현재 정렬 순서 값에 의존하지만, 한 메서드가 완료되기 전에 다른 한 메서드가 호출되면 최종값이 잘못 생성됩니다. 또한 이러한 메서드는 UI 스레드에서 호출될 때 UI 버벅거림 및 엄격 모드 위반을 일으킬 수 있습니다.

Datastore를 사용하여 이러한 문제를 해결하는 방법을 알아보겠습니다.

4. Datastore - 기본사항

소규모 또는 단순한 데이터 세트를 저장해야 하는 경우가 있을 수 있습니다. 이를 위해 이전에는 SharedPreferences를 사용했지만, 이 API에는 일련의 단점이 있습니다. Jetpack Datastore 라이브러리는 이러한 문제를 해결하고 데이터 저장을 위한 간단하고 더 안전한 비동기 API를 만드는 것을 목표로 합니다. 이 라이브러리에서는 다음의 서로 다른 두 가지 구현을 제공합니다.

  • Preferences Datastore
  • Proto Datastore

기능

SharedPreferences

PreferencesDatastore

ProtoDatastore

비동기 API

✅(변경된 값을 읽는 용도로만, 리스너를 통해)

✅(Flow와 RxJava 2 & 3 Flowable을 통해)

✅(Flow와 RxJava 2 & 3 Flowable을 통해)

동기 API

✅(단, UI 스레드에서 호출하는 것은 안전하지 않음)

UI 스레드에서 호출하기에 안전함

❌1

✅(작업은 내부에서 Dispatchers.IO로 이동됨)

✅(작업은 내부에서 Dispatchers.IO로 이동됨)

오류 신호 전송 가능

런타임 예외로부터 안전함

❌2

strong consistency가 보장되는 트랜잭션 API가 있음

데이터 이전 처리

유형 안전성

✅(프로토콜 버퍼 포함)

1 SharedPreferences에는 UI 스레드에서 호출하기에 안전해 보일 수 있지만 실제로는 디스크 I/O 작업을 하는 동기 API가 있습니다. 또한 apply()fsync()에서 UI 스레드를 차단합니다. 대기 중인 fsync() 호출은 서비스가 시작되거나 중지될 때마다, 그리고 애플리케이션에서 활동이 시작되거나 중지될 때마다 트리거됩니다. UI 스레드는 apply()에서 예약한 대기 중인 fsync() 호출에서 차단되며 주로 ANR의 소스가 됩니다.

2 SharedPreferences는 파싱 오류를 런타임 예외로 발생시킵니다.

Preferences Datastore와 Proto Datastore 비교

Preferences Datastore와 Proto Datastore에서는 모두 데이터 저장이 가능하지만 저장 방법이 서로 다릅니다.

  • Preference Datastore는 SharedPreferences와 마찬가지로 스키마를 먼저 정의하지 않은 상태에서 키를 기반으로 데이터에 액세스합니다.
  • Proto Datastore프로토콜 버퍼를 사용하여 스키마를 정의합니다. Protobuf를 사용하기 때문에 강타입(strongly typed) 데이터를 유지할 수 있습니다. 이러한 데이터는 XML 등 다른 유사한 데이터 형식보다 빠르고 작고 간결하며 덜 모호합니다. Proto Datastore를 사용하려면 새로운 직렬화 메커니즘을 배워야 하지만 Proto Datastore의 강타입 이점이 그만한 가치가 있습니다.

Room과 Datastore 비교

부분 업데이트, 참조 무결성 또는 대규모/복잡한 데이터 세트가 필요한 경우에는 Datastore 대신 Room을 사용하는 것이 좋습니다. Datastore는 소규모 또는 단순한 데이터 세트에 적합하며 부분 업데이트나 참조 무결성을 지원하지 않습니다.

5. Preferences Datastore 개요

Preferences Datastore API는 SharedPreferences와 비슷하지만 다음과 같은 차이점을 주목해야 합니다.

  • 데이터 업데이트를 트랜잭션 방식으로 처리함
  • 데이터의 현재 상태를 나타내는 Flow를 노출함
  • 데이터 영구 메서드(apply(), commit())가 없음
  • 변경 가능한 참조를 내부 상태에 반환하지 않음
  • 타입 키가 있는 MapMutableMap과 유사한 API를 노출함

Preferences Datastore API를 프로젝트에 추가하고 SharedPreferences를 Datastore로 이전하는 방법을 살펴보겠습니다.

종속 항목 추가

build.gradle 파일을 업데이트하여 다음 Preferences Datastore 종속 항목을 추가합니다.

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

6. Preferences Datastore에서 데이터 유지

showCompletedsortOrder 플래그는 모두 사용자 환경설정이지만, 현재 두 개의 다른 객체로 표현됩니다. 그런 이유로 UserPreferences 클래스에 두 플래그를 통합하고 Datastore를 사용하여 UserPreferencesRepository에 저장하는 것이 앞으로 실행할 목표 중 하나입니다. 현재 showCompleted 플래그는 TasksViewModel로 메모리에 유지됩니다.

먼저 UserPreferencesRepository에서 UserPreferences 데이터 클래스를 만들어 보겠습니다. 지금은 showCompleted 필드 하나만 있습니다. 나중에 정렬 순서를 추가합니다.

data class UserPreferences(val showCompleted: Boolean)

Datastore 만들기

Datastore 인스턴스를 만들기 위해 preferencesDataStore 위임을 사용하며 수신기로 Context를 사용합니다. 이 Codelab에서는 간단하게 이를 TasksActivity에서 처리합니다.

private const val USER_PREFERENCES_NAME = "user_preferences"

private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME
)

preferencesDataStore 위임은 애플리케이션에 이 이름을 가진 Datastore 인스턴스가 하나만 있음을 보장합니다. 현재, UserPreferencesRepositorysortOrderFlow를 포함하고 TasksActivity의 수명 주기에 직접 연결되는 것을 회피하기 위해 싱글톤으로 구현되었습니다. UserPreferenceRepository는 Datastore의 데이터를 사용할 뿐 새로운 객체를 만들거나 포함하지 않으므로 다음과 같이 싱글톤 구현을 삭제할 수 있습니다.

  • companion object 삭제
  • constructor를 공개로 변경

UserPreferencesRepository는 생성자 매개변수로 DataStore 인스턴스를 가져와야 합니다. 현재는 SharedPreferences에서 Context를 필요로 하므로 매개변수로 남겨둘 수 있지만, 향후 삭제할 예정입니다.

class UserPreferencesRepository(
    private val userPreferencesStore: DataStore<UserPreferences>,
    context: Context
) { ... }

이제 TasksActivity에서 UserPreferencesRepository의 생성자를 업데이트하고 dataStore에 전달합니다.

viewModel = ViewModelProvider(
    this,
    TasksViewModelFactory(
        TasksRepository,
        UserPreferencesRepository(dataStore, this)
    )
).get(TasksViewModel::class.java)

Preferences Datastore에서 데이터 읽기

Preferences Datastore는 환경설정이 변경될 때마다 방출되는 Flow<Preferences>에 저장된 데이터를 노출합니다. 전체 Preferences 객체를 노출하지 않고 UserPreferences 객체를 노출하려고 합니다. 이렇게 하려면 Flow<Preferences>를 매핑하고 키를 기반으로 원하는 부울 값을 가져오고 UserPreferences 객체를 구성해야 합니다.

따라서 가장 먼저 해야 할 일은 show_completed 키를 정의하는 것입니다. 이 키는 비공개 PreferencesKeys 객체에서 멤버로 선언할 수 있는 booleanPreferencesKey 값입니다.

private object PreferencesKeys {
  val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
}

dataStore.data: Flow<Preferences>를 기반으로 구성된 userPreferencesFlow: Flow<UserPreferences>를 노출하겠습니다. 그런 다음, 매핑하여 올바른 환경설정을 가져옵니다.

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .map { preferences ->
        // Get our show completed value, defaulting to false if not set:
        val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED]?: false
        UserPreferences(showCompleted)
    }

데이터를 읽는 동안 예외 처리

Datastore가 파일에서 데이터를 읽을 때 데이터를 읽는 동안 오류가 발생하면 IOExceptions가 발생합니다. map() 앞에 catch() Flow 연산자를 사용하고 발생한 예외가 IOException인 경우 emptyPreferences()를 방출하면 처리할 수 있습니다. 다른 유형의 예외가 발생한다면 예외를 다시 발생시키는 편이 좋습니다.

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .catch { exception ->
        // dataStore.data throws an IOException when an error is encountered when reading data
        if (exception is IOException) {
            emit(emptyPreferences())
        } else {
            throw exception
        }
    }.map { preferences ->
        // Get our show completed value, defaulting to false if not set:
        val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED]?: false
        UserPreferences(showCompleted)
    }

Preferences Datastore에 데이터 쓰기

데이터를 쓰기 위해 Datastore는 정지 함수 DataStore.edit(transform: suspend (MutablePreferences) -> Unit)를 제공합니다. 이 함수는 Datastore에서 트랜잭션 방식으로 상태를 업데이트할 수 있는 transform 블록을 허용합니다.

변환 블록에 전달된 MutablePreferences는 이전에 실행된 수정사항으로 최신 상태가 됩니다. transform 블록에서 MutablePreferences의 모든 변경사항은 transform이 완료된 후 edit이 완료되기 전에 디스크에 적용됩니다. MutablePreferences에서 하나의 값을 설정하면 다른 모든 환경설정이 변경되지 않고 유지됩니다.

참고: 변환 블록 외부에서 MutablePreferences를 수정하지 마세요.

UserPreferencesshowCompleted 속성을 업데이트할 수 있는 updateShowCompleted()라는 정지 함수를 만들겠습니다. 이 함수는 dataStore.edit()를 호출하고 새 값을 설정합니다.

suspend fun updateShowCompleted(showCompleted: Boolean) {
    dataStore.edit { preferences ->
        preferences[PreferencesKeys.SHOW_COMPLETED] = showCompleted
    }
}

edit()는 디스크를 읽거나 디스크에 쓰는 중에 오류가 발생하면 IOException을 발생시킬 수 있습니다. 변환 블록에서 생긴 다른 오류의 경우 edit()에 의해 발생합니다.

이 시점에서 앱을 컴파일해야 하지만 UserPreferencesRepository에서 방금 만든 기능은 사용되지 않습니다.

7. SharedPreferences에서 Preferences Datastore로 옮기기

정렬 순서는 SharedPreferences에 저장됩니다. 정렬 순서를 Datastore로 옮기겠습니다. 이렇게 하려면 먼저, 정렬 순서도 저장하도록 UserPreferences를 업데이트합니다.

data class UserPreferences(
    val showCompleted: Boolean,
    val sortOrder: SortOrder
)

SharedPreferences에서 이전

Datastore로 이전하려면 dataStore 빌더를 업데이트하여 이전 목록으로 SharedPreferencesMigration을 전달해야 합니다. Datastore는 SharedPreferences에서 Datastore로 자동 이전할 수 있습니다. 이전은 Datastore에서 데이터 액세스가 발생하기 전에 실행됩니다. 즉, 먼저 이전에 성공해야 DataStore.data가 값을 방출하고 DataStore.edit()이 데이터를 업데이트할 수 있습니다.

참고: 키는 SharedPreferences에서 한 번만 이전되므로 코드가 Datastore로 이전된 후에는 기존 SharedPreferences 사용을 중단해야 합니다.

먼저 TasksActivity에서 Datastore 생성을 업데이트합니다.

private const val USER_PREFERENCES_NAME = "user_preferences"

private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME,
    produceMigrations = { context ->
        // Since we're migrating from SharedPreferences, add a migration based on the
        // SharedPreferences name
        listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME))
    }
)

그런 다음, PreferencesKeyssort_order를 추가합니다.

private object PreferencesKeys {
    ...
    // Note: this has the the same name that we used with SharedPreferences.
    val SORT_ORDER = stringPreferencesKey("sort_order")
}

모든 키가 Datastore로 이전되고 사용자 환경설정 SharedPreferences에서 삭제됩니다. 이제 Preferences에서 SORT_ORDER 키를 기반으로 SortOrder를 가져오고 업데이트할 수 있습니다.

Datastore에서 정렬 순서 읽기

map() 변환의 정렬 순서도 가져오도록 userPreferencesFlow를 업데이트하겠습니다.

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .catch { exception ->
        if (exception is IOException) {
            emit(emptyPreferences())
        } else {
            throw exception
        }
    }.map { preferences ->
        // Get the sort order from preferences and convert it to a [SortOrder] object
        val sortOrder =
            SortOrder.valueOf(
                preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name)

        // Get our show completed value, defaulting to false if not set:
        val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED] ?: false
        UserPreferences(showCompleted, sortOrder)
    }

정렬 순서를 Datastore에 저장

현재 UserPreferencesRepository는 정렬 순서 플래그를 설정하는 동기 방식만 노출하며 동시 실행 문제가 있습니다. 정렬 순서를 업데이트하는 두 메서드, 즉 enableSortByDeadline()enableSortByPriority()를 노출합니다. 두 메서드는 현재 정렬 순서 값에 의존하지만, 한 메서드가 완료되기 전에 다른 메서드가 호출되면 최종값이 잘못 생성됩니다.

Datastore에서는 트랜잭션 방식의 데이터 업데이트 처리를 보장하므로 더 이상 이 문제가 발생하지 않습니다. 다음과 같이 변경하겠습니다.

  • enableSortByDeadline()enableSortByPriority()dataStore.edit()을 사용하는 suspend 함수가 되도록 업데이트합니다.
  • edit()의 변환 블록에서 currentOrder_sortOrderFlow 필드에서 검색하는 대신 환경설정 매개변수에서 가져옵니다.
  • updateSortOrder(newSortOrder)를 호출하는 대신 환경설정에서 정렬 순서를 직접 업데이트할 수 있습니다.

다음과 같이 구현됩니다.

suspend fun enableSortByDeadline(enable: Boolean) {
    // edit handles data transactionally, ensuring that if the sort is updated at the same
    // time from another thread, we won't have conflicts
    dataStore.edit { preferences ->
        // Get the current SortOrder as an enum
        val currentOrder = SortOrder.valueOf(
            preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name
        )

        val newSortOrder =
            if (enable) {
                if (currentOrder == SortOrder.BY_PRIORITY) {
                    SortOrder.BY_DEADLINE_AND_PRIORITY
                } else {
                    SortOrder.BY_DEADLINE
                }
            } else {
                if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
                    SortOrder.BY_PRIORITY
                } else {
                    SortOrder.NONE
                }
            }
        preferences[PreferencesKeys.SORT_ORDER] = newSortOrder.name
    }
}

suspend fun enableSortByPriority(enable: Boolean) {
    // edit handles data transactionally, ensuring that if the sort is updated at the same
    // time from another thread, we won't have conflicts
    dataStore.edit { preferences ->
        // Get the current SortOrder as an enum
        val currentOrder = SortOrder.valueOf(
            preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.NONE.name
        )

        val newSortOrder =
            if (enable) {
                if (currentOrder == SortOrder.BY_DEADLINE) {
                    SortOrder.BY_DEADLINE_AND_PRIORITY
                } else {
                    SortOrder.BY_PRIORITY
                }
            } else {
                if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
                    SortOrder.BY_DEADLINE
                } else {
                    SortOrder.NONE
                }
            }
        preferences[PreferencesKeys.SORT_ORDER] = newSortOrder.name
    }
}

이제 context 생성자 매개변수와 SharedPreferences의 모든 사용을 삭제할 수 있습니다.

8. UserPreferencesRepository를 사용하도록 TasksViewModel 업데이트

이제 UserPreferencesRepository가 Datastore에 show_completedsort_order 플래그를 모두 저장하고 Flow<UserPreferences>를 노출합니다. 이러한 요소를 사용하도록 TasksViewModel을 업데이트합니다.

showCompletedFlowsortOrderFlow를 삭제하고, userPreferencesRepository.userPreferencesFlow로 초기화되는 userPreferencesFlow라는 값을 만듭니다.

private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow

tasksUiModelFlow 생성 시 showCompletedFlowsortOrderFlowuserPreferencesFlow로 대체합니다. 그에 따라 매개변수를 대체합니다.

filterSortTasks를 호출할 때 userPreferencesshowCompletedsortOrder를 전달합니다. 코드는 다음과 같이 표시됩니다.

private val tasksUiModelFlow = combine(
        repository.tasks,
        userPreferencesFlow
    ) { tasks: List<Task>, userPreferences: UserPreferences ->
        return@combine TasksUiModel(
            tasks = filterSortTasks(
                tasks,
                userPreferences.showCompleted,
                userPreferences.sortOrder
            ),
            showCompleted = userPreferences.showCompleted,
            sortOrder = userPreferences.sortOrder
        )
    }

이제 showCompletedTasks() 함수가 userPreferencesRepository.updateShowCompleted()를 호출하도록 업데이트됩니다. 이 함수는 정지 함수이므로 viewModelScope에서 새 코루틴을 만드세요.

fun showCompletedTasks(show: Boolean) {
    viewModelScope.launch {
        userPreferencesRepository.updateShowCompleted(show)
    }
}

이제 userPreferencesRepository 함수인 enableSortByDeadline()enableSortByPriority()가 정지 함수이므로 viewModelScope에서 실행되는 새 코루틴에서도 호출되어야 합니다.

fun enableSortByDeadline(enable: Boolean) {
    viewModelScope.launch {
       userPreferencesRepository.enableSortByDeadline(enable)
    }
}

fun enableSortByPriority(enable: Boolean) {
    viewModelScope.launch {
        userPreferencesRepository.enableSortByPriority(enable)
    }
}

UserPreferencesRepository 지우기

더 이상 필요하지 않은 필드와 메서드를 삭제하겠습니다. 다음 항목을 삭제할 수 있습니다.

  • _sortOrderFlow
  • sortOrderFlow
  • updateSortOrder()
  • private val sortOrder: SortOrder

이제 앱이 성공적으로 컴파일됩니다. 이제 앱을 실행하여 show_completedsort_order 플래그가 올바르게 저장되었는지 확인합니다.

Codelab 저장소의 preferences_datastore 분기를 확인하여 변경사항을 비교하세요.

9. 요약정리

Preferences Datastore로 이전을 완료했으니 배운 내용을 요약해 보겠습니다.

  • SharedPreferences는 UI 스레드에서 호출하기에 안전해 보일 수 있는 동기 API가 있고, 오류 신호를 보내는 메커니즘이 없고, 트랜잭션 API가 없다는 등 일련의 단점이 있습니다.
  • SharedPreferences를 대체하는 Datastore는 API의 거의 모든 단점을 해결합니다.
  • Datastore는 Kotlin 코루틴과 Flow를 사용하는 완전 비동기 API가 있으며, 데이터 이전을 처리하고, 데이터 일관성을 보장하고, 데이터 손상을 처리합니다.