0

Decided to try Datastore and it needs Serializer<T> to be implemented so it can save data.

I came up with such class using Protobuf:

object UserDataSerializer : Serializer<LogInState> {
    override val defaultValue: LogInState = LogInState.NotLoggedIn

    override suspend fun readFrom(input: InputStream): LogInState {
        try {
            return ProtoBuf.decodeFromByteArray(input.readBytes())
        } catch (exception: SerializationException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(
        t: LogInState,
        output: OutputStream,
    ) {
        val encodeToByteArray = ProtoBuf.encodeToByteArray(t)
        output.write(encodeToByteArray)
    }
}

and it works fine, but later I will also need serializers for other classes. So I wrote an abstract class:

abstract class ProtobufSerializer<T: Any> : Serializer<T> {

    override suspend fun readFrom(input: InputStream): T {
        try {
            return ProtoBuf.decodeFromByteArray(input.readBytes())
        } catch (exception: SerializationException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(
        t: T,
        output: OutputStream,
    ) {
        val encodeToByteArray = ProtoBuf.encodeToByteArray(t)
        output.write(encodeToByteArray)
    }
}

but it complains that Cannot use 'T' as reified type parameter. Use a class instead. for the ProtoBuf.encodeToByteArray(t) call.

Any ideas for the base class? I want something like this:

class UserDataSerializer : ProtobufSerializer<LogInState> {
    override val defaultValue: LogInState = LogInState.NotLoggedIn
}

I am using https://github.com/Kotlin/kotlinx.serialization and https://developer.android.com/topic/libraries/architecture/datastore

aSemy
  • 5,485
  • 2
  • 25
  • 51
George Shalvashvili
  • 1,263
  • 13
  • 21

1 Answers1

0

After few days I figured this out. Due to type erasure we can not directly access the T class.

To fix this a reified type argument is needed, sadly it does not support classes, but I came up with a function that directly returns the created class

inline fun <reified T> createProtobufSerializer(defaultValue: T) = object : Serializer<T> {
    override val defaultValue: T = defaultValue

    override suspend fun readFrom(input: InputStream): T {
        try {
            return ProtoBuf.decodeFromByteArray(input.readBytes())
        } catch (exception: SerializationException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(
        t: T,
        output: OutputStream,
    ) {
        val encodeToByteArray = ProtoBuf.encodeToByteArray(t)
        withContext(Dispatchers.IO) {
            output.write(encodeToByteArray)
        }
    }
}

Here is the usage (in AndroidTest)

class DataStoreTest {

    @Test
    fun bla2h() = runBlocking {
        val dataStore = ApplicationProvider.getApplicationContext<App>().userData
        println(dataStore.data.first())
        dataStore.updateData { "zxcv" }
        println(dataStore.data.first())
        println("data")
    }
}


private val Context.userData: DataStore<String> by dataStore(
    fileName = USER_DATA,
    serializer = createProtobufSerializer("123")
)
George Shalvashvili
  • 1,263
  • 13
  • 21