Android Studio에서 Proto DataStore 설정하는 방법 (gradle kotlin)

2024. 11. 7. 08:00Android/Jetpack

728x90
반응형

Proto DataStore에 대한 친절한 정보가 많이 없어서

제가 열심히 삽질해가며 작성한 글입니다... 😭

 

참고한 소스코드 : NowInAndroid


[versions]
# datastore
androidxDataStore = "1.1.1"
protobufPlugin = "0.9.4"
protobuf = "4.26.1"
junitJunit = "4.12"

[libraries]
# AndroidX
androidx-dataStore = { group = "androidx.datastore", name = "datastore", version.ref = "androidxDataStore" }
androidx-dataStore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "androidxDataStore" }
androidx-dataStore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "androidxDataStore" }

# google protobuf
protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" }
protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
junit-junit = { group = "junit", name = "junit", version.ref = "junitJunit" }

[plugins]
# google protobuf
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }

기능 세팅 전, libs.versions.toml 사전에 추가할 내용들.

미리 추가해두면 gradle에 implementation이 훨씬 수월해진다.


DataStore Module

1. Gradle 설정

plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.hilt)
}

android {
    namespace = "{packagename}.core.datastore"
}

dependencies {
    api(libs.androidx.dataStore)
    api(libs.androidx.dataStore.core)
    api(libs.androidx.dataStore.preferences)
    implementation(project(":core:common"))
    implementation(project(":core:datastore-proto"))
}

2. DI DataStoreModule 객체 생성

/**
 * < Proto DataStore >
 * UserPreferences는 core:datastore-proto 모듈 안, user_preferences.proto 파일에 위치 (message)
 */
@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {

    @Provides
    @Singleton
    internal fun providesUserPreferencesDataStore(
        @ApplicationContext context: Context,
        @Dispatcher(IO) ioDispatcher: CoroutineDispatcher,
        @ApplicationScope scope: CoroutineScope,
        userPreferencesSerializer: UserPreferencesSerializer,
    ): DataStore<UserPreferences> =
        DataStoreFactory.create(
            serializer = userPreferencesSerializer,
            scope = CoroutineScope(scope.coroutineContext + ioDispatcher),
        ) {
            context.dataStoreFile("user_preferences.pb")
        }
}

의존성 주입을 위해 모듈 객체를 생성해야 한다.

위치는 모듈 내 di 패키지를 만들어 클래스 파일 생성할 것을 권장한다.

이 소스에서 UserPreferences는 다음에 만들 datastore-proto 모듈 내에 생성될 예정이다.

3. 데이터 직렬화 - 역직렬화 담당 클래스를 생성한다.

class UserPreferencesSerializer @Inject constructor() : Serializer<UserPreferences> {
    override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): UserPreferences =
        try {
            UserPreferences.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }

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

readFrom

  • DataStore에서 데이터를 읽어오는 메서드
  • Protobuf 파일을 사용할 경우, UserPreferences와 같은 데이터 구조를 InputStream에서 읽어서 프로토콜 버퍼 메시지로 변환하는 역할

writeTo

  • DataStore에 데이터를 저장하는 메서드
  • 주로 Protocol Buffers 형식으로 데이터를 저장
  • 데이터를 OutputStream에 작성하는 작업을 수행하며, DataStore의 백그라운드 스레드에서 비동기적으로 호출되어야 한다

DataStore-Proto 모듈

1. datastore-proto gradle 세팅

plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.protobuf)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace = "{packagename}.core.datastore.proto"
}

protobuf {
    protoc {
        artifact = libs.protobuf.protoc.get().toString()
    }
    generateProtoTasks {
        all().forEach { task ->
            task.builtins {
                register("java") {
                    option("lite")
                }
                register("kotlin") {
                    option("lite")
                }
            }
        }
    }
}

// Android 프로젝트에서 프로토콜 버퍼(protobuf)로 생성된 소스 파일을 빌드 경로에 추가
// DataStore를 Proto 형식으로 사용할 때 프로토콜 버퍼로 생성된 코드를 소스 세트에 포함하기 위해 필요
androidComponents {
    beforeVariants {
        android.sourceSets.getByName(it.name) {
            val buildDir = layout.buildDirectory.get().asFile
            java.srcDir(buildDir.resolve("generated/source/proto/${it.name}/java"))
            kotlin.srcDir(buildDir.resolve("generated/source/proto/${it.name}/kotlin"))
        }
    }
}

dependencies {
    api(libs.protobuf.kotlin.lite)
}

gradle 작성 중 protobuf를 인식하지 못한다면 plugins 내에 alias(libs.plugins.protobuf) 를 선언해주고 sync를 한 번 해주면 import가 정상적으로 된다.

androidComponents 는 Android 프로젝트에서 프로토콜 버퍼(protobuf)로 생성된 소스 파일을 빌드 경로에 추가하기 위한 코드이다.

이 전 문서 참고.

2. user_preference.proto 파일 생성

syntax = "proto3";

option java_package = "{pakcagename}.core.datastore.proto";
option java_multiple_files = true;

message UserPreferences {
    string user_key = 1;
    string user_name = 2;
    bool is_logged_in = 3;
}

파일 위치 : 모듈 패키지 > src > main > proto

절대 java 파일 내에 위치하면 안된다.

나는 처음 위치 생성을 잘못해서 파일을 인식하지 못해 하루동안 삽질했다 ㅠㅠ

3. Clean Project → Rebuild Project

이렇게까지 작성해도 DataStore 모듈 내에서는 UserPreferences 파일을 인식하지 못한다.

Rebuild를 한 번 해주면 build 폴더 내에 UserPreferencesKt.kt 파일이 생성되어 정상적으로 읽힌다.

나 같은 경우는 junit이 제대로 추가되지 않아 rebuild 시 오류가 발생하는데, 그래도 proto 파일을 읽는 데에는 문제가 없었다.


정상적으로 build가 성공된 소스코드

DataStoreModule에 정상적으로 import 된 UserPreferences
build 파일로 생성된 UserPreferences

 

이걸 생성하기까지 얼마가 고되고 힘들었는가... 😭😭😭

너무 감격에 겨워 적는 글 💛

728x90
반응형