I'm trying to store class with List in proto datastore on android using kotlin by following the instruction in theType-Safe Preferences With Proto DataStore (without Protobuf Files!) - Full Guide on youtube. The class I want to store in the store looks like this:
@Serializable
data class QuizResult(
val quizNameId:Int,
val quizAboutId:Int,
val answers: PersistentList<QuestionResult> = persistentListOf()
)
@Serializable
data class QuestionResult(
val questionId:Int,
val yesAnswered:Boolean
)
I have also serializer for the class:
object QuizResultSerializer : Serializer<QuizResult> {
override val defaultValue: QuizResult
get() = QuizResult(-1,-1)
override suspend fun readFrom(input: InputStream): QuizResult {
return try{
Json.decodeFromString(
deserializer = QuizResult.serializer(),
string = input.readBytes().decodeToString()
)
}catch(e: SerializationException){
e.printStackTrace()
defaultValue
}
}
override suspend fun writeTo(t: QuizResult, output: OutputStream) {
output.write(
Json.encodeToString(
serializer = QuizResult.serializer(),
value = t
).encodeToByteArray()
)
}
}
Database itself is defined in following way:
val Context.dataStore by dataStore("quiz-results.json",QuizResultSerializer)
I gather list of results in view model as mutableListOf<QuestionResult>()
in results variable. When all answers are given, I'm trying to store it in the datastore with following code:
scope.launch {
context.dataStore.updateData {
it.copy(
quizNameId = 123456,
quizAboutId = 456789,
answers = results.toPersistentList()
)
}
}
But when it's executed I'm getting runtime error stating that:
Process: com.example.redflagdetector, PID: 8535 kotlinx.serialization.SerializationException: Class 'SmallPersistentVector' is not registered for polymorphic serialization in the scope of 'PersistentList'. To be registered automatically, class 'SmallPersistentVector' has to be '@Serializable', and the base class 'PersistentList' has to be sealed and '@Serializable'.
When I comment out the line answers = results.toPersistentList()
then there is no error and both quizNameId and quizAboutId are stored properly.
Any help why this is happening is greatelly appreciated.
This is due to the fact that in order to serialize Kotlin immutable collections, you have to write a custom serializer for the immutable collection that you are using.
Therefore, in your case, you have to write a Custom PersistentList
serializer similar to the one below:
@OptIn(ExperimentalSerializationApi::class)
@Serializer(forClass = PersistentList::class)
class MyPersistentListSerializer(
private val serializer: KSerializer<QuestionResult>,
) : KSerializer<PersistentList<QuestionResult>> {
private class PersistentListDescriptor :
SerialDescriptor by serialDescriptor<List<QuestionResult>>() {
@ExperimentalSerializationApi
override val serialName: String = "kotlinx.serialization.immutable.persistentList"
}
override val descriptor: SerialDescriptor = PersistentListDescriptor()
override fun serialize(encoder: Encoder, value: PersistentList<QuestionResult>) {
return ListSerializer(serializer).serialize(encoder, value)
}
override fun deserialize(decoder: Decoder): PersistentList<QuestionResult> {
return ListSerializer(serializer).deserialize(decoder).toPersistentList()
}
}
Then bind this serializer to your PersistentList
inside your QuizResult
data class using @Serializable
annotation:
@Serializable
data class QuizResult(
val quizNameId: Int,
val quizAboutId: Int,
@Serializable(with = MyPersistentListSerializer::class)
val answers: PersistentList<QuestionResult> = persistentListOf()
)
And that's it! Now when you try to store your data class inside DataStore
, you won't get an exception.
Note: Do not confuse MyPersistentListSerializer
with QuizResultSerializer
, they each do separate things and you need to have both of them.
Note: This Medium article is worth reading for anyone wondering how to serialize PersistentMap
, or for anyone who wants to dig deeper on this topic.