시작하기 전에
현재 페스타고 앱은 토큰 관리를 EncryptedSharedPreferences 로 하고 있다.
그러나 공식 문서는 SharedPreferences 를 DataStore 로 이전할 것을 권장한다.
이전하기 전에 DataStore 가 무엇인지 공부해보자!
이 글은 안드로이드 공식 문서를 바탕으로 작성되었습니다.
https://developer.android.com/topic/libraries/architecture/datastore?hl=ko
https://developer.android.com/codelabs/android-preferences-datastore?hl=ko
DataStore 란?
DataStore 는 Protocol Buffer 를 사용해 Key - value 쌍 또는 유형이 지정된 객체를 저장할 수 있는 저장소이다.
Kotlin Coroutine 및 Flow 를 사용해 비동기적이고 일관적인 트랜잭션 방식으로 데이터를 저장한다.
아직 어떤 의미인지 와닿지 않는다.
잠깐! Protocol Buffer 란?
Protocol Buffers are language-neutral, platform-neutral extensible mechanisms for serializing structured data.구조화된 데이터 직렬화를 위한 언어 중립적이고 플랫폼 중립적인 확장 가능한 메커니즘이라고 한다.
Google에서 개발한 이 프로토콜은 데이터를 효율적으로 저장하고 전송하기 위한 목적으로 디자인 되었다고 한다.
공식문서에서는 다음과 같이 권장하고 있다.
SharedPreferences 를 사용해 데이터를 저장하고 있다면 DataStore 로 이전하는 것이 좋다.
그러나 복잡한 데이터 셋, 부분 업데이트, 참조 무결성을 지원해야한다면 DataStore 대신 Room 을 사용하라.
DataStore 는 소규모 단순 데이터 셋에 적합하며 부분 업데이트나 참조 무결성은 지원하지 않는다.
DataStore 의 종류
- Preferences DataStore : 키를 사용해 데이터를 저장하고 데이터에 엑세스한다. 이 구현은 Type Safety 를 제공하지 않으며 사전 정의된 스키마가 필요하지 않다.
- Proto DataStore : 맞춤 데이터 type 의 인스턴스로 데이터를 저장한다. Type Safety 를 제공하며 Protocol buffer 를 사용해 스키마를 정의해야 한다.
DataStore 를 올바르게 사용하려면?
1. 같은 프로세스에서 DataStore 인스턴스를 두 개 이상 만들지 않는다. 동일한 프로세스에서 특정 파일의 DataStore 가 여러 개 활성화되어 있다면 데이터 Read 나 Update 시 IllegalStateException 을 발생시킨다.
2. DataStore 의 Type 은 변경 불가능해야 한다. DataStore 에 사용된 Type 을 변경하면 모든 보장이 무효화되며 심각한 버그를 야기할 수 있다. 따라서 불변성을 보장하라.
3. 동일한 파일에서 SingleProcessDataStore 에서 MultiProcessDataStore 를 사용하지 마라
SharedPreferences vs DataStore
특히 Preferences DataStore 는 SharedPreferences 와 비슷하지만 여러 차이점이 존재한다.
- 데이터 업데이트를 트랜잭션 방식
- 현재 상태를 나타내는 Flow 노출
- 데이터 영구 메서드(apply, commit) 가 없음
- 변경 가능한 참조를 내부 상태에 반환하지 않는다.
- 타입 키가 있는 Map, MutableMap 과 같은 유사한 API 를 노출한다.
요약하자면 SharedPreferences는 UI 스레드에서 호출하기에 안전해 보일 수 있는 동기 API가 있고, 오류 신호를 보내는 메커니즘이 없으며 트랜잭션 API가 없다. 거의 모든 단점을 DataStore 는 해결하며 대체한다. Datastore는 Kotlin 코루틴과 Flow를 사용하는 완전 비동기 API가 있으며, 데이터 이전을 처리하고, 데이터 일관성을 보장하고, 데이터 손상을 처리한다.
DataStore 만들기
DataStore 인스턴스는 preferencesDataStore 위임을 사용한다. 이 위임은 앱에서 이 이름을 가진 DataStore 인스턴스가 하나만 존재함을 보장한다.
private val Context.dataStore by preferencesDataStore(name = "user_preferences")
"production 앱에서 DataStore 인스턴스는 DataStoreFactory 를 사용해 인스턴스 사용을 필요로하는 클래스에 싱글톤으로 삽입되어야 한다."
Hilt 를 사용해 Repository 생성시 싱글톤으로 주입해 주면 될 것 같다. 아마 Factory 니깐 provides 로 예상된다.
Preferences DataStore 에서 데이터 읽기
Preferences DataStore 는 환경 설정이 변경될 때마다 emit 되는 Flow<Preferences> 에 저장된 데이터를 노출한다.
먼저 key 를 정의해야 한다.
private object PreferencesKeys {
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
}
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
// 데이터 읽는 도중 에러가 발생하면 IOException 발생
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}.map { preferences ->
// 정상적으로 읽었으면 값을 전달한다. Default 는 false 이다.
val showCompleted = preferences[PreferencesKeys.SHOW_COMPLETED]?: false
UserPreferences(showCompleted)
}
Preferences DataStore 에서 데이터 쓰기
데이터 쓰기를 위해 DataStore.edit(transform: suspend(MutablePreferences) -> Unit) 을 제공한다.
이 함수는 트랜잭션 방식으로 상태를 업데이트할 수 있는 transform 블록을 허용한다.
transform 블록에 전달된 MutablePreferences 는 이전에 실행된 수정 사항으로 최신 상태가 된다. 블록에서 모든 변경 사항은 transform 완료 후 edit 이 완성되기 전에 디스크에 적용된다.
"변환 블록 외부에서 MutablePreferences 를 수정하지 마세요."
suspend fun updateShowCompleted(showCompleted: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.SHOW_COMPLETED] = showCompleted
}
}
얘도 마찬가지로 디스크를 읽거나 쓰는 과정에 오류 발생시 IOException 을 발생시킨다.
SharedPreferences 에서 Preference DataStore 로 옮기기
DataStore 로 이전하려면 dataStore 빌더를 업데이트해 SharedPreferencesMigration 을 전달해야 한다. 자동이전 가능하며 이전은 데이터 엑세스 전에 실행된다. 이전에 성공해야 읽기 및 쓰기가 가능하다.
참고: 키들은 SharedPreferences 에서 한번만 이전되므로 이전 이후에는 SharedPreferences 를 사용해선 안된다.
마무리하며
갑자기 급하게 마무리한것 같나요?
맞습니다... 다른 부분은 실제로 적용하면서 공부하는게 낫다고 판단했기 때문입니다.
그럼 이론은 여기까지하고 다음편으로 돌아오겠습니다ㅎㅎ
공부할 키워드
1. FULL Guide to Encryption & Decryption in Android (Keystore, Ciphers and more)
2. How to Encrypt DataStore in Android
'Android' 카테고리의 다른 글
페스타고 상세 페이지 이동 애니메이션 변경 및 UX 개선 (0) | 2024.05.12 |
---|---|
[Android] Encryption & Decryption in Android (0) | 2024.01.10 |
RecyclerView 목록 스크롤에 CoordinatorLayout 적용하기 (0) | 2023.11.17 |
[안드로이드] Repository Pattern (저장소 패턴) (0) | 2023.10.26 |
뷰가 그려지는 과정(View Lifecycle) 이해하기 : [우아한테크코스 5기 AN_베르] (0) | 2023.10.22 |