핀수로그
  • [Android] SharedPreferences를 대체할 DataStore에 대해 알아보자
    2024년 01월 19일 11시 53분 37초에 업로드 된 글입니다.
    작성자: 핀수
    728x90
    반응형

    DataStore

    • SharedPreferences를 대체
    • 개선된 신규 데이터 저장소 솔루션
    • Kotlin 코루틴과 Flow를 기반으로 함
    • 비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장 → SharedPreferences의 단점을 일부 극복
      • UI 스레드 차단
        • 디스크 I/O 작업을 하는 동기 API가 존재
        • apply()fsync()에서 UI 스레드를 차단합니다. 대기 중인 fsync() 호출은 서비스가 시작되거나 중지될 때마다, 그리고 애플리케이션에서 활동이 시작되거나 중지될 때마다 트리거됩니다. UI 스레드는 apply()에서 예약한 대기 중인 fsync() 호출에서 차단되며 주로 ANR의 소스가 됩니다.
      • 런타임 예외
        • 파싱 오류를 런타임 예외로 처리
    • Preferences DataStore와 Proto DataStore로 나뉨
      • Preferences DataStore
        • key를 사용함
        • 타입에 제약이 없음
      • Proto DataStore
        • 커스텀 데이터 타입의 인스턴스 데이터 저장에 사용
        • 타입 객체를 저장

    사용 시 주의점

    1. 같은 프로세스에서 특정 파일의 DataStore 인스턴스를 두개 이상 만들지 않는다.
      • 이 경우 모든 DataStore의 기능이 중단됨
      • 동일한 프로세스에서 DataStore를 통해 읽기나 쓰기를 수행할 경우 DataStore에서 IllegalStateException을 발생시킴
    2. DataStore의 일반 유형은 변경되어선 안됨 (변경 불가능 해야함)
    3. 한 파일에서 SingleProcessDataStore와 MultiProcessDataStore를 함께 사용해선 안됨
      • 둘 이상의 프로세스에서 DataStore에 액세스 하려면 MultiProcessDataStore를 사용하면 됨

    Preferences DataStore 사용해보기

    위에서 살펴봤듯 Preferences DataStore는 SharedPreferences랑 비슷하다. (이름부터가..)

    SharedPreferences와의 차이점은 다음과 같다.

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

    종속 항목 추가

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

     

    1. 인스턴스 생성하기

    // providing delegate 연산자를 통해 인스턴스 생성
    // 이를 통해 이 이름을 가진 인스턴스가 하나만 있음을 보장
    private val Context.dataStore by preferencesDataStore(
        name = "SamplePreferencesDataStore"
    )

     

    2. 데이터 읽기

    Preferences DataStore는 환경설정이 변경될 때마다 방출되는 Flow<Preferences>에 저장된 데이터를 노출한다.

    전체 Preferences가 아니라 원하는 객체만 노출하려면 Flow<Preferences>를 매핑하고 Key 기반으로 원하는 값을 가져와야 한다.

    // key 생성
    private object PreferencesKeys {
        val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
    }

     

    booleanPreferencesKey 메서드로 boolean 값을 가진 key를 생성한다.

    같은 Preferences 내에서 중복된 key를 사용할 경우 같은 타입이라면 덮어씌워지겠지만

    다른 타입일 경우 ClassCastException 가 발생하게 되니 주의하자.

    val userPreferencesFlow: Flow<UserPreferences> = context.dataStore.data
            .catch { exception ->
                // 예외처리
                // 데이터스토어가 파일을 읽는 도중 예외가 발생하면 IOException이 발생함
                // 해당 예외가 발생하면 emptyPreferneces()를 방출하면 처리할 수 있다고 함
                // 다른 예외일 경우 예외를 다시 발생시키는 것이 좋음
                when (exception) {
                    // emptyPreferences() : a new Preferences instance with no preferences set
                    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에서 원하는 키를 통해 값을 가져와 우리가 원하는 객체 (UserPreferences)를 매핑한다.

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

    데이터를 쓰기 위해 정지함수 edit()을 제공한다.

    이 함수는 DataStore에서 트랜잭션 방식으로 데이터를 업데이트 할 수 있는 transform 블럭을 허용한다.

     

    transform에 전달된 MutablePreferences는 이전에 실행된 수정사항으로 최신 상태가 된다.

    transform 블럭에서 MutablePreferences의 모든 변경사항은 transform 블럭이 모두 실행된 후

    edit이 완료되기 전에 디스크에 적용된다.

    MutablePreferences에서 하나의 값을 설정하면 다른 모든 환경설정이 변경되지 않고 유지된다.

    transform 외부에서 MutablePreferences를 수정하면 안된다.

    public suspend fun DataStore<Preferences>.edit(
        transform: suspend (MutablePreferences) -> Unit
    ): Preferences {
        return this.updateData {
            // It's safe to return MutablePreferences since we freeze it in
            // PreferencesDataStore.updateData()
            it.toMutablePreferences().apply { transform(this) }
        }
    }

     

    원하는 속성을 업데이트 할 수 있는 updateShowCompleted 함수를 만들었다.

    suspend fun updateShowCompleted(showCompleted: Boolean) {
        context.dataStore.edit { preferences ->
            // 타입 키가 있는 Map 및 MutableMap과 유사한 API를 노출하기 때문에 아래와 같이 사용할 수 있는 것임
            preferences[PreferencesKeys.SHOW_COMPLETED] = showCompleted
        }
    }

     

    edit()도 마찬가지로 디스크에 쓰는 도중 예외가 발생하면 IOException을 발생시킬 수 있다.

    transform에서 생긴 다른 오류의 경우 edit()에 의해 발생한다.

     

    3. 사용해보기

    아주아주아주 간단한 화면 구성으로 테스트를 해보았다.

    ShowCompleted 값을 받아와 화면에 띄우고, 버튼을 누르면 반대값을 저장해 화면에 나타나도록 했다.

     

    DataStore 구현은 오래 걸리지 않았는데 컴포즈를 몰라섴ㅋㅋㅋㅋㅋ오래 걸렸다.

    얼른 마무리해두고 컴포즈부터 제대로 공부하러 가야겠다..


    공부하며 작성된 글이라 잘못된 정보가 있을 수 있습니다.

    말씀해주시면 수정하겠습니다. 감사합니다.

    References

    아래 글을 참고하여 작성 되었습니다.

     

    Preferences Datastore를 사용하여 작업하기  |  Android Developers

    이 Codelab에서는 샘플 앱을 수정하여 SharedPreferences를 대체하는 새로운 향상된 데이터 저장소 솔루션인 Jetpack Preferences Datastore를 통합합니다.

    developer.android.com

     

     

    앱 아키텍처: 데이터 영역 - Datastore - Android 개발자  |  Android Developers

    데이터 영역 라이브러리에 관한 이 앱 아키텍처 가이드를 통해 Preferences DataStore 및 Proto DataStore, 설정 등을 알아보세요.

    developer.android.com

     

     

    Preferences Datastore를 사용하여 작업하기  |  Android Developers

    이 Codelab에서는 샘플 앱을 수정하여 SharedPreferences를 대체하는 새로운 향상된 데이터 저장소 솔루션인 Jetpack Preferences Datastore를 통합합니다.

    developer.android.com

     

    728x90
    반응형
    댓글