Debbi Story

[Jetpack] DataStore 본문

안드로이드/Jetpack

[Jetpack] DataStore

Debbi 2021. 6. 22. 11:18
728x90

 

안녕하세요. 안드로이드 SharedPreferences를 찾아보다 

로컬 저장소에 값을 저장하는 방법을 정리 해보려합니다. 

 

1. SharedPreferences

https://developer.android.com/training/data-storage/shared-preferences?hl=ko 

 

키-값 데이터 저장  |  Android 개발자  |  Android Developers

저장하려는 키-값 컬렉션이 비교적 작은 경우 SharedPreferences API를 사용해야 합니다. SharedPreferences 객체는 키-값 쌍이 포함된 파일을 가리키며 키-값 쌍을 읽고 쓸 수 있는 간단한 메서드를 제공합

developer.android.com

2. EncryptedSharedPreferences 

https://developer.android.com/topic/security/data?hl=ko#edit-shared-preferences

 

Android 개발자  |  Android Developers

더 안전하게 데이터 사용 Android Jetpack의 구성요소 보안 라이브러리는 저장 데이터 읽기 및 쓰기와 관련된 보안 권장사항의 구현과 키 생성 및 인증을 제공합니다. 라이브러리는 빌더 패턴을 사

developer.android.com

3. DataStore

https://developer.android.com/topic/libraries/architecture/datastore?hl=ko#preferences-datastore 

 

Datastore  |  Android 개발자  |  Android Developers

Datastore   Android Jetpack의 구성요소. Jetpack Datastore는 프로토콜 버퍼를 사용하여 키-값 쌍 또는 유형이 지정된 객체를 저장할 수 있는 데이터 저장소 솔루션입니다. Datastore는 Kotlin 코루틴 및 Flow를

developer.android.com

4. Room

https://developer.android.com/training/data-storage/room?hl=ko 

 

Room을 사용하여 로컬 데이터베이스에 데이터 저장  |  Android 개발자  |  Android Developers

Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기

developer.android.com

 

 

현재 SharedPreferences를 사용하여 데이터를 저장하고 있다면 대신 Datastore로 이전하는 것이 좋습니다.

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

 

Datastore란?

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

 

기존 SharedPreferences의 단점

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

 

기능 SharedPreferences PreferencesDatastore ProtoDatastore
비동기 API ✅(변경된 값을 읽는 용도로만, 리스너를 통해) ✅(Flow를 통해) ✅(Flow를 통해)
동기 API ✅(단, UI 스레드에서 호출하는 것은 안전하지 않음)
UI 스레드에서 호출하기에 안전함 ❌* ✅(작업은 내부에서 Dispatchers.IO로 이동됨) ✅(작업은 내부에서 Dispatchers.IO로 이동됨)
오류 신호 전송 가능
런타임 예외로부터 안전함 ❌**
strong consistency가 보장되는 트랜잭션 API가 있음
데이터 이전 처리 ✅(SharedPreferences에서) ✅(SharedPreferences에서)
유형 안전성 ✅(프로토콜 버퍼 포함)

 

Preferences Datastore와 Proto Datastore 비교

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

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

 

 

 

DataStore 저장 방식에는 위 처럼 두가지 방식이 있고, 저는 Preference Datastore를 사용해보려 합니다.

 

 

 

라이브러리 추가.

// Preferences DataStore (SharedPreferences like APIs)
dependencies {
  implementation "androidx.datastore:datastore-preferences:1.0.0-beta01"
}

 

Preferences Datastore 만들기.

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

 

Preferences Datastore에서 값 쓰기.

 

 - key 생성 

private val IS_DARK_THEME = booleanPreferencesKey("IS_DARK_THEME")

 

- Datastore는 비동기 방식이여서 함수 앞에 suspend를 붙여야하고 Coroutines에서 실행해야 합니다.

suspend fun setDarkTheme(value: Boolean) {
        context.dataStore.edit { preferences ->
            preferences[IS_DARK_THEME] = value
        }
    }

 

 

Preferences Datastore에서 값 읽기.

 

- Datastore.data 속성 값이 flow로 return이 되고, 예외 처리를 한 확장 함수를 만들어 줍니다.

private fun <T> DataStore<Preferences>.getValueAsFlow(
        key: Preferences.Key<T>,
        defaultValue: T
    ): Flow<T> {
        return this.data.catch { exception ->
            // dataStore.data throws an IOException when an error is encountered when reading data
            if (exception is IOException) {
                // we try again to store the value in the map operator
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }.map { preferences ->
            preferences[key] ?: defaultValue
        }
    }

 

- 위에서 만든 확장함수를 호출하고 key 와 default값을 넘겨 줍니다.

val isDarkTheme: Flow<Boolean>
        get() = context.dataStore.getValueAsFlow(IS_DARK_THEME, false)

 

Activity에서 사용하기

 

 - 공식 문서에서 제공하는 읽기 방법

val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[IS_DARK_THEME] ?: false
}

 

- 동기 방식

val exampleData = runBlocking { context.dataStore.data.first() }

 

- 비동기 방식

lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }

 

- 저는 flow를 livedata로 바꾸어 obseve하기 위해 livedata를 추가하였습니다

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
isDarkTheme.asLiveData().observe(this) { isDark ->
      
}

 

 

- 쓰기 함수 호출

  lifecycle 코루틴을 사용하기 위해 추가

implementation "androidx.fragment:fragment-ktx:1.3.5"
lifecycleScope.launch {
      setDarkTheme(true)
}

 

 

 

이상으로 Preferences Datastore 사용 방법을 알아보았습니다.

기존 SharedPreferences 처럼 key, value로 단순한 값을 저장하는 방법이고,

사용자 정의 객체를 저장하는 방법은 Proto Datastore로 해야합니다. 

검색을 하다가 암호화를 추가한 EncryptedSharedPreferences 가 있다는 사실도 처음 알게되었고, Datastore의 존재도 알게되었습니다. 

 

 

 

 

 

 

 

 

 

'안드로이드 > Jetpack' 카테고리의 다른 글

[Jetpack] App Startup  (0) 2021.07.02