Proto Data Store 사용방법

Ham's Velog·2024년 4월 21일
post-thumbnail

의존성 추가

plugins {
	id("com.google.protobuf") version "0.9.4"
}

dependencies {
	implementation("androidx.datastore:datastore:1.0.0")
	implementation("com.google.protobuf:protobuf-kotlin-lite:3.25.2")
}

Protobuf 정의

...
dependencies{
	...
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.25.2"
    }
    generateProtoTasks {
        all().forEach { tasks ->
            tasks.builtins {
                register("kotlin") {
                    option("lite")
                }
            }
        }
    }
}

.proto 파일 생성

// proto datastore를 사용하는 모듈의 `모듈명`/src/main/xxx_preferences.proto 파일 생성

syntax = "proto3";

option java_package = "해당 모듈의 패키지 명";
option java_multiple_files = true;

message XxxPreferences {
	// protocol buffers 자료형 참고
	// https://protobuf.dev/programming-guides/proto3/
    string value = 1;
}

Serializer 생성

object XxxPreferencesSerializer : Serializer<XxxPreferences> {

    override val defaultValue: XxxPreferences = XxxPreferences.getDefaultInstance()

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

    override suspend fun writeTo(t: XxxPreferences, output: OutputStream) =
        t.writeTo(output)

}

Hilt를 사용하여 DataStore 인스턴스 생성

@Module
@InstallIn(SingletonComponent::class)
class DataStoreModule {

    @Provides
    @Singleton
    fun provideXxxPreferencesProtoDataStore(
        @ApplicationContext context: Context,
        xxxPreferencesSerializer: XxxPreferencesSerializer
    ) : DataStore<XxxPreferences> =
        DataStoreFactory.create(
            serializer = xxxPreferencesSerializer,
            produceFile = {
                context.dataStoreFile(XXX_PREFERENCES_FILE)
            }
        )

    companion object {
        private const val XXX_PREFERENCES_FILE = "xxx_preferences.pb"
    }

}

DataStoreManager 정의 및 구현

interface XxxDataStoreManager {

    suspend fun setValue(value: String): String

    fun getValue(): Flow<String>

}

class XxxDataStoreManagerImpl @Inject constructor(
    private val xxxPreferences: DataStore<XxxPreferences>
) : XxxDataStoreManager {

    override suspend fun setValue(value: String): String {
        val updatedPreferences = userPreferences.updateData { preferences ->
            preferences.toBuilder()
                .setValue(value)
                .build()
        }

        return updatedPreferences.value
    }


    override fun getValue(): Flow<String> =
        userPreferences.data.map { preferences ->
            preferences.value
        }

}

DataStoreManager DI 생성

@Module
@InstallIn(SingletonComponent::class)
abstract class DataStoreManagerModule {

    @Binds
    @Singleton
    abstract fun bindXxxDataStoreManager(
    	xxxDataStoreManagerImpl: XxxDataStoreManagerImpl
    ): XxxDataStoreManager

}

주의사항!

1. KSP를 사용하는 Hilt로 DataStore DI를 생성하는 경우

DataStore를 DI로 구현해놓고 사용할 때 주의할 점이 있다. 프로젝트를 빌드할 때 Ksp를 사용할 경우 Protocol Buffer 코드보다 ksp의 태스크가 먼저 빌드되는 경우가 발생한다. 이 경우 Hilt 내부 코드에서는 Protocol Buffer 코드가 빌드되지 않았기 때문에 DataStore<XxxPrefer>를 참조할 수 없어 에러가 발생한다. 이를 해결하기 위해 프로젝트 빌드 시 Proto Buffer를 KotlinCompiler 태스크에 같이 빌드되도록 지정해주어 에러를 해결할 수 있다.

// Issue Ref
// https://github.com/google/ksp/issues/1590#issuecomment-1846036028

androidComponents {
    onVariants(selector().all()) { variant ->
        afterEvaluate {
            val capName = variant.name.capitalized()
            tasks.getByName<KotlinCompile>("ksp${capName}Kotlin") {
                setSource(tasks.getByName("generate${capName}Proto").outputs)
            }
        }
    }
}
profile
#안드로이드개발자

0개의 댓글