프로그래밍/Android

[안드로이드] Proto DataStore 사용법

Lou Park 2020. 10. 10. 13:43

원문

- medium.com/swlh/welcome-datastore-good-bye-sharedpreferences-fdeb831a1e58

- developer.android.com/topic/libraries/architecture/datastore

원문을 읽으면서 제가 공부하려고 번/의역한 것 입니다.
아직 블로그 반말을 써야할지 존댓말을 써야할지...안정해서 혼란스러운점 양해부탁드립니다...ㅋㅋㅋ
이제부터 그냥 존댓말 쓸게요. 나혼자 주저리 리뷰글이아니라면...


Proto DataStore가 Preferences Data Store와 다른 점을 알아보려면 제 이전 글을 읽어주세요!

- [Android] Preferences DataStore 사용법과 개념


Proto DataStore 간단 개요!

- 사용자가 정의한 형식의 데이터(Custom data types)를 저장하고 가져옵니다.

- Protocol Buffers를 이용해 스키마를 정의합니다.

- 작고, 빠르고, 단순하고, XML을 비롯한 다른 데이터 형식들보다 덜 모호합니다.

 

Protocol Buffers는 뭔데요?

Protocol Buffers는 구글의 *데이터를 직렬화 하기위한 메커니즘입니다. 언어 중립적/플랫폼 중립적/확장 가능하다는 특징이있습니다.

Protocol Buffers는 현재 Java, Python, Objective-C, 그리고 C++에서 생선된 코드를 지원합니다.

*데이터의 직렬화(serialization): 메모리의 객체를 디스크에 저장하거나, 네트워크를 통해 전송되는 형식으로 변환하는 작업.
*역직렬화(deserialization): 디스크에 저장된 데이터를 읽거나, 네트워크를 통해 전송된 데이터를 받아 메모리에 재구축하는 것.
- 출처: 118k.tistory.com/740

 

 

1.Gradle Settings

Proto DataStore를 사용하려면 아래의 dependency가 필요합니다. (*protobuf 내용 추가완료!)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id "com.google.protobuf" version "0.8.12"
}

dependencies {
    implementation "androidx.datastore:datastore-core:1.0.0-alpha01"
    // Protobuf
    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'
                }
            }
        }
    }
}

 

 

2. 스키마 정의하기
app/src/main/proto경로에 스키마를 정의해야합니다. 우리는 user_info.proto라는 파일을 app/src/main/proto경로 아래에 만들어 보겠습니다. 프로토 스키마에 관해 더 알아보고 싶다면, 이 페이지를 참고해주세요.

syntax = "proto3";

option java_package = "본인 패키지 명";
option java_multiple_files = true;

message UserSettings {
  enum BgColor {
    COLOR_WHITE = 0;
    COLOR_BLACK = 1;
  }
  BgColor bgColor = 1;
}

위의 스키마를 정의한 후 Gradle Project를 Rebuild해줍니다.

 

 

 

3. Proto 클래스를 위한 Serializer 만들기
프로토 파일에 정의한 타입 TSerializer<T>를 implment하는 클래스를 만들어 볼겁니다. 즉 UserSettings가 타입이 됩니다. 이 Serializer 클래스는 DataStore에게 이 타입의 클래스를 어떻게 읽고, 써야하는지 알려줍니다.

object ProtoSettingsSerializer : Serializer<UserSettings> {
    override fun readFrom(input: InputStream): UserSettings {
        try {
            return UserSettings.parseFrom(input)
        } catch (ex: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", ex)
        }
    }

    override fun writeTo(t: UserSettings, output: OutputStream) {
        t.writeTo(output)
    }
}

 

 

4. Proto DataStore 만들기
Context.createDataStore()를 이용하여 DataStore<UserSettings>의 인스턴스를 만들어 볼 것입니다. filename 파라미터는 DataStore가 데이터를 저장하기 위해 어떤 파일을 사용해야하는지 알려줍니다. 그리고 serializer 파라미터는 Serializer 클래스의 이름입니다. 우리가 앞서 정의한 ProtocolSettingsSerializer를 사용해주면 되겠죠.

아래 코드는 DataStore를 생성하는 부분입니다. 후에 읽기/쓰기시 다루기 편하도록 ProtoSettingsManager라는 클래스를 만들었습니다.

class ProtoSettingsManager(val context: Context) {
    private val dataStore: DataStore<UserSettings> =
        context.createDataStore(fileName = "user_settings.pb", serializer = ProtoSettingsSerializer)
}

 

 

5. Proto DataStore에서 데이터 읽기
이전 Preferences DataStore에 관한 글에서 다루었던 것 처럼, DataStore.data를 이용해 저장된 오브젝트에 대한 Flow를 가져옵니다. 아래 코드에서 userSettingsFLow 변수가 UserSettings 객체에 대한 Flow를 보냅니다.

class ProtoSettingsManager(val context: Context) {
    private val dataStore: DataStore<UserSettings> =
        context.createDataStore(fileName = "user_settings.pb", serializer = ProtoSettingsSerializer)

    val userSettingsFlow: Flow<UserSettings> = dataStore.data.catch { ex ->
        if (ex is IOException) {
            emit(UserSettings.getDefaultInstance())
        } else {
            throw ex
        }
    }
}

 

 

6. Proto DataStore에 쓰기
Proto DataStore는 updateData()라는 메소드를 통해 저장된 오브젝트를 업데이트 할 수 있습니다. updateColor()의 인자인 UserSettings.BgColor 역시 user_info.proto에 enum으로 정의되어 있었죠.

class ProtoSettingsManager(val context: Context) {
    private val dataStore: DataStore<UserSettings> =
        context.createDataStore(fileName = "user_settings.pb", serializer = ProtoSettingsSerializer)

    val userSettingsFlow: Flow<UserSettings> = dataStore.data.catch { ex ->
        if (ex is IOException) {
            emit(UserSettings.getDefaultInstance())
        } else {
            throw ex
        }
    }
    suspend fun updateColor(bgColor: UserSettings.BgColor) {
        dataStore.updateData { userSetting ->
            userSetting.toBuilder().setBgColor(bgColor).build()
        }
    }
}

* Activity에서 DataStore를 사용하는 방법은 이 전글과 동일합니다.

 

 

DataStore에 대한 나의 생각

Prefence DataStore는 SharedPreference를 대체하기 적절해보이긴하는데... Proto DataStore는 타입을 보장해주긴 하지만 스키마도 만들어야해서 좀 더 구현하기 귀찮을 것 같다. 하지만 구글 문서 예제처럼 "설정" 값 전체를 하나의 객체로 묶어서 저장하고 업데이트할때 좋을듯....? Key-Value 쌍으로 하기엔 너무 많은것들 같은거? 다음 앱에 한번 써봐야지 ㅎㅎ

 

문서에는 더 많은 데이터 셋이나, 복잡한 데이터 같은 경우에는  Room에 저장해라고 권고하고있다.