
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")
}
...
dependencies{
...
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.2"
}
generateProtoTasks {
all().forEach { tasks ->
tasks.builtins {
register("kotlin") {
option("lite")
}
}
}
}
}
// 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;
}
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)
}
@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"
}
}
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
}
}
@Module
@InstallIn(SingletonComponent::class)
abstract class DataStoreManagerModule {
@Binds
@Singleton
abstract fun bindXxxDataStoreManager(
xxxDataStoreManagerImpl: XxxDataStoreManagerImpl
): XxxDataStoreManager
}
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)
}
}
}
}