ZenitechSoftware / Krate

A SharedPreferences wrapper powered by Kotlin delegates
Apache License 2.0
288 stars 11 forks source link

How/Can I use a custom KSerializer? #23

Closed joostfunkekupper closed 3 years ago

joostfunkekupper commented 3 years ago

Is it possible to use a custom defined KSerializer to store an object that does not have the @Serializable annotation defined? For example, let's say I want to store a Date using the kotlinxPref delegate – basing this on the https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#serializing-3rd-party-classes example.

I've defined a KSerializer in my Kotlin file.

@file:UseSerializers(DateAsLongSerializer::class)

object DateAsLongSerializer : KSerializer<Date> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
    override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}

And created a SerializersModule so that I can set that in the json extension property.

val module = SerializersModule {
    contextual(DateAsLongSerializer)
}

And then defined a SimpleKrate as;

class DateKrate(context: Context) : SimpleKrate(context) {
        init {
            json = Json { serializersModule = module }
        }

        var currentDate: Date? by kotlinxPref("current-date")
    }

But when I try to assign a value to that Krate I get the following exception. Serializer for class 'Date' is not found. Mark the class as @Serializable or provide the serializer explicitly.

Any ideas of what I might be doing wrong here? (Thanks for creating this great library btw)

Edit: The alternative is to just store it as a private longPref and define a getter/setter which casts the value to and from a long. Just curious though on the use of the KSerializer.

zsmb13 commented 3 years ago

Good catch, thanks for reporting this! This is a bug in the library, it simply doesn't use the custom Json instance that you're setting. I'll fix this up and release a new version soon.

zsmb13 commented 3 years ago

You can follow the upcoming release here https://github.com/AutSoft/Krate/pull/24

Got a bunch of other things to include in it too

joostfunkekupper commented 3 years ago

You can follow the upcoming release here https://github.com/AutSoft/Krate/pull/24

Whoa, that was quick! Thanks so much, I'll keep eye on it.

zsmb13 commented 3 years ago

1.2.0 is now live on MavenCentral. Please check that it fixes the bug 😃

joostfunkekupper commented 3 years ago

Thanks for doing a release so quickly. Sadly it hasn't fixed my issue. Still get the same exception being thrown.

Edit: I forgot to update hu.autsoft:krate-kotlinx to 1.2.0 as well, but that seems to have lost the kotlinx.serialization package. So now I can't compile.

joostfunkekupper commented 3 years ago

Ok, so–after a brief 6.0 earthquake here in Melbourne, Australia (we're all safe)–I've been able to compile after adding the org.jetbrains.kotlinx:kotlinx-serialization-core:1.2.2 and org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2 dependencies since that was removed from hu.autsoft:krate-kotlinx. Maybe worth mentioning that in the README.

I now get a runtime exception in the KotlinxDelegate.getValue() method because we're trying to cast from a Long to a String. The SerialDescriptor I had set up was to store the value as a PrimitiveKind.LONG.

Caused by: java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
        at android.app.SharedPreferencesImpl.getString(SharedPreferencesImpl.java:302)
        at hu.autsoft.krate.kotlinx.optional.KotlinxDelegate.getValue(KotlinxDelegate.kt:22)

Edit: Scrap that! I was storing that value using longPref using the same key name. So when I changed it to using the kotlinxPref using the serializer, it of course failed 🤦‍♂️

So after an reinstall of the app, it is all working! 🎉 Thanks so much for the quick fix.

joostfunkekupper commented 3 years ago

Márton came in with a quick fix–what a legend!

zsmb13 commented 3 years ago

Glad it works! 🎉

since that was removed from hu.autsoft:krate-kotlinx

I'm not sure what you mean by this part though, kotlinx-serialization-json has always been just an implementation dependency of krate-kotlinx, so you had to add it yourself if you wanted to interact with its APIs directly. And this hasn't changed in this new release.

joostfunkekupper commented 3 years ago

Previously I did not need kotlinx-serialization-json in my project. I was able to just use krate-kotlinx:1.0.0 and use the kotlinxPref without needing to include kotlinx-serialization-core. The import kotlinx.serialization.* compiled with just krate-kotlinx. Then when I switched to 1.2.0 I needed to add those dependencies.

kotlinx-serialization-json was needed for their Json class which I wasn't using before trying to implement the serializer.

Gradle dependencies... 🤷‍♂️

zsmb13 commented 3 years ago

Ah, got it. Back in 1.0.0, the publishing script I was using was incorrect, and pulled in too many things as a compile dependency, exposing them transitively despite them being listed as implementation dependencies. I fixed this in later versions.