Preferences DataStore 234 has no schema: a misspelled key compiles, and a value's type is checked only when read. A typed DataStore stores one immutable object instead, written by a Serializer<T> you supply. The classic form, Proto DataStore, generates that class from a Protocol Buffers schema (message SyncState { string source = 1; ... }) with the com.google.protobuf Gradle 19,597 plugin. The same Serializer interface also accepts JSON through kotlinx.serialization 5,947 , which BookNest already uses for its routes, so it stores its sync state that way, with no protobuf toolchain:
@Serializable
data class SyncState(val source: String = "none", val books: Int = 0, val at: Long = 0)
object SyncStateSerializer : Serializer<SyncState> {
override val defaultValue = SyncState()
override suspend fun readFrom(input: InputStream): SyncState = try {
Json.decodeFromString(SyncState.serializer(), input.readBytes().decodeToString())
} catch (e: SerializationException) {
throw CorruptionException("sync.json is not a SyncState", e)
}
override suspend fun writeTo(t: SyncState, output: OutputStream) =
output.write(Json.encodeToString(SyncState.serializer(), t).encodeToByteArray())
}
val Context.syncStore: DataStore<SyncState> by dataStore("sync.json", SyncStateSerializer)Updates are transformations of the whole object, sync.updateData { it.copy(...) } or a new instance, applied atomically. After the repository of Local Catalog Cache seeded the cache, the file on the device read:
{"source":"assets","books":6,"at":1790286303059}Protobuf 59,573 gives a compact binary file and schema evolution by field number; JSON gives a readable file and no extra plugin.