Datastore 튜토리얼
Datastore
Jetpack Datastore은 키-값 쌍(Preferences Datastore) 또는 유형(Proto Datastore)이 지정된 객체를 저장할수 있는 데이터 솔루션입니다.
이전에 쓰던 SharedPreference를 Datastore로 이전하는걸 추천합니다.
SharedPreference는 저장시 UI Thread 동작을 멈춥니다.
그 외로도 Datastore을 사용하게 된다면 얻을수 있는 이점들이 많습니다.
- 비동기 처리
- 에러핸들링
- 타입 safety
- 데이터 일관성
Datastore는 간단한 값을 저장할때 쓰기 좋습니다.
문서에도 나와있는데, 복잡한 대규모 데이터 세트나 부분 업데이트, 참조무결성을 원한다면 (말그대로 데이터베이스) ROOM을 사용하는것이 좋습니다.
데이터스토어는 두가지 저장방식을 제공합니다.
- Preference DataStore
- Proto DataStore
두가지 모두 알아보겠습니다.
1. Preference DataStore
키-값으로 저장하는 데이터스토어입니다.
Shared Preference와 비슷합니다.
gradle에 datastore-preferences를 추가합니다.
dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
}
Datastore 인스턴스를 코틀린파일 최상위수준에서 한번 호출해주어야 전역적으로 인스턴스에 액세스할수 있습니다.
아래와 같이 구현하면 쉽게 Datastore를 싱글톤으로 유지할수있습니다.
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
저는 이 코드를 MainActivity.kt 상단에 작성하였습니다.
Datastore에 사용할 키-값을 정합니다.
적절한 타입유형과 키 이름을 지정합니다.
- booleanPreferencesKey
- doublePreferencesKey
- emptyPreferences
- floatPreferencesKey
- 등등... 참고 top-level-functions
val EXAMPLE_COUNT = intPreferencesKey("example_count")
EXAMPLE_COUNT는 preferences를 접근할때 사용하는 Preferences.Key 입니다.
데이터 읽기
val exampleCounterFlow: Flow<Int> = this.dataStore.data.map{preference->
preference[EXAMPLE_COUNT] ?: 0
}
this.dataStore.data의 flow를 생성하고 이를 구독하는 value를 만듭니다.
저는 컴포즈를 사용하고있기 때문에 컴포즈 기준으로 설명드리겠습니다.
val counter = exampleCounterFlow.collectAsState(initial = 0)
counter 변수는 exampleCounterFlow를 구독하고 있습니다.
counter.value로 저장된 값을 사용할수 있습니다.
에러핸들링
flow에서 data에 대한 에러 핸들링이가능합니다.
val exampleCounterFlow: Flow<Int> = this.dataStore.data
.catch( exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
.map{preference->
preference[EXAMPLE_COUNT] ?: 0
}
데이터를 읽는동안 오류가 발생하면 IOException을 발생시킵니다. IOException은 emptyPreferences()를 방출하여 처리할수 있고, 다른 예외상황은 throw를 시키는 편이 좋습니다.
데이터 쓰기
데이터를 쓰는 방법으로는 DataStore.edit(transform: suspend (MutablePreferences) -> Unit)을 제공합니다. 이 함수는 트랜잭션 장식으로 상태를 업데이트할수있는 transform 블록을 허용합니다.
settings의 EXAMPLE_COUNT 값을 증가시키는 예제입니다.
suspend fun increaseCounter() {
this.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNT] ?: 0
settings[EXAMPLE_COUNT] = currentCounterValue + 1
}
}
2. Proto
Proto 버퍼 데이터구조를 가집니다.
복잡한 구조의 데이터를 저장합니다 (enum, list)
Proto Datastore를 사용해야 할때
- 타입 오브젝트를 사용해야 하는경우
- 효과적인 에러핸들링
- 코루틴/flow 비동기처리
- 빠른 데이터 마이그레이션
Protocol Buffers란?
- 구글에서 개발한 다양한 언어와 플랫폼을 지원하는 구조화된 데이터를 직렬화하는 매커니즘
- XML보다 직렬화 속도가 빠르고 크기도 작다
- 쉽게 데이터를 읽을수 있다.
proto datastore 사용해보기
gradle에 datastore을 추가합니다.
implementation("androidx.datastore:datastore:1.0.0")
Proto datastore을 구현하려면 app/src/main/proto/ 디렉터리에 proto 파일에 사전정의된 스키마가 있어야합니다.
https://developers.google.com/protocol-buffers/docs/proto3?hl=ko
proto 스키마에 대해서는 위 링크를 참고해주세요.
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message SettingsPreferences {
int32 example_counter = 1;
}
예제를 따라가다 보니 막히는 부분이 proto 스키마를 만들었으나 Serializer로 생성해주지 않았다.
https://developer.android.com/codelabs/android-proto-datastore?hl=ko#4 문서를 참고해보니 Serializer를 생성하려면 gradle 을 조금 수정해주어야 한다.
plugins {
...
id "com.google.protobuf" version "0.8.12"
}
dependencies {
implementation "androidx.datastore:datastore-core:1.0.0-alpha04"
implementation "com.google.protobuf:protobuf-javalite:3.10.0"
...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.10.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
빌드를 해주면 message에 작성해놓은 SettingsPreferences 가 자동 생성됩니다.
data패키지에 Serializer를 구현합니다.
object SettingsPreferencesSerializer : Serializer<SettingsPreferences>{
override val defaultValue: SettingsPreferences = SettingsPreferences.getDefaultInstance()
override suspend fun readFrom(input: InputStream): SettingsPreferences {
try {
return SettingsPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: SettingsPreferences, output: OutputStream) = t.writeTo(output)
}
이제 데이터스토어를 만들면 되는데 여기서 삽질을 좀 했습니다.
private val dataStore: DataStore<UserPreferences> =
context.createDataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer)
Context에 createDataStore 함수가 없습니다.
원인을 찾아보니 아래 릴리즈 로그에 createDataStore은 Context에서 삭제되었다고 나와
https://developer.android.com/jetpack/androidx/releases/datastore?hl=ko#1.0.0-alpha07
대신 아래와 같이 구현합니다.
val Context.dataProto: DataStore<SettingsPreferences> by dataStore<SettingsPreferences>(
fileName = "settings.db",
serializer = SettingsPreferencesSerializer,
)
값 업데이트 하기
suspend fun increaseProtoCounter() {
this.dataProto.updateData { t: SettingsPreferences ->
t.toBuilder().setExampleCounter(t.exampleCounter + 1).build()
}
}
updateData 함수를 이용해 트랜잭션 방식으로 업데이트 합니다.
Datastore로 간단한 작은 데이터, 상태들을 저장할수 있음을 알수 있었습니다.
정말 간단하게 key-value만으로 저장한다면 Datastore Preference.
Object형식으로 크지 않은 데이터를 저장한다면 protocols buffer를 활용한 Datastore proto를 활용하는게 좋아보입니다.
Best Practice로는 Hilts 로 DI를 구현하라고 합니다.
https://www.youtube.com/watch?v=S10ci36lBJ4&list=PLWz5rJ2EKKc8to3Ere-ePuco69yBUmQ9C&index=5
Hilts를 공부해고 좀더 나은 구조로 바꿔보겠습니다.
참고
https://www.youtube.com/watch?v=9ws-cJzlJkU
https://developer.android.com/codelabs/android-proto-datastore?hl=ko#4
구글 프로토콜 버퍼 - https://bcho.tistory.com/1182
Author And Source
이 문제에 관하여(Datastore 튜토리얼), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@burndown/Datastore-튜토리얼저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)