환경세팅
안드로이드에서 Proto Datastore을 사용하기 위해서는 Type Safety를 위한 처리를 해주어야 하기 때문에 Preference Datastore보다 복잡하다.
Gradle 파일 세팅
1. 모듈 수준의 gradle 파일에 플러그인을 추가한다.
plugins {
..
id("com.google.protobuf") version "0.8.17"
}
2. 모듈 수준의 gradle에 라이브러리를 추가한다. 하나는 datastore 라이브러리이고 다른 하나는 protocol buffer을 위한 java 라이브러리이다.
dependencies {
..
implementation("androidx.datastore:datastore:1.0.0")
implementation("com.google.protobuf:protobuf-javalite:3.19.4")
}
3. protobuf 블록을 module 수준의 gradle에 추가한다. 이 내부의 generateProtoTask는 Protocol Buffer을 사용하기 위해 Code Generation 작업을 해준다.
protobuf {
protoc {
// The artifact spec for the Protobuf Compiler
artifact = "com.google.protobuf:protoc:3.19.4"
}
generateProtoTasks {
all().forEach { tasks ->
tasks.builtins {
id("java") {
option("lite")
}
}
}
}
}
자 여기까지 Proto Datastore을 사용하기 위한 준비를 마쳤다. 이제 본격적으로 사용해보도록 하자.
Proto Datastore 사용하기
1. Proto Datastore을 사용하기 위해서는 먼저 Proto Datastore 내부에 Type Safe하게 저장할 수 있는 객체를 만드는 작업이 필요하다. app/src/main/proto directory를 생성하고 내부에 .proto 파일을 만든다.
2. .proto 파일 내부에는 3가지가 필요하다.
- 첫째 syntax 를 설정하여 protocol buffer 3버전을 사용한다는 것을 명시한다.
- 둘째 proto가 생성될 패키지명을 추가하고 java_multiple_files 옵션을 true로 한다.
- 마지막으로 가장 중요한 message는 Proto Datastore에 입력될 데이터의 구조이다. 이 데이터 구조는 Code Generation이 되면서 클래스로 변환된다.
예제는 다음과 같다.
syntax = "proto3"; // syntax 를 설정하여 protocol buffer 3버전을 사용한다는 것을 명시
option java_package = "[패키지명]"; // 클래스가 생성될 package 명 명시
option java_multiple_files = true;
message OnBoardingState { // 데이터 구조 정의
bool isAddTodo = 1;
bool isChangeSequenceDone = 2;
bool isPermissionGranted = 3;
}
3. .proto 파일을 작성한 후 Rebuild를 눌러 빌드가 되면 message가 java class로 Auto Generation된다. 위 2번에서 생성한 .proto 파일에 의해 생성된 클래스는 [그림2]와 같다.
4. 다음은 Datastore가 [그림2]의 클래스를 직렬화, 역직렬화 할 수 있도록 Serializer을 생성해야 한다. Proto Datastore의 Serializer은 직렬화, 역직렬화를 하도록 하는 인터페이스로 readFrom을 통해 역직렬화가 수행되고 writeTo를 통해 직렬화가 수행된다. 이 Serializer은 직접 구현해야 한다.
Serializer 내부의 구현은 우리가 3번에서 생성한 클래스에 의해 가능하다. 자동 생성된 클래스는 그 자체로 직렬화, 역직렬화 할 수 있는 메서드들을 가지고 있다. parseFrom은 InputStream을 인자로 받아 역직렬화를 하는 메서드이며, writeTo는 객체를 직렬화 하여 OutputStream에 넣는 작업을 하는 메서드이다.
위에서 작성한 OnBoardingState를 위한 Serializer의 구현체는 다음과 같다.
object OnBoardingStateSerializer : Serializer<OnBoardingState> {
override val defaultValue: OnBoardingState = OnBoardingState.getDefaultInstance()
override suspend fun readFrom(input: InputStream): OnBoardingState {
try {
return OnBoardingState.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(
t: OnBoardingState,
output: OutputStream
) = t.writeTo(output)
}
val Context.storeOnBoardingState: DataStore<OnBoardingState> by dataStore(
fileName = "onboardingSettings.pb",
serializer = OnBoardingStateSerializer
)
5. 마지막으로 Datastore을 사용하기 위해 dataStore의 파일 이름을 설정하고 이 dataStore을 사용할 때 사용할 Serializer을 설정한다.
val Context.storeOnBoardingState: DataStore<OnBoardingState> by dataStore(
fileName = "onboardingSettings.pb",
serializer = OnBoardingStateSerializer
)
6. 위와 같이 설정을 마쳤으면 Context가 있는 곳에서 storeOnBoardingState 라는 변수로 dataStore을 사용할 수 있게 된다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
storeOnBoardingState.data
..
}
}